Merge pull request #61 from taigaio/permissions
[HUGE CHANGE] Changed the permissions systemremotes/origin/enhancement/email-actions
commit
c7ecd622e1
|
@ -21,11 +21,10 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.permissions import AllowAny
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework import viewsets
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from taiga.base.api import viewsets
|
||||||
from taiga.base.decorators import list_route
|
from taiga.base.decorators import list_route
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
from taiga.base.connectors import github
|
from taiga.base.connectors import github
|
||||||
|
@ -41,6 +40,8 @@ from .services import public_register
|
||||||
from .services import github_register
|
from .services import github_register
|
||||||
from .services import make_auth_response_data
|
from .services import make_auth_response_data
|
||||||
|
|
||||||
|
from .permissions import AuthPermission
|
||||||
|
|
||||||
|
|
||||||
def _parse_data(data:dict, *, cls):
|
def _parse_data(data:dict, *, cls):
|
||||||
"""
|
"""
|
||||||
|
@ -95,7 +96,7 @@ def parse_register_type(userdata:dict) -> str:
|
||||||
|
|
||||||
|
|
||||||
class AuthViewSet(viewsets.ViewSet):
|
class AuthViewSet(viewsets.ViewSet):
|
||||||
permission_classes = (AllowAny,)
|
permission_classes = (AuthPermission,)
|
||||||
|
|
||||||
def _public_register(self, request):
|
def _public_register(self, request):
|
||||||
if not settings.PUBLIC_REGISTER_ENABLED:
|
if not settings.PUBLIC_REGISTER_ENABLED:
|
||||||
|
@ -123,8 +124,10 @@ class AuthViewSet(viewsets.ViewSet):
|
||||||
data = make_auth_response_data(user)
|
data = make_auth_response_data(user)
|
||||||
return Response(data, status=status.HTTP_201_CREATED)
|
return Response(data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
@list_route(methods=["POST"], permission_classes=[AllowAny])
|
@list_route(methods=["POST"])
|
||||||
def register(self, request, **kwargs):
|
def register(self, request, **kwargs):
|
||||||
|
self.check_permissions(request, 'register', None)
|
||||||
|
|
||||||
type = request.DATA.get("type", None)
|
type = request.DATA.get("type", None)
|
||||||
if type == "public":
|
if type == "public":
|
||||||
return self._public_register(request)
|
return self._public_register(request)
|
||||||
|
@ -157,6 +160,8 @@ class AuthViewSet(viewsets.ViewSet):
|
||||||
|
|
||||||
# Login view: /api/v1/auth
|
# Login view: /api/v1/auth
|
||||||
def create(self, request, **kwargs):
|
def create(self, request, **kwargs):
|
||||||
|
self.check_permissions(request, 'create', None)
|
||||||
|
|
||||||
type = request.DATA.get("type", None)
|
type = request.DATA.get("type", None)
|
||||||
if type == "normal":
|
if type == "normal":
|
||||||
return self._login(request)
|
return self._login(request)
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be> # Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
||||||
|
# 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 taiga.base.api.permissions import ResourcePermission, AllowAny
|
||||||
|
|
||||||
|
|
||||||
|
class AuthPermission(ResourcePermission):
|
||||||
|
create_perms = AllowAny()
|
||||||
|
register_perms = AllowAny()
|
|
@ -1,121 +0,0 @@
|
||||||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
|
||||||
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
|
||||||
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
|
||||||
# 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 import transaction
|
|
||||||
|
|
||||||
from rest_framework import viewsets
|
|
||||||
from rest_framework import status
|
|
||||||
from rest_framework import mixins
|
|
||||||
from rest_framework.response import Response
|
|
||||||
|
|
||||||
from . import pagination
|
|
||||||
from . import serializers
|
|
||||||
|
|
||||||
|
|
||||||
# Transactional version of rest framework mixins.
|
|
||||||
|
|
||||||
class CreateModelMixin(mixins.CreateModelMixin):
|
|
||||||
@transaction.atomic
|
|
||||||
def create(self, *args, **kwargs):
|
|
||||||
return super().create(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class RetrieveModelMixin(mixins.RetrieveModelMixin):
|
|
||||||
@transaction.atomic
|
|
||||||
def retrieve(self, *args, **kwargs):
|
|
||||||
return super().retrieve(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateModelMixin(mixins.UpdateModelMixin):
|
|
||||||
@transaction.atomic
|
|
||||||
def update(self, *args, **kwargs):
|
|
||||||
return super().update(*args, **kwargs)
|
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def partial_update(self, request, *args, **kwargs):
|
|
||||||
return super().partial_update(request, *args, **kwargs)
|
|
||||||
|
|
||||||
class ListModelMixin(mixins.ListModelMixin):
|
|
||||||
@transaction.atomic
|
|
||||||
def list(self, *args, **kwargs):
|
|
||||||
return super().list(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class DestroyModelMixin(mixins.DestroyModelMixin):
|
|
||||||
@transaction.atomic
|
|
||||||
def destroy(self, request, *args, **kwargs):
|
|
||||||
return super().destroy(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
# Other mixins (what they are doing here?)
|
|
||||||
|
|
||||||
|
|
||||||
class PreconditionMixin(object):
|
|
||||||
def pre_conditions_on_save(self, obj):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def pre_conditions_on_delete(self, obj):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def pre_save(self, obj):
|
|
||||||
super().pre_save(obj)
|
|
||||||
self.pre_conditions_on_save(obj)
|
|
||||||
|
|
||||||
def pre_delete(self, obj):
|
|
||||||
super().pre_delete(obj)
|
|
||||||
self.pre_conditions_on_delete(obj)
|
|
||||||
|
|
||||||
|
|
||||||
class DetailAndListSerializersMixin(object):
|
|
||||||
"""
|
|
||||||
Use a diferent serializer class to the list action.
|
|
||||||
"""
|
|
||||||
list_serializer_class = None
|
|
||||||
|
|
||||||
def get_serializer_class(self):
|
|
||||||
if self.action == "list" and self.list_serializer_class:
|
|
||||||
return self.list_serializer_class
|
|
||||||
return super().get_serializer_class()
|
|
||||||
|
|
||||||
|
|
||||||
# Own subclasses of django rest framework viewsets
|
|
||||||
|
|
||||||
class ModelCrudViewSet(DetailAndListSerializersMixin,
|
|
||||||
PreconditionMixin,
|
|
||||||
pagination.HeadersPaginationMixin,
|
|
||||||
pagination.ConditionalPaginationMixin,
|
|
||||||
CreateModelMixin,
|
|
||||||
RetrieveModelMixin,
|
|
||||||
UpdateModelMixin,
|
|
||||||
DestroyModelMixin,
|
|
||||||
ListModelMixin,
|
|
||||||
viewsets.GenericViewSet):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ModelListViewSet(DetailAndListSerializersMixin,
|
|
||||||
PreconditionMixin,
|
|
||||||
pagination.HeadersPaginationMixin,
|
|
||||||
pagination.ConditionalPaginationMixin,
|
|
||||||
RetrieveModelMixin,
|
|
||||||
ListModelMixin,
|
|
||||||
viewsets.GenericViewSet):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class GenericViewSet(viewsets.GenericViewSet):
|
|
||||||
pass
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
# This code is partially taken from django-rest-framework:
|
||||||
|
# Copyright (c) 2011-2014, Tom Christie
|
||||||
|
|
||||||
|
from .viewsets import ModelListViewSet
|
||||||
|
from .viewsets import ModelCrudViewSet
|
||||||
|
from .viewsets import GenericViewSet
|
||||||
|
|
||||||
|
__all__ = ["ModelCrudViewSet",
|
||||||
|
"ModelListViewSet",
|
||||||
|
"GenericViewSet"]
|
|
@ -0,0 +1,493 @@
|
||||||
|
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
# This code is partially taken from django-rest-framework:
|
||||||
|
# Copyright (c) 2011-2014, Tom Christie
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
||||||
|
from django.core.paginator import Paginator, InvalidPage
|
||||||
|
from django.http import Http404
|
||||||
|
from django.shortcuts import get_object_or_404 as _get_object_or_404
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
from rest_framework import exceptions
|
||||||
|
from rest_framework.request import clone_request
|
||||||
|
from rest_framework.settings import api_settings
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
from . import mixins
|
||||||
|
|
||||||
|
|
||||||
|
def strict_positive_int(integer_string, cutoff=None):
|
||||||
|
"""
|
||||||
|
Cast a string to a strictly positive integer.
|
||||||
|
"""
|
||||||
|
ret = int(integer_string)
|
||||||
|
if ret <= 0:
|
||||||
|
raise ValueError()
|
||||||
|
if cutoff:
|
||||||
|
ret = min(ret, cutoff)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def get_object_or_404(queryset, *filter_args, **filter_kwargs):
|
||||||
|
"""
|
||||||
|
Same as Django's standard shortcut, but make sure to raise 404
|
||||||
|
if the filter_kwargs don't match the required types.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return _get_object_or_404(queryset, *filter_args, **filter_kwargs)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
|
||||||
|
class GenericAPIView(views.APIView):
|
||||||
|
"""
|
||||||
|
Base class for all other generic views.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# You'll need to either set these attributes,
|
||||||
|
# or override `get_queryset()`/`get_serializer_class()`.
|
||||||
|
queryset = None
|
||||||
|
serializer_class = None
|
||||||
|
|
||||||
|
# This shortcut may be used instead of setting either or both
|
||||||
|
# of the `queryset`/`serializer_class` attributes, although using
|
||||||
|
# the explicit style is generally preferred.
|
||||||
|
model = None
|
||||||
|
|
||||||
|
# If you want to use object lookups other than pk, set this attribute.
|
||||||
|
# For more complex lookup requirements override `get_object()`.
|
||||||
|
lookup_field = 'pk'
|
||||||
|
lookup_url_kwarg = None
|
||||||
|
|
||||||
|
# Pagination settings
|
||||||
|
paginate_by = api_settings.PAGINATE_BY
|
||||||
|
paginate_by_param = api_settings.PAGINATE_BY_PARAM
|
||||||
|
max_paginate_by = api_settings.MAX_PAGINATE_BY
|
||||||
|
pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS
|
||||||
|
page_kwarg = 'page'
|
||||||
|
|
||||||
|
# The filter backend classes to use for queryset filtering
|
||||||
|
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
|
||||||
|
|
||||||
|
# The following attributes may be subject to change,
|
||||||
|
# and should be considered private API.
|
||||||
|
model_serializer_class = api_settings.DEFAULT_MODEL_SERIALIZER_CLASS
|
||||||
|
paginator_class = Paginator
|
||||||
|
|
||||||
|
######################################
|
||||||
|
# These are pending deprecation...
|
||||||
|
|
||||||
|
pk_url_kwarg = 'pk'
|
||||||
|
slug_url_kwarg = 'slug'
|
||||||
|
slug_field = 'slug'
|
||||||
|
allow_empty = True
|
||||||
|
|
||||||
|
def get_serializer_context(self):
|
||||||
|
"""
|
||||||
|
Extra context provided to the serializer class.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'request': self.request,
|
||||||
|
'format': self.format_kwarg,
|
||||||
|
'view': self
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_serializer(self, instance=None, data=None,
|
||||||
|
files=None, many=False, partial=False):
|
||||||
|
"""
|
||||||
|
Return the serializer instance that should be used for validating and
|
||||||
|
deserializing input, and for serializing output.
|
||||||
|
"""
|
||||||
|
serializer_class = self.get_serializer_class()
|
||||||
|
context = self.get_serializer_context()
|
||||||
|
return serializer_class(instance, data=data, files=files,
|
||||||
|
many=many, partial=partial, context=context)
|
||||||
|
|
||||||
|
def get_pagination_serializer(self, page):
|
||||||
|
"""
|
||||||
|
Return a serializer instance to use with paginated data.
|
||||||
|
"""
|
||||||
|
class SerializerClass(self.pagination_serializer_class):
|
||||||
|
class Meta:
|
||||||
|
object_serializer_class = self.get_serializer_class()
|
||||||
|
|
||||||
|
pagination_serializer_class = SerializerClass
|
||||||
|
context = self.get_serializer_context()
|
||||||
|
return pagination_serializer_class(instance=page, context=context)
|
||||||
|
|
||||||
|
def paginate_queryset(self, queryset, page_size=None):
|
||||||
|
"""
|
||||||
|
Paginate a queryset if required, either returning a page object,
|
||||||
|
or `None` if pagination is not configured for this view.
|
||||||
|
"""
|
||||||
|
deprecated_style = False
|
||||||
|
if page_size is not None:
|
||||||
|
warnings.warn('The `page_size` parameter to `paginate_queryset()` '
|
||||||
|
'is due to be deprecated. '
|
||||||
|
'Note that the return style of this method is also '
|
||||||
|
'changed, and will simply return a page object '
|
||||||
|
'when called without a `page_size` argument.',
|
||||||
|
PendingDeprecationWarning, stacklevel=2)
|
||||||
|
deprecated_style = True
|
||||||
|
else:
|
||||||
|
# Determine the required page size.
|
||||||
|
# If pagination is not configured, simply return None.
|
||||||
|
page_size = self.get_paginate_by()
|
||||||
|
if not page_size:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not self.allow_empty:
|
||||||
|
warnings.warn(
|
||||||
|
'The `allow_empty` parameter is due to be deprecated. '
|
||||||
|
'To use `allow_empty=False` style behavior, You should override '
|
||||||
|
'`get_queryset()` and explicitly raise a 404 on empty querysets.',
|
||||||
|
PendingDeprecationWarning, stacklevel=2
|
||||||
|
)
|
||||||
|
|
||||||
|
paginator = self.paginator_class(queryset, page_size,
|
||||||
|
allow_empty_first_page=self.allow_empty)
|
||||||
|
page_kwarg = self.kwargs.get(self.page_kwarg)
|
||||||
|
page_query_param = self.request.QUERY_PARAMS.get(self.page_kwarg)
|
||||||
|
page = page_kwarg or page_query_param or 1
|
||||||
|
try:
|
||||||
|
page_number = paginator.validate_number(page)
|
||||||
|
except InvalidPage:
|
||||||
|
if page == 'last':
|
||||||
|
page_number = paginator.num_pages
|
||||||
|
else:
|
||||||
|
raise Http404(_("Page is not 'last', nor can it be converted to an int."))
|
||||||
|
try:
|
||||||
|
page = paginator.page(page_number)
|
||||||
|
except InvalidPage as e:
|
||||||
|
raise Http404(_('Invalid page (%(page_number)s): %(message)s') % {
|
||||||
|
'page_number': page_number,
|
||||||
|
'message': str(e)
|
||||||
|
})
|
||||||
|
|
||||||
|
if deprecated_style:
|
||||||
|
return (paginator, page, page.object_list, page.has_other_pages())
|
||||||
|
return page
|
||||||
|
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
"""
|
||||||
|
Given a queryset, filter it with whichever filter backend is in use.
|
||||||
|
|
||||||
|
You are unlikely to want to override this method, although you may need
|
||||||
|
to call it either from a list view, or from a custom `get_object`
|
||||||
|
method if you want to apply the configured filtering backend to the
|
||||||
|
default queryset.
|
||||||
|
"""
|
||||||
|
for backend in self.get_filter_backends():
|
||||||
|
queryset = backend().filter_queryset(self.request, queryset, self)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def get_filter_backends(self):
|
||||||
|
"""
|
||||||
|
Returns the list of filter backends that this view requires.
|
||||||
|
"""
|
||||||
|
filter_backends = self.filter_backends or []
|
||||||
|
if not filter_backends and hasattr(self, 'filter_backend'):
|
||||||
|
raise RuntimeException('The `filter_backend` attribute and `FILTER_BACKEND` setting '
|
||||||
|
'are due to be deprecated in favor of a `filter_backends` '
|
||||||
|
'attribute and `DEFAULT_FILTER_BACKENDS` setting, that take '
|
||||||
|
'a *list* of filter backend classes.')
|
||||||
|
return filter_backends
|
||||||
|
|
||||||
|
|
||||||
|
########################
|
||||||
|
### The following methods provide default implementations
|
||||||
|
### that you may want to override for more complex cases.
|
||||||
|
|
||||||
|
def get_paginate_by(self, queryset=None):
|
||||||
|
"""
|
||||||
|
Return the size of pages to use with pagination.
|
||||||
|
|
||||||
|
If `PAGINATE_BY_PARAM` is set it will attempt to get the page size
|
||||||
|
from a named query parameter in the url, eg. ?page_size=100
|
||||||
|
|
||||||
|
Otherwise defaults to using `self.paginate_by`.
|
||||||
|
"""
|
||||||
|
if queryset is not None:
|
||||||
|
raise RuntimeException('The `queryset` parameter to `get_paginate_by()` '
|
||||||
|
'is due to be deprecated.')
|
||||||
|
if self.paginate_by_param:
|
||||||
|
try:
|
||||||
|
return strict_positive_int(
|
||||||
|
self.request.QUERY_PARAMS[self.paginate_by_param],
|
||||||
|
cutoff=self.max_paginate_by
|
||||||
|
)
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return self.paginate_by
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
if self.action == "list" and hasattr(self, "list_serializer_class"):
|
||||||
|
return self.list_serializer_class
|
||||||
|
|
||||||
|
serializer_class = self.serializer_class
|
||||||
|
if serializer_class is not None:
|
||||||
|
return serializer_class
|
||||||
|
|
||||||
|
assert self.model is not None, \
|
||||||
|
"'%s' should either include a 'serializer_class' attribute, " \
|
||||||
|
"or use the 'model' attribute as a shortcut for " \
|
||||||
|
"automatically generating a serializer class." \
|
||||||
|
% self.__class__.__name__
|
||||||
|
|
||||||
|
class DefaultSerializer(self.model_serializer_class):
|
||||||
|
class Meta:
|
||||||
|
model = self.model
|
||||||
|
return DefaultSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""
|
||||||
|
Get the list of items for this view.
|
||||||
|
This must be an iterable, and may be a queryset.
|
||||||
|
Defaults to using `self.queryset`.
|
||||||
|
|
||||||
|
You may want to override this if you need to provide different
|
||||||
|
querysets depending on the incoming request.
|
||||||
|
|
||||||
|
(Eg. return a list of items that is specific to the user)
|
||||||
|
"""
|
||||||
|
if self.queryset is not None:
|
||||||
|
return self.queryset._clone()
|
||||||
|
|
||||||
|
if self.model is not None:
|
||||||
|
return self.model._default_manager.all()
|
||||||
|
|
||||||
|
raise ImproperlyConfigured("'%s' must define 'queryset' or 'model'"
|
||||||
|
% self.__class__.__name__)
|
||||||
|
|
||||||
|
def get_object(self, queryset=None):
|
||||||
|
"""
|
||||||
|
Returns the object the view is displaying.
|
||||||
|
|
||||||
|
You may want to override this if you need to provide non-standard
|
||||||
|
queryset lookups. Eg if objects are referenced using multiple
|
||||||
|
keyword arguments in the url conf.
|
||||||
|
"""
|
||||||
|
# Determine the base queryset to use.
|
||||||
|
if queryset is None:
|
||||||
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
|
else:
|
||||||
|
# NOTE: explicit exception for avoid and fix
|
||||||
|
# usage of deprecated way of get_object
|
||||||
|
raise RuntimeException("DEPRECATED")
|
||||||
|
|
||||||
|
# Perform the lookup filtering.
|
||||||
|
# Note that `pk` and `slug` are deprecated styles of lookup filtering.
|
||||||
|
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
||||||
|
lookup = self.kwargs.get(lookup_url_kwarg, None)
|
||||||
|
pk = self.kwargs.get(self.pk_url_kwarg, None)
|
||||||
|
slug = self.kwargs.get(self.slug_url_kwarg, None)
|
||||||
|
|
||||||
|
if lookup is not None:
|
||||||
|
filter_kwargs = {self.lookup_field: lookup}
|
||||||
|
elif pk is not None and self.lookup_field == 'pk':
|
||||||
|
raise RuntimeException('The `pk_url_kwarg` attribute is due to be deprecated. '
|
||||||
|
'Use the `lookup_field` attribute instead')
|
||||||
|
elif slug is not None and self.lookup_field == 'pk':
|
||||||
|
raise RuntimeException('The `slug_url_kwarg` attribute is due to be deprecated. '
|
||||||
|
'Use the `lookup_field` attribute instead')
|
||||||
|
else:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
'Expected view %s to be called with a URL keyword argument '
|
||||||
|
'named "%s". Fix your URL conf, or set the `.lookup_field` '
|
||||||
|
'attribute on the view correctly.' %
|
||||||
|
(self.__class__.__name__, self.lookup_field)
|
||||||
|
)
|
||||||
|
|
||||||
|
obj = get_object_or_404(queryset, **filter_kwargs)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def get_object_or_none(self):
|
||||||
|
try:
|
||||||
|
return self.get_object()
|
||||||
|
except Http404:
|
||||||
|
return None
|
||||||
|
|
||||||
|
########################
|
||||||
|
### The following are placeholder methods,
|
||||||
|
### and are intended to be overridden.
|
||||||
|
###
|
||||||
|
### The are not called by GenericAPIView directly,
|
||||||
|
### but are used by the mixin methods.
|
||||||
|
|
||||||
|
def pre_conditions_on_save(self, obj):
|
||||||
|
"""
|
||||||
|
Placeholder method called by mixins before save for check
|
||||||
|
some conditions before save.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def pre_conditions_on_delete(self, obj):
|
||||||
|
"""
|
||||||
|
Placeholder method called by mixins before delete for check
|
||||||
|
some conditions before delete.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def pre_save(self, obj):
|
||||||
|
"""
|
||||||
|
Placeholder method for calling before saving an object.
|
||||||
|
|
||||||
|
May be used to set attributes on the object that are implicit
|
||||||
|
in either the request, or the url.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def post_save(self, obj, created=False):
|
||||||
|
"""
|
||||||
|
Placeholder method for calling after saving an object.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def pre_delete(self, obj):
|
||||||
|
"""
|
||||||
|
Placeholder method for calling before deleting an object.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def post_delete(self, obj):
|
||||||
|
"""
|
||||||
|
Placeholder method for calling after deleting an object.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
##########################################################
|
||||||
|
### Concrete view classes that provide method handlers ###
|
||||||
|
### by composing the mixin classes with the base view. ###
|
||||||
|
### NOTE: not used by taiga. ###
|
||||||
|
##########################################################
|
||||||
|
|
||||||
|
class CreateAPIView(mixins.CreateModelMixin,
|
||||||
|
GenericAPIView):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Concrete view for creating a model instance.
|
||||||
|
"""
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
return self.create(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ListAPIView(mixins.ListModelMixin,
|
||||||
|
GenericAPIView):
|
||||||
|
"""
|
||||||
|
Concrete view for listing a queryset.
|
||||||
|
"""
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return self.list(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveAPIView(mixins.RetrieveModelMixin,
|
||||||
|
GenericAPIView):
|
||||||
|
"""
|
||||||
|
Concrete view for retrieving a model instance.
|
||||||
|
"""
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return self.retrieve(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class DestroyAPIView(mixins.DestroyModelMixin,
|
||||||
|
GenericAPIView):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Concrete view for deleting a model instance.
|
||||||
|
"""
|
||||||
|
def delete(self, request, *args, **kwargs):
|
||||||
|
return self.destroy(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateAPIView(mixins.UpdateModelMixin,
|
||||||
|
GenericAPIView):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Concrete view for updating a model instance.
|
||||||
|
"""
|
||||||
|
def put(self, request, *args, **kwargs):
|
||||||
|
return self.update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def patch(self, request, *args, **kwargs):
|
||||||
|
return self.partial_update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ListCreateAPIView(mixins.ListModelMixin,
|
||||||
|
mixins.CreateModelMixin,
|
||||||
|
GenericAPIView):
|
||||||
|
"""
|
||||||
|
Concrete view for listing a queryset or creating a model instance.
|
||||||
|
"""
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return self.list(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
return self.create(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveUpdateAPIView(mixins.RetrieveModelMixin,
|
||||||
|
mixins.UpdateModelMixin,
|
||||||
|
GenericAPIView):
|
||||||
|
"""
|
||||||
|
Concrete view for retrieving, updating a model instance.
|
||||||
|
"""
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return self.retrieve(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def put(self, request, *args, **kwargs):
|
||||||
|
return self.update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def patch(self, request, *args, **kwargs):
|
||||||
|
return self.partial_update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveDestroyAPIView(mixins.RetrieveModelMixin,
|
||||||
|
mixins.DestroyModelMixin,
|
||||||
|
GenericAPIView):
|
||||||
|
"""
|
||||||
|
Concrete view for retrieving or deleting a model instance.
|
||||||
|
"""
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return self.retrieve(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def delete(self, request, *args, **kwargs):
|
||||||
|
return self.destroy(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
|
||||||
|
mixins.UpdateModelMixin,
|
||||||
|
mixins.DestroyModelMixin,
|
||||||
|
GenericAPIView):
|
||||||
|
"""
|
||||||
|
Concrete view for retrieving, updating or deleting a model instance.
|
||||||
|
"""
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return self.retrieve(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def put(self, request, *args, **kwargs):
|
||||||
|
return self.update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def patch(self, request, *args, **kwargs):
|
||||||
|
return self.partial_update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def delete(self, request, *args, **kwargs):
|
||||||
|
return self.destroy(request, *args, **kwargs)
|
|
@ -0,0 +1,217 @@
|
||||||
|
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
# This code is partially taken from django-rest-framework:
|
||||||
|
# Copyright (c) 2011-2014, Tom Christie
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.http import Http404
|
||||||
|
from django.db import transaction as tx
|
||||||
|
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.request import clone_request
|
||||||
|
from rest_framework.settings import api_settings
|
||||||
|
|
||||||
|
|
||||||
|
def _get_validation_exclusions(obj, pk=None, slug_field=None, lookup_field=None):
|
||||||
|
"""
|
||||||
|
Given a model instance, and an optional pk and slug field,
|
||||||
|
return the full list of all other field names on that model.
|
||||||
|
|
||||||
|
For use when performing full_clean on a model instance,
|
||||||
|
so we only clean the required fields.
|
||||||
|
"""
|
||||||
|
include = []
|
||||||
|
|
||||||
|
if pk:
|
||||||
|
# Pending deprecation
|
||||||
|
pk_field = obj._meta.pk
|
||||||
|
while pk_field.rel:
|
||||||
|
pk_field = pk_field.rel.to._meta.pk
|
||||||
|
include.append(pk_field.name)
|
||||||
|
|
||||||
|
if slug_field:
|
||||||
|
# Pending deprecation
|
||||||
|
include.append(slug_field)
|
||||||
|
|
||||||
|
if lookup_field and lookup_field != 'pk':
|
||||||
|
include.append(lookup_field)
|
||||||
|
|
||||||
|
return [field.name for field in obj._meta.fields if field.name not in include]
|
||||||
|
|
||||||
|
|
||||||
|
class CreateModelMixin(object):
|
||||||
|
"""
|
||||||
|
Create a model instance.
|
||||||
|
"""
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
|
||||||
|
|
||||||
|
if serializer.is_valid():
|
||||||
|
self.check_permissions(request, 'create', serializer.object)
|
||||||
|
|
||||||
|
self.pre_save(serializer.object)
|
||||||
|
self.pre_conditions_on_save(serializer.object)
|
||||||
|
self.object = serializer.save(force_insert=True)
|
||||||
|
self.post_save(self.object, created=True)
|
||||||
|
headers = self.get_success_headers(serializer.data)
|
||||||
|
return Response(serializer.data, status=status.HTTP_201_CREATED,
|
||||||
|
headers=headers)
|
||||||
|
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
def get_success_headers(self, data):
|
||||||
|
try:
|
||||||
|
return {'Location': data[api_settings.URL_FIELD_NAME]}
|
||||||
|
except (TypeError, KeyError):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
class ListModelMixin(object):
|
||||||
|
"""
|
||||||
|
List a queryset.
|
||||||
|
"""
|
||||||
|
empty_error = "Empty list and '%(class_name)s.allow_empty' is False."
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
self.object_list = self.filter_queryset(self.get_queryset())
|
||||||
|
|
||||||
|
# Default is to allow empty querysets. This can be altered by setting
|
||||||
|
# `.allow_empty = False`, to raise 404 errors on empty querysets.
|
||||||
|
if not self.allow_empty and not self.object_list:
|
||||||
|
warnings.warn(
|
||||||
|
'The `allow_empty` parameter is due to be deprecated. '
|
||||||
|
'To use `allow_empty=False` style behavior, You should override '
|
||||||
|
'`get_queryset()` and explicitly raise a 404 on empty querysets.',
|
||||||
|
PendingDeprecationWarning
|
||||||
|
)
|
||||||
|
class_name = self.__class__.__name__
|
||||||
|
error_msg = self.empty_error % {'class_name': class_name}
|
||||||
|
raise Http404(error_msg)
|
||||||
|
|
||||||
|
# Switch between paginated or standard style responses
|
||||||
|
page = self.paginate_queryset(self.object_list)
|
||||||
|
if page is not None:
|
||||||
|
serializer = self.get_pagination_serializer(page)
|
||||||
|
else:
|
||||||
|
serializer = self.get_serializer(self.object_list, many=True)
|
||||||
|
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveModelMixin(object):
|
||||||
|
"""
|
||||||
|
Retrieve a model instance.
|
||||||
|
"""
|
||||||
|
def retrieve(self, request, *args, **kwargs):
|
||||||
|
self.object = self.get_object_or_none()
|
||||||
|
|
||||||
|
self.check_permissions(request, 'retrieve', self.object)
|
||||||
|
|
||||||
|
if self.object is None:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
serializer = self.get_serializer(self.object)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateModelMixin(object):
|
||||||
|
"""
|
||||||
|
Update a model instance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@tx.atomic
|
||||||
|
def update(self, request, *args, **kwargs):
|
||||||
|
partial = kwargs.pop('partial', False)
|
||||||
|
self.object = self.get_object_or_none()
|
||||||
|
|
||||||
|
self.check_permissions(request, 'update', self.object)
|
||||||
|
|
||||||
|
serializer = self.get_serializer(self.object, data=request.DATA,
|
||||||
|
files=request.FILES, partial=partial)
|
||||||
|
|
||||||
|
if not serializer.is_valid():
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
# Hooks
|
||||||
|
try:
|
||||||
|
self.pre_save(serializer.object)
|
||||||
|
self.pre_conditions_on_save(serializer.object)
|
||||||
|
except ValidationError as err:
|
||||||
|
# full_clean on model instance may be called in pre_save,
|
||||||
|
# so we have to handle eventual errors.
|
||||||
|
return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if self.object is None:
|
||||||
|
self.object = serializer.save(force_insert=True)
|
||||||
|
self.post_save(self.object, created=True)
|
||||||
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
self.object = serializer.save(force_update=True)
|
||||||
|
self.post_save(self.object, created=False)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def partial_update(self, request, *args, **kwargs):
|
||||||
|
kwargs['partial'] = True
|
||||||
|
return self.update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def pre_save(self, obj):
|
||||||
|
"""
|
||||||
|
Set any attributes on the object that are implicit in the request.
|
||||||
|
"""
|
||||||
|
# pk and/or slug attributes are implicit in the URL.
|
||||||
|
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
||||||
|
lookup = self.kwargs.get(lookup_url_kwarg, None)
|
||||||
|
pk = self.kwargs.get(self.pk_url_kwarg, None)
|
||||||
|
slug = self.kwargs.get(self.slug_url_kwarg, None)
|
||||||
|
slug_field = slug and self.slug_field or None
|
||||||
|
|
||||||
|
if lookup:
|
||||||
|
setattr(obj, self.lookup_field, lookup)
|
||||||
|
|
||||||
|
if pk:
|
||||||
|
setattr(obj, 'pk', pk)
|
||||||
|
|
||||||
|
if slug:
|
||||||
|
setattr(obj, slug_field, slug)
|
||||||
|
|
||||||
|
# Ensure we clean the attributes so that we don't eg return integer
|
||||||
|
# pk using a string representation, as provided by the url conf kwarg.
|
||||||
|
if hasattr(obj, 'full_clean'):
|
||||||
|
exclude = _get_validation_exclusions(obj, pk, slug_field, self.lookup_field)
|
||||||
|
obj.full_clean(exclude)
|
||||||
|
|
||||||
|
|
||||||
|
class DestroyModelMixin(object):
|
||||||
|
"""
|
||||||
|
Destroy a model instance.
|
||||||
|
"""
|
||||||
|
@tx.atomic
|
||||||
|
def destroy(self, request, *args, **kwargs):
|
||||||
|
obj = self.get_object_or_none()
|
||||||
|
self.check_permissions(request, 'destroy', obj)
|
||||||
|
|
||||||
|
if obj is None:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
self.pre_delete(obj)
|
||||||
|
self.pre_conditions_on_delete(obj)
|
||||||
|
obj.delete()
|
||||||
|
self.post_delete(obj)
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
|
@ -0,0 +1,218 @@
|
||||||
|
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
import abc
|
||||||
|
|
||||||
|
from taiga.base.utils import sequence as sq
|
||||||
|
from taiga.permissions.service import user_has_perm, is_project_owner
|
||||||
|
from django.db.models.loading import get_model
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Base permissiones definition
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
class ResourcePermission(object):
|
||||||
|
"""
|
||||||
|
Base class for define resource permissions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
enought_perms = None
|
||||||
|
global_perms = None
|
||||||
|
retrieve_perms = None
|
||||||
|
create_perms = None
|
||||||
|
update_perms = None
|
||||||
|
destroy_perms = None
|
||||||
|
list_perms = None
|
||||||
|
|
||||||
|
def __init__(self, request, view):
|
||||||
|
self.request = request
|
||||||
|
self.view = view
|
||||||
|
|
||||||
|
def check_permissions(self, action:str, obj:object=None):
|
||||||
|
permset = getattr(self, "{}_perms".format(action))
|
||||||
|
|
||||||
|
if isinstance(permset, (list, tuple)):
|
||||||
|
permset = reduce(lambda acc, v: acc & v, permset)
|
||||||
|
elif permset is None:
|
||||||
|
# Use empty operator that always return true with
|
||||||
|
# empty components.
|
||||||
|
permset = And()
|
||||||
|
elif isinstance(permset, PermissionComponent):
|
||||||
|
# Do nothing
|
||||||
|
pass
|
||||||
|
elif inspect.isclass(permset) and issubclass(permset, PermissionComponent):
|
||||||
|
permset = permset()
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Invalid permission definition.")
|
||||||
|
|
||||||
|
if self.global_perms:
|
||||||
|
permset = (self.global_perms & permset)
|
||||||
|
|
||||||
|
if self.enought_perms:
|
||||||
|
permset = (self.enought_perms | permset)
|
||||||
|
|
||||||
|
return permset.check_permissions(request=self.request,
|
||||||
|
view=self.view,
|
||||||
|
obj=obj)
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionComponent(object, metaclass=abc.ABCMeta):
|
||||||
|
@abc.abstractmethod
|
||||||
|
def check_permissions(self, request, view, obj=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __invert__(self):
|
||||||
|
return Not(self)
|
||||||
|
|
||||||
|
def __and__(self, component):
|
||||||
|
return And(self, component)
|
||||||
|
|
||||||
|
def __or__(self, component):
|
||||||
|
return Or(self, component)
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionOperator(PermissionComponent):
|
||||||
|
"""
|
||||||
|
Base class for all logical operators for compose
|
||||||
|
components.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *components):
|
||||||
|
self.components = tuple(components)
|
||||||
|
|
||||||
|
|
||||||
|
class Not(PermissionOperator):
|
||||||
|
"""
|
||||||
|
Negation operator as permission composable component.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Overwrites the default constructor for fix
|
||||||
|
# to one parameter instead of variable list of them.
|
||||||
|
def __init__(self, component):
|
||||||
|
super().__init__(component)
|
||||||
|
|
||||||
|
def check_permissions(self, *args, **kwargs):
|
||||||
|
component = sq.first(self.components)
|
||||||
|
return (not component.check_permissions(*args, **kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
class Or(PermissionOperator):
|
||||||
|
"""
|
||||||
|
Or logical operator as permission component.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def check_permissions(self, *args, **kwargs):
|
||||||
|
valid = False
|
||||||
|
|
||||||
|
for component in self.components:
|
||||||
|
if component.check_permissions(*args, **kwargs):
|
||||||
|
valid = True
|
||||||
|
break
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
|
|
||||||
|
class And(PermissionOperator):
|
||||||
|
"""
|
||||||
|
And logical operator as permission component.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def check_permissions(self, *args, **kwargs):
|
||||||
|
valid = True
|
||||||
|
|
||||||
|
for component in self.components:
|
||||||
|
if not component.check_permissions(*args, **kwargs):
|
||||||
|
valid = False
|
||||||
|
break
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Generic components.
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
class AllowAny(PermissionComponent):
|
||||||
|
def check_permissions(self, request, view, obj=None):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class DenyAll(PermissionComponent):
|
||||||
|
def check_permissions(self, request, view, obj=None):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class IsAuthenticated(PermissionComponent):
|
||||||
|
def check_permissions(self, request, view, obj=None):
|
||||||
|
return request.user and request.user.is_authenticated()
|
||||||
|
|
||||||
|
|
||||||
|
class IsSuperUser(PermissionComponent):
|
||||||
|
def check_permissions(self, request, view, obj=None):
|
||||||
|
return request.user and request.user.is_authenticated() and request.user.is_superuser
|
||||||
|
|
||||||
|
|
||||||
|
class HasProjectPerm(PermissionComponent):
|
||||||
|
def __init__(self, perm, *components):
|
||||||
|
self.project_perm = perm
|
||||||
|
super().__init__(*components)
|
||||||
|
|
||||||
|
def check_permissions(self, request, view, obj=None):
|
||||||
|
return user_has_perm(request.user, self.project_perm, obj)
|
||||||
|
|
||||||
|
|
||||||
|
class HasProjectParamAndPerm(PermissionComponent):
|
||||||
|
def __init__(self, perm, *components):
|
||||||
|
self.project_perm = perm
|
||||||
|
super().__init__(*components)
|
||||||
|
|
||||||
|
def check_permissions(self, request, view, obj=None):
|
||||||
|
Project = get_model('projects', 'Project')
|
||||||
|
project_id = request.QUERY_PARAMS.get("project", None)
|
||||||
|
try:
|
||||||
|
project = Project.objects.get(pk=project_id)
|
||||||
|
except Project.DoesNotExist:
|
||||||
|
return False
|
||||||
|
return user_has_perm(request.user, self.project_perm, project)
|
||||||
|
|
||||||
|
|
||||||
|
class HasMandatoryParam(PermissionComponent):
|
||||||
|
def __init__(self, param, *components):
|
||||||
|
self.mandatory_param = param
|
||||||
|
super().__init__(*components)
|
||||||
|
|
||||||
|
def check_permissions(self, request, view, obj=None):
|
||||||
|
param = request.GET.get(self.mandatory_param, None)
|
||||||
|
if param:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class IsProjectOwner(PermissionComponent):
|
||||||
|
def check_permissions(self, request, view, obj=None):
|
||||||
|
return is_project_owner(request.user, obj)
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Generic permissions.
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
class AllowAnyPermission(ResourcePermission):
|
||||||
|
enought_perms = AllowAny()
|
||||||
|
|
||||||
|
class IsAuthenticatedPermission(ResourcePermission):
|
||||||
|
enought_perms = IsAuthenticated()
|
|
@ -0,0 +1,432 @@
|
||||||
|
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
# This code is partially taken from django-rest-framework:
|
||||||
|
# Copyright (c) 2011-2014, Tom Christie
|
||||||
|
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.http import Http404
|
||||||
|
from django.utils.datastructures import SortedDict
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
|
from rest_framework import status, exceptions
|
||||||
|
from rest_framework.compat import smart_text, HttpResponseBase, View
|
||||||
|
from rest_framework.request import Request
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.settings import api_settings
|
||||||
|
from rest_framework.utils import formatting
|
||||||
|
|
||||||
|
from taiga.base.utils.iterators import as_tuple
|
||||||
|
|
||||||
|
|
||||||
|
def get_view_name(view_cls, suffix=None):
|
||||||
|
"""
|
||||||
|
Given a view class, return a textual name to represent the view.
|
||||||
|
This name is used in the browsable API, and in OPTIONS responses.
|
||||||
|
|
||||||
|
This function is the default for the `VIEW_NAME_FUNCTION` setting.
|
||||||
|
"""
|
||||||
|
name = view_cls.__name__
|
||||||
|
name = formatting.remove_trailing_string(name, 'View')
|
||||||
|
name = formatting.remove_trailing_string(name, 'ViewSet')
|
||||||
|
name = formatting.camelcase_to_spaces(name)
|
||||||
|
if suffix:
|
||||||
|
name += ' ' + suffix
|
||||||
|
|
||||||
|
return name
|
||||||
|
|
||||||
|
def get_view_description(view_cls, html=False):
|
||||||
|
"""
|
||||||
|
Given a view class, return a textual description to represent the view.
|
||||||
|
This name is used in the browsable API, and in OPTIONS responses.
|
||||||
|
|
||||||
|
This function is the default for the `VIEW_DESCRIPTION_FUNCTION` setting.
|
||||||
|
"""
|
||||||
|
description = view_cls.__doc__ or ''
|
||||||
|
description = formatting.dedent(smart_text(description))
|
||||||
|
if html:
|
||||||
|
return formatting.markup_description(description)
|
||||||
|
return description
|
||||||
|
|
||||||
|
|
||||||
|
def exception_handler(exc):
|
||||||
|
"""
|
||||||
|
Returns the response that should be used for any given exception.
|
||||||
|
|
||||||
|
By default we handle the REST framework `APIException`, and also
|
||||||
|
Django's builtin `Http404` and `PermissionDenied` exceptions.
|
||||||
|
|
||||||
|
Any unhandled exceptions may return `None`, which will cause a 500 error
|
||||||
|
to be raised.
|
||||||
|
"""
|
||||||
|
if isinstance(exc, exceptions.APIException):
|
||||||
|
headers = {}
|
||||||
|
if getattr(exc, 'auth_header', None):
|
||||||
|
headers['WWW-Authenticate'] = exc.auth_header
|
||||||
|
if getattr(exc, 'wait', None):
|
||||||
|
headers['X-Throttle-Wait-Seconds'] = '%d' % exc.wait
|
||||||
|
|
||||||
|
return Response({'detail': exc.detail},
|
||||||
|
status=exc.status_code,
|
||||||
|
headers=headers)
|
||||||
|
|
||||||
|
elif isinstance(exc, Http404):
|
||||||
|
return Response({'detail': 'Not found'},
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
elif isinstance(exc, PermissionDenied):
|
||||||
|
return Response({'detail': 'Permission denied'},
|
||||||
|
status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
# Note: Unhandled exceptions will raise a 500 error.
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class APIView(View):
|
||||||
|
# The following policies may be set at either globally, or per-view.
|
||||||
|
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
|
||||||
|
parser_classes = api_settings.DEFAULT_PARSER_CLASSES
|
||||||
|
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
|
||||||
|
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
|
||||||
|
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
|
||||||
|
content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
|
||||||
|
|
||||||
|
# Allow dependancy injection of other settings to make testing easier.
|
||||||
|
settings = api_settings
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def as_view(cls, **initkwargs):
|
||||||
|
"""
|
||||||
|
Store the original class on the view function.
|
||||||
|
|
||||||
|
This allows us to discover information about the view when we do URL
|
||||||
|
reverse lookups. Used for breadcrumb generation.
|
||||||
|
"""
|
||||||
|
view = super(APIView, cls).as_view(**initkwargs)
|
||||||
|
view.cls = cls
|
||||||
|
return view
|
||||||
|
|
||||||
|
@property
|
||||||
|
def allowed_methods(self):
|
||||||
|
"""
|
||||||
|
Wrap Django's private `_allowed_methods` interface in a public property.
|
||||||
|
"""
|
||||||
|
return self._allowed_methods()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_response_headers(self):
|
||||||
|
headers = {
|
||||||
|
'Allow': ', '.join(self.allowed_methods),
|
||||||
|
}
|
||||||
|
if len(self.renderer_classes) > 1:
|
||||||
|
headers['Vary'] = 'Accept'
|
||||||
|
return headers
|
||||||
|
|
||||||
|
|
||||||
|
def http_method_not_allowed(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
If `request.method` does not correspond to a handler method,
|
||||||
|
determine what kind of exception to raise.
|
||||||
|
"""
|
||||||
|
raise exceptions.MethodNotAllowed(request.method)
|
||||||
|
|
||||||
|
def permission_denied(self, request):
|
||||||
|
"""
|
||||||
|
If request is not permitted, determine what kind of exception to raise.
|
||||||
|
"""
|
||||||
|
if not request.successful_authenticator:
|
||||||
|
raise exceptions.NotAuthenticated()
|
||||||
|
raise exceptions.PermissionDenied()
|
||||||
|
|
||||||
|
def throttled(self, request, wait):
|
||||||
|
"""
|
||||||
|
If request is throttled, determine what kind of exception to raise.
|
||||||
|
"""
|
||||||
|
raise exceptions.Throttled(wait)
|
||||||
|
|
||||||
|
def get_authenticate_header(self, request):
|
||||||
|
"""
|
||||||
|
If a request is unauthenticated, determine the WWW-Authenticate
|
||||||
|
header to use for 401 responses, if any.
|
||||||
|
"""
|
||||||
|
authenticators = self.get_authenticators()
|
||||||
|
if authenticators:
|
||||||
|
return authenticators[0].authenticate_header(request)
|
||||||
|
|
||||||
|
def get_parser_context(self, http_request):
|
||||||
|
"""
|
||||||
|
Returns a dict that is passed through to Parser.parse(),
|
||||||
|
as the `parser_context` keyword argument.
|
||||||
|
"""
|
||||||
|
# Note: Additionally `request` and `encoding` will also be added
|
||||||
|
# to the context by the Request object.
|
||||||
|
return {
|
||||||
|
'view': self,
|
||||||
|
'args': getattr(self, 'args', ()),
|
||||||
|
'kwargs': getattr(self, 'kwargs', {})
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_renderer_context(self):
|
||||||
|
"""
|
||||||
|
Returns a dict that is passed through to Renderer.render(),
|
||||||
|
as the `renderer_context` keyword argument.
|
||||||
|
"""
|
||||||
|
# Note: Additionally 'response' will also be added to the context,
|
||||||
|
# by the Response object.
|
||||||
|
return {
|
||||||
|
'view': self,
|
||||||
|
'args': getattr(self, 'args', ()),
|
||||||
|
'kwargs': getattr(self, 'kwargs', {}),
|
||||||
|
'request': getattr(self, 'request', None)
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_view_name(self):
|
||||||
|
"""
|
||||||
|
Return the view name, as used in OPTIONS responses and in the
|
||||||
|
browsable API.
|
||||||
|
"""
|
||||||
|
func = self.settings.VIEW_NAME_FUNCTION
|
||||||
|
return func(self.__class__, getattr(self, 'suffix', None))
|
||||||
|
|
||||||
|
def get_view_description(self, html=False):
|
||||||
|
"""
|
||||||
|
Return some descriptive text for the view, as used in OPTIONS responses
|
||||||
|
and in the browsable API.
|
||||||
|
"""
|
||||||
|
func = self.settings.VIEW_DESCRIPTION_FUNCTION
|
||||||
|
return func(self.__class__, html)
|
||||||
|
|
||||||
|
# API policy instantiation methods
|
||||||
|
|
||||||
|
def get_format_suffix(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Determine if the request includes a '.json' style format suffix
|
||||||
|
"""
|
||||||
|
if self.settings.FORMAT_SUFFIX_KWARG:
|
||||||
|
return kwargs.get(self.settings.FORMAT_SUFFIX_KWARG)
|
||||||
|
|
||||||
|
def get_renderers(self):
|
||||||
|
"""
|
||||||
|
Instantiates and returns the list of renderers that this view can use.
|
||||||
|
"""
|
||||||
|
return [renderer() for renderer in self.renderer_classes]
|
||||||
|
|
||||||
|
def get_parsers(self):
|
||||||
|
"""
|
||||||
|
Instantiates and returns the list of parsers that this view can use.
|
||||||
|
"""
|
||||||
|
return [parser() for parser in self.parser_classes]
|
||||||
|
|
||||||
|
def get_authenticators(self):
|
||||||
|
"""
|
||||||
|
Instantiates and returns the list of authenticators that this view can use.
|
||||||
|
"""
|
||||||
|
return [auth() for auth in self.authentication_classes]
|
||||||
|
|
||||||
|
@as_tuple
|
||||||
|
def get_permissions(self):
|
||||||
|
"""
|
||||||
|
Instantiates and returns the list of permissions that this view requires.
|
||||||
|
"""
|
||||||
|
for permcls in self.permission_classes:
|
||||||
|
instance = permcls(request=self.request,
|
||||||
|
view=self)
|
||||||
|
yield instance
|
||||||
|
|
||||||
|
def get_throttles(self):
|
||||||
|
"""
|
||||||
|
Instantiates and returns the list of throttles that this view uses.
|
||||||
|
"""
|
||||||
|
return [throttle() for throttle in self.throttle_classes]
|
||||||
|
|
||||||
|
def get_content_negotiator(self):
|
||||||
|
"""
|
||||||
|
Instantiate and return the content negotiation class to use.
|
||||||
|
"""
|
||||||
|
if not getattr(self, '_negotiator', None):
|
||||||
|
self._negotiator = self.content_negotiation_class()
|
||||||
|
return self._negotiator
|
||||||
|
|
||||||
|
# API policy implementation methods
|
||||||
|
|
||||||
|
def perform_content_negotiation(self, request, force=False):
|
||||||
|
"""
|
||||||
|
Determine which renderer and media type to use render the response.
|
||||||
|
"""
|
||||||
|
renderers = self.get_renderers()
|
||||||
|
conneg = self.get_content_negotiator()
|
||||||
|
|
||||||
|
try:
|
||||||
|
return conneg.select_renderer(request, renderers, self.format_kwarg)
|
||||||
|
except Exception:
|
||||||
|
if force:
|
||||||
|
return (renderers[0], renderers[0].media_type)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def perform_authentication(self, request):
|
||||||
|
"""
|
||||||
|
Perform authentication on the incoming request.
|
||||||
|
|
||||||
|
Note that if you override this and simply 'pass', then authentication
|
||||||
|
will instead be performed lazily, the first time either
|
||||||
|
`request.user` or `request.auth` is accessed.
|
||||||
|
"""
|
||||||
|
request.user
|
||||||
|
|
||||||
|
def check_permissions(self, request, action, obj=None):
|
||||||
|
for permission in self.get_permissions():
|
||||||
|
if not permission.check_permissions(action=action, obj=obj):
|
||||||
|
self.permission_denied(request)
|
||||||
|
|
||||||
|
def check_throttles(self, request):
|
||||||
|
"""
|
||||||
|
Check if request should be throttled.
|
||||||
|
Raises an appropriate exception if the request is throttled.
|
||||||
|
"""
|
||||||
|
for throttle in self.get_throttles():
|
||||||
|
if not throttle.allow_request(request, self):
|
||||||
|
self.throttled(request, throttle.wait())
|
||||||
|
|
||||||
|
# Dispatch methods
|
||||||
|
|
||||||
|
def initialize_request(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Returns the initial request object.
|
||||||
|
"""
|
||||||
|
parser_context = self.get_parser_context(request)
|
||||||
|
|
||||||
|
return Request(request,
|
||||||
|
parsers=self.get_parsers(),
|
||||||
|
authenticators=self.get_authenticators(),
|
||||||
|
negotiator=self.get_content_negotiator(),
|
||||||
|
parser_context=parser_context)
|
||||||
|
|
||||||
|
def initial(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Runs anything that needs to occur prior to calling the method handler.
|
||||||
|
"""
|
||||||
|
self.format_kwarg = self.get_format_suffix(**kwargs)
|
||||||
|
|
||||||
|
# Ensure that the incoming request is permitted
|
||||||
|
self.perform_authentication(request)
|
||||||
|
self.check_throttles(request)
|
||||||
|
|
||||||
|
# Perform content negotiation and store the accepted info on the request
|
||||||
|
neg = self.perform_content_negotiation(request)
|
||||||
|
request.accepted_renderer, request.accepted_media_type = neg
|
||||||
|
|
||||||
|
def finalize_response(self, request, response, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Returns the final response object.
|
||||||
|
"""
|
||||||
|
# Make the error obvious if a proper response is not returned
|
||||||
|
assert isinstance(response, HttpResponseBase), (
|
||||||
|
'Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` '
|
||||||
|
'to be returned from the view, but received a `%s`'
|
||||||
|
% type(response)
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(response, Response):
|
||||||
|
if not getattr(request, 'accepted_renderer', None):
|
||||||
|
neg = self.perform_content_negotiation(request, force=True)
|
||||||
|
request.accepted_renderer, request.accepted_media_type = neg
|
||||||
|
|
||||||
|
response.accepted_renderer = request.accepted_renderer
|
||||||
|
response.accepted_media_type = request.accepted_media_type
|
||||||
|
response.renderer_context = self.get_renderer_context()
|
||||||
|
|
||||||
|
for key, value in self.headers.items():
|
||||||
|
response[key] = value
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def handle_exception(self, exc):
|
||||||
|
"""
|
||||||
|
Handle any exception that occurs, by returning an appropriate response,
|
||||||
|
or re-raising the error.
|
||||||
|
"""
|
||||||
|
if isinstance(exc, (exceptions.NotAuthenticated,
|
||||||
|
exceptions.AuthenticationFailed)):
|
||||||
|
# WWW-Authenticate header for 401 responses, else coerce to 403
|
||||||
|
auth_header = self.get_authenticate_header(self.request)
|
||||||
|
|
||||||
|
if auth_header:
|
||||||
|
exc.auth_header = auth_header
|
||||||
|
else:
|
||||||
|
exc.status_code = status.HTTP_403_FORBIDDEN
|
||||||
|
|
||||||
|
response = self.settings.EXCEPTION_HANDLER(exc)
|
||||||
|
|
||||||
|
if response is None:
|
||||||
|
raise
|
||||||
|
|
||||||
|
response.exception = True
|
||||||
|
return response
|
||||||
|
|
||||||
|
# Note: session based authentication is explicitly CSRF validated,
|
||||||
|
# all other authentication is CSRF exempt.
|
||||||
|
@csrf_exempt
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
`.dispatch()` is pretty much the same as Django's regular dispatch,
|
||||||
|
but with extra hooks for startup, finalize, and exception handling.
|
||||||
|
"""
|
||||||
|
self.args = args
|
||||||
|
self.kwargs = kwargs
|
||||||
|
request = self.initialize_request(request, *args, **kwargs)
|
||||||
|
self.request = request
|
||||||
|
self.headers = self.default_response_headers
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.initial(request, *args, **kwargs)
|
||||||
|
|
||||||
|
# Get the appropriate handler method
|
||||||
|
if request.method.lower() in self.http_method_names:
|
||||||
|
handler = getattr(self, request.method.lower(),
|
||||||
|
self.http_method_not_allowed)
|
||||||
|
else:
|
||||||
|
handler = self.http_method_not_allowed
|
||||||
|
|
||||||
|
response = handler(request, *args, **kwargs)
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
response = self.handle_exception(exc)
|
||||||
|
|
||||||
|
self.response = self.finalize_response(request, response, *args, **kwargs)
|
||||||
|
return self.response
|
||||||
|
|
||||||
|
def options(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Handler method for HTTP 'OPTIONS' request.
|
||||||
|
We may as well implement this as Django will otherwise provide
|
||||||
|
a less useful default implementation.
|
||||||
|
"""
|
||||||
|
return Response(self.metadata(request), status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def metadata(self, request):
|
||||||
|
"""
|
||||||
|
Return a dictionary of metadata about the view.
|
||||||
|
Used to return responses for OPTIONS requests.
|
||||||
|
"""
|
||||||
|
# By default we can't provide any form-like information, however the
|
||||||
|
# generic views override this implementation and add additional
|
||||||
|
# information for POST and PUT methods, based on the serializer.
|
||||||
|
ret = SortedDict()
|
||||||
|
ret['name'] = self.get_view_name()
|
||||||
|
ret['description'] = self.get_view_description()
|
||||||
|
ret['renders'] = [renderer.media_type for renderer in self.renderer_classes]
|
||||||
|
ret['parses'] = [parser.media_type for parser in self.parser_classes]
|
||||||
|
return ret
|
|
@ -0,0 +1,161 @@
|
||||||
|
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
# This code is partially taken from django-rest-framework:
|
||||||
|
# Copyright (c) 2011-2014, Tom Christie
|
||||||
|
|
||||||
|
from functools import update_wrapper
|
||||||
|
from django.utils.decorators import classonlymethod
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
from . import mixins
|
||||||
|
from . import generics
|
||||||
|
from . import pagination
|
||||||
|
|
||||||
|
|
||||||
|
class ViewSetMixin(object):
|
||||||
|
"""
|
||||||
|
This is the magic.
|
||||||
|
|
||||||
|
Overrides `.as_view()` so that it takes an `actions` keyword that performs
|
||||||
|
the binding of HTTP methods to actions on the Resource.
|
||||||
|
|
||||||
|
For example, to create a concrete view binding the 'GET' and 'POST' methods
|
||||||
|
to the 'list' and 'create' actions...
|
||||||
|
|
||||||
|
view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classonlymethod
|
||||||
|
def as_view(cls, actions=None, **initkwargs):
|
||||||
|
"""
|
||||||
|
Because of the way class based views create a closure around the
|
||||||
|
instantiated view, we need to totally reimplement `.as_view`,
|
||||||
|
and slightly modify the view function that is created and returned.
|
||||||
|
"""
|
||||||
|
# The suffix initkwarg is reserved for identifing the viewset type
|
||||||
|
# eg. 'List' or 'Instance'.
|
||||||
|
cls.suffix = None
|
||||||
|
|
||||||
|
# sanitize keyword arguments
|
||||||
|
for key in initkwargs:
|
||||||
|
if key in cls.http_method_names:
|
||||||
|
raise TypeError("You tried to pass in the %s method name as a "
|
||||||
|
"keyword argument to %s(). Don't do that."
|
||||||
|
% (key, cls.__name__))
|
||||||
|
if not hasattr(cls, key):
|
||||||
|
raise TypeError("%s() received an invalid keyword %r" % (
|
||||||
|
cls.__name__, key))
|
||||||
|
|
||||||
|
def view(request, *args, **kwargs):
|
||||||
|
self = cls(**initkwargs)
|
||||||
|
# We also store the mapping of request methods to actions,
|
||||||
|
# so that we can later set the action attribute.
|
||||||
|
# eg. `self.action = 'list'` on an incoming GET request.
|
||||||
|
self.action_map = actions
|
||||||
|
|
||||||
|
# Bind methods to actions
|
||||||
|
# This is the bit that's different to a standard view
|
||||||
|
for method, action in actions.items():
|
||||||
|
handler = getattr(self, action)
|
||||||
|
setattr(self, method, handler)
|
||||||
|
|
||||||
|
# Patch this in as it's otherwise only present from 1.5 onwards
|
||||||
|
if hasattr(self, 'get') and not hasattr(self, 'head'):
|
||||||
|
self.head = self.get
|
||||||
|
|
||||||
|
# And continue as usual
|
||||||
|
return self.dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
# take name and docstring from class
|
||||||
|
update_wrapper(view, cls, updated=())
|
||||||
|
|
||||||
|
# and possible attributes set by decorators
|
||||||
|
# like csrf_exempt from dispatch
|
||||||
|
update_wrapper(view, cls.dispatch, assigned=())
|
||||||
|
|
||||||
|
# We need to set these on the view function, so that breadcrumb
|
||||||
|
# generation can pick out these bits of information from a
|
||||||
|
# resolved URL.
|
||||||
|
view.cls = cls
|
||||||
|
view.suffix = initkwargs.get('suffix', None)
|
||||||
|
return view
|
||||||
|
|
||||||
|
def initialize_request(self, request, *args, **kargs):
|
||||||
|
"""
|
||||||
|
Set the `.action` attribute on the view,
|
||||||
|
depending on the request method.
|
||||||
|
"""
|
||||||
|
request = super(ViewSetMixin, self).initialize_request(request, *args, **kargs)
|
||||||
|
self.action = self.action_map.get(request.method.lower())
|
||||||
|
return request
|
||||||
|
|
||||||
|
def check_permissions(self, request, action:str=None, obj:object=None):
|
||||||
|
if action is None:
|
||||||
|
action = self.action
|
||||||
|
return super().check_permissions(request, action=action, obj=obj)
|
||||||
|
|
||||||
|
|
||||||
|
class ViewSet(ViewSetMixin, views.APIView):
|
||||||
|
"""
|
||||||
|
The base ViewSet class does not provide any actions by default.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
|
||||||
|
"""
|
||||||
|
The GenericViewSet class does not provide any actions by default,
|
||||||
|
but does include the base set of generic view behavior, such as
|
||||||
|
the `get_object` and `get_queryset` methods.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
|
||||||
|
mixins.ListModelMixin,
|
||||||
|
GenericViewSet):
|
||||||
|
"""
|
||||||
|
A viewset that provides default `list()` and `retrieve()` actions.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ModelViewSet(mixins.CreateModelMixin,
|
||||||
|
mixins.RetrieveModelMixin,
|
||||||
|
mixins.UpdateModelMixin,
|
||||||
|
mixins.DestroyModelMixin,
|
||||||
|
mixins.ListModelMixin,
|
||||||
|
GenericViewSet):
|
||||||
|
"""
|
||||||
|
A viewset that provides default `create()`, `retrieve()`, `update()`,
|
||||||
|
`partial_update()`, `destroy()` and `list()` actions.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ModelCrudViewSet(pagination.HeadersPaginationMixin,
|
||||||
|
pagination.ConditionalPaginationMixin,
|
||||||
|
ModelViewSet):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ModelListViewSet(pagination.HeadersPaginationMixin,
|
||||||
|
pagination.ConditionalPaginationMixin,
|
||||||
|
mixins.RetrieveModelMixin,
|
||||||
|
mixins.ListModelMixin,
|
||||||
|
GenericViewSet):
|
||||||
|
pass
|
|
@ -26,6 +26,7 @@ def detail_route(methods=['get'], **kwargs):
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
func.bind_to_methods = methods
|
func.bind_to_methods = methods
|
||||||
func.detail = True
|
func.detail = True
|
||||||
|
func.permission_classes = kwargs.get('permission_classes', [])
|
||||||
func.kwargs = kwargs
|
func.kwargs = kwargs
|
||||||
return func
|
return func
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -38,6 +39,7 @@ def list_route(methods=['get'], **kwargs):
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
func.bind_to_methods = methods
|
func.bind_to_methods = methods
|
||||||
func.detail = False
|
func.detail = False
|
||||||
|
func.permission_classes = kwargs.get('permission_classes', [])
|
||||||
func.kwargs = kwargs
|
func.kwargs = kwargs
|
||||||
return func
|
return func
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -52,6 +54,7 @@ def link(**kwargs):
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
func.bind_to_methods = ['get']
|
func.bind_to_methods = ['get']
|
||||||
func.detail = True
|
func.detail = True
|
||||||
|
func.permission_classes = kwargs.get('permission_classes', [])
|
||||||
func.kwargs = kwargs
|
func.kwargs = kwargs
|
||||||
return func
|
return func
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -66,6 +69,7 @@ def action(methods=['post'], **kwargs):
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
func.bind_to_methods = methods
|
func.bind_to_methods = methods
|
||||||
func.detail = True
|
func.detail = True
|
||||||
|
func.permission_classes = kwargs.get('permission_classes', [])
|
||||||
func.kwargs = kwargs
|
func.kwargs = kwargs
|
||||||
return func
|
return func
|
||||||
return decorator
|
return decorator
|
||||||
|
|
|
@ -16,13 +16,14 @@
|
||||||
|
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.db.models.sql.where import ExtraWhere, OR
|
||||||
|
|
||||||
from rest_framework import filters
|
from rest_framework import filters
|
||||||
|
|
||||||
from taiga.base import tags
|
from taiga.base import tags
|
||||||
|
|
||||||
|
|
||||||
class QueryParamsFilterMixin(object):
|
class QueryParamsFilterMixin(filters.BaseFilterBackend):
|
||||||
_special_values_dict = {
|
_special_values_dict = {
|
||||||
'true': True,
|
'true': True,
|
||||||
'false': False,
|
'false': False,
|
||||||
|
@ -53,7 +54,8 @@ class QueryParamsFilterMixin(object):
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
class OrderByFilterMixin(object):
|
|
||||||
|
class OrderByFilterMixin(QueryParamsFilterMixin):
|
||||||
order_by_query_param = "order_by"
|
order_by_query_param = "order_by"
|
||||||
|
|
||||||
def filter_queryset(self, request, queryset, view):
|
def filter_queryset(self, request, queryset, view):
|
||||||
|
@ -72,18 +74,122 @@ class OrderByFilterMixin(object):
|
||||||
if field_name not in order_by_fields:
|
if field_name not in order_by_fields:
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
return queryset.order_by(raw_fieldname)
|
return super().filter_queryset(request, queryset.order_by(raw_fieldname), view)
|
||||||
|
|
||||||
|
|
||||||
class FilterBackend(OrderByFilterMixin,
|
class FilterBackend(OrderByFilterMixin):
|
||||||
QueryParamsFilterMixin,
|
|
||||||
filters.BaseFilterBackend):
|
|
||||||
"""
|
"""
|
||||||
Default filter backend.
|
Default filter backend.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionBasedFilterBackend(FilterBackend):
|
||||||
|
permission = None
|
||||||
|
|
||||||
|
def filter_queryset(self, request, queryset, view):
|
||||||
|
# TODO: Make permissions aware of members permissions, now only check membership.
|
||||||
|
qs = queryset
|
||||||
|
|
||||||
|
if request.user.is_authenticated() and request.user.is_superuser:
|
||||||
|
qs = qs
|
||||||
|
elif request.user.is_authenticated():
|
||||||
|
qs = qs.filter(Q(project__owner=request.user) |
|
||||||
|
Q(project__members=request.user) |
|
||||||
|
Q(project__is_private=False))
|
||||||
|
qs.query.where.add(ExtraWhere(["projects_project.public_permissions @> ARRAY['{}']".format(self.permission)], []), OR)
|
||||||
|
else:
|
||||||
|
qs = qs.filter(project__is_private=False)
|
||||||
|
qs.query.where.add(ExtraWhere(["projects_project.anon_permissions @> ARRAY['{}']".format(self.permission)], []), OR)
|
||||||
|
|
||||||
|
return super().filter_queryset(request, qs.distinct(), view)
|
||||||
|
|
||||||
|
|
||||||
|
class CanViewProjectFilterBackend(PermissionBasedFilterBackend):
|
||||||
|
permission = "view_project"
|
||||||
|
|
||||||
|
|
||||||
|
class CanViewUsFilterBackend(PermissionBasedFilterBackend):
|
||||||
|
permission = "view_us"
|
||||||
|
|
||||||
|
|
||||||
|
class CanViewIssuesFilterBackend(PermissionBasedFilterBackend):
|
||||||
|
permission = "view_issues"
|
||||||
|
|
||||||
|
|
||||||
|
class CanViewTasksFilterBackend(PermissionBasedFilterBackend):
|
||||||
|
permission = "view_tasks"
|
||||||
|
|
||||||
|
|
||||||
|
class CanViewWikiPagesFilterBackend(PermissionBasedFilterBackend):
|
||||||
|
permission = "view_wiki_pages"
|
||||||
|
|
||||||
|
|
||||||
|
class CanViewWikiLinksFilterBackend(PermissionBasedFilterBackend):
|
||||||
|
permission = "view_wiki_links"
|
||||||
|
|
||||||
|
|
||||||
|
class CanViewMilestonesFilterBackend(PermissionBasedFilterBackend):
|
||||||
|
permission = "view_milestones"
|
||||||
|
|
||||||
|
class PermissionBasedAttachmentFilterBackend(FilterBackend):
|
||||||
|
permission = None
|
||||||
|
|
||||||
|
def filter_queryset(self, request, queryset, view):
|
||||||
|
# TODO: Make permissions aware of members permissions, now only check membership.
|
||||||
|
qs = queryset
|
||||||
|
|
||||||
|
if request.user.is_authenticated() and request.user.is_superuser:
|
||||||
|
qs = qs
|
||||||
|
elif request.user.is_authenticated():
|
||||||
|
qs = qs.filter(Q(project__owner=request.user) |
|
||||||
|
Q(project__members=request.user) |
|
||||||
|
Q(project__is_private=False))
|
||||||
|
qs.query.where.add(ExtraWhere(["projects_project.public_permissions @> ARRAY['{}']".format(self.permission)], []), OR)
|
||||||
|
else:
|
||||||
|
qs = qs.filter(project__is_private=False)
|
||||||
|
qs.query.where.add(ExtraWhere(["projects_project.anon_permissions @> ARRAY['{}']".format(self.permission)], []), OR)
|
||||||
|
|
||||||
|
ct = view.get_content_type()
|
||||||
|
qs = qs.filter(content_type=ct)
|
||||||
|
|
||||||
|
return super().filter_queryset(request, qs.distinct(), view)
|
||||||
|
|
||||||
|
|
||||||
|
class CanViewUserStoryAttachmentFilterBackend(PermissionBasedAttachmentFilterBackend):
|
||||||
|
permission = "view_us"
|
||||||
|
|
||||||
|
|
||||||
|
class CanViewTaskAttachmentFilterBackend(PermissionBasedAttachmentFilterBackend):
|
||||||
|
permission = "view_tasks"
|
||||||
|
|
||||||
|
|
||||||
|
class CanViewIssueAttachmentFilterBackend(PermissionBasedAttachmentFilterBackend):
|
||||||
|
permission = "view_issues"
|
||||||
|
|
||||||
|
|
||||||
|
class CanViewWikiAttachmentFilterBackend(PermissionBasedAttachmentFilterBackend):
|
||||||
|
permission = "view_wiki_pages"
|
||||||
|
|
||||||
|
|
||||||
|
class CanViewProjectObjFilterBackend(FilterBackend):
|
||||||
|
def filter_queryset(self, request, queryset, view):
|
||||||
|
qs = queryset
|
||||||
|
|
||||||
|
if request.user.is_authenticated() and request.user.is_superuser:
|
||||||
|
qs = qs
|
||||||
|
elif request.user.is_authenticated():
|
||||||
|
qs = qs.filter(Q(owner=request.user) |
|
||||||
|
Q(members=request.user) |
|
||||||
|
Q(is_private=False))
|
||||||
|
qs.query.where.add(ExtraWhere(["projects_project.public_permissions @> ARRAY['view_project']"], []), OR)
|
||||||
|
else:
|
||||||
|
qs = qs.filter(is_private=False)
|
||||||
|
qs.query.where.add(ExtraWhere(["projects_project.anon_permissions @> ARRAY['view_project']"], []), OR)
|
||||||
|
|
||||||
|
return super().filter_queryset(request, qs.distinct(), view)
|
||||||
|
|
||||||
|
|
||||||
class IsProjectMemberFilterBackend(FilterBackend):
|
class IsProjectMemberFilterBackend(FilterBackend):
|
||||||
def filter_queryset(self, request, queryset, view):
|
def filter_queryset(self, request, queryset, view):
|
||||||
queryset = super().filter_queryset(request, queryset, view)
|
queryset = super().filter_queryset(request, queryset, view)
|
||||||
|
@ -92,7 +198,7 @@ class IsProjectMemberFilterBackend(FilterBackend):
|
||||||
if user.is_authenticated():
|
if user.is_authenticated():
|
||||||
queryset = queryset.filter(Q(project__members=request.user) |
|
queryset = queryset.filter(Q(project__members=request.user) |
|
||||||
Q(project__owner=request.user))
|
Q(project__owner=request.user))
|
||||||
return queryset.distinct()
|
return super().filter_queryset(request, queryset.distinct(), view)
|
||||||
|
|
||||||
|
|
||||||
class TagsFilter(FilterBackend):
|
class TagsFilter(FilterBackend):
|
||||||
|
@ -107,4 +213,4 @@ class TagsFilter(FilterBackend):
|
||||||
if query_tags:
|
if query_tags:
|
||||||
queryset = tags.filter(queryset, contains=query_tags)
|
queryset = tags.filter(queryset, contains=query_tags)
|
||||||
|
|
||||||
return queryset
|
return super().filter_queryset(request, queryset, view)
|
||||||
|
|
|
@ -45,6 +45,19 @@ class JsonField(serializers.WritableField):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class PgArrayField(serializers.WritableField):
|
||||||
|
"""
|
||||||
|
PgArray objects serializer.
|
||||||
|
"""
|
||||||
|
widget = widgets.Textarea
|
||||||
|
|
||||||
|
def to_native(self, obj):
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def from_native(self, data):
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class NeighborsSerializerMixin:
|
class NeighborsSerializerMixin:
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
|
||||||
|
def first(iterable):
|
||||||
|
if len(iterable) == 0:
|
||||||
|
return None
|
||||||
|
return iterable[0]
|
||||||
|
|
||||||
|
def next(data:list):
|
||||||
|
return data[1:]
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
ANON_PERMISSIONS = [
|
||||||
|
('view_project', _('View project')),
|
||||||
|
('view_milestones', _('View milestones')),
|
||||||
|
('view_us', _('View user stories')),
|
||||||
|
('view_tasks', _('View tasks')),
|
||||||
|
('view_issues', _('View issues')),
|
||||||
|
('view_wiki_pages', _('View wiki pages')),
|
||||||
|
('view_wiki_links', _('View wiki links')),
|
||||||
|
]
|
||||||
|
|
||||||
|
USER_PERMISSIONS = [
|
||||||
|
('view_project', _('View project')),
|
||||||
|
('view_milestones', _('View milestones')),
|
||||||
|
('view_us', _('View user stories')),
|
||||||
|
('view_issues', _('View issues')),
|
||||||
|
('vote_issues', _('Vote issues')),
|
||||||
|
('view_tasks', _('View tasks')),
|
||||||
|
('view_wiki_pages', _('View wiki pages')),
|
||||||
|
('view_wiki_links', _('View wiki links')),
|
||||||
|
('request_membership', _('Request membership')),
|
||||||
|
('add_us_to_project', _('Add user story to project')),
|
||||||
|
('add_comments_to_us', _('Add comments to user stories')),
|
||||||
|
('add_comments_to_task', _('Add comments to tasks')),
|
||||||
|
('add_issue', _('Add issues')),
|
||||||
|
('add_comments_issue', _('Add comments to issues')),
|
||||||
|
('add_wiki_page', _('Add wiki page')),
|
||||||
|
('modify_wiki_page', _('Modify wiki page')),
|
||||||
|
('add_wiki_link', _('Add wiki link')),
|
||||||
|
('modify_wiki_link', _('Modify wiki link')),
|
||||||
|
]
|
||||||
|
|
||||||
|
MEMBERS_PERMISSIONS = [
|
||||||
|
('view_project', _('View project')),
|
||||||
|
# Milestone permissions
|
||||||
|
('view_milestones', _('View milestones')),
|
||||||
|
('add_milestone', _('Add milestone')),
|
||||||
|
('modify_milestone', _('Modify milestone')),
|
||||||
|
('delete_last_milestone', _('Delete last milestone')),
|
||||||
|
('delete_milestone', _('Delete milestone')),
|
||||||
|
('add_us_to_milestone', _('Add use to milestone')),
|
||||||
|
('remove_us_from_milestone', _('Remove us from milestone')),
|
||||||
|
('reorder_us_on_milestone', _('Reorder us on milestone')),
|
||||||
|
# US permissions
|
||||||
|
('view_us', _('View user story')),
|
||||||
|
('add_us', _('Add user story')),
|
||||||
|
('modify_us', _('Modify user story')),
|
||||||
|
('delete_us', _('Delete user story')),
|
||||||
|
# Task permissions
|
||||||
|
('view_tasks', _('View tasks')),
|
||||||
|
('add_task', _('Add task')),
|
||||||
|
('modify_task', _('Modify task')),
|
||||||
|
('delete_task', _('Delete task')),
|
||||||
|
# Issue permissions
|
||||||
|
('view_issues', _('View issues')),
|
||||||
|
('vote_issues', _('Vote issues')),
|
||||||
|
('add_issue', _('Add issue')),
|
||||||
|
('modify_issue', _('Modify issue')),
|
||||||
|
('delete_issue', _('Delete issue')),
|
||||||
|
# Wiki page permissions
|
||||||
|
('view_wiki_pages', _('View wiki pages')),
|
||||||
|
('add_wiki_page', _('Add wiki page')),
|
||||||
|
('modify_wiki_page', _('Modify wiki page')),
|
||||||
|
('delete_wiki_page', _('Delete wiki page')),
|
||||||
|
# Wiki link permissions
|
||||||
|
('view_wiki_links', _('View wiki links')),
|
||||||
|
('add_wiki_link', _('Add wiki link')),
|
||||||
|
('modify_wiki_link', _('Modify wiki link')),
|
||||||
|
('delete_wiki_link', _('Delete wiki link')),
|
||||||
|
]
|
||||||
|
|
||||||
|
OWNERS_PERMISSIONS = [
|
||||||
|
('modify_project', _('Modify project')),
|
||||||
|
('add_member', _('Add member')),
|
||||||
|
('remove_member', _('Remove member')),
|
||||||
|
('delete_project', _('Delete project')),
|
||||||
|
('admin_project_values', _('Admin project values')),
|
||||||
|
('admin_roles', _('Admin roles')),
|
||||||
|
]
|
|
@ -0,0 +1,75 @@
|
||||||
|
from taiga.projects.models import Membership, Project
|
||||||
|
from .permissions import OWNERS_PERMISSIONS, MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||||
|
|
||||||
|
|
||||||
|
def _get_user_project_membership(user, project):
|
||||||
|
if user.is_anonymous():
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
return Membership.objects.get(user=user, project=project)
|
||||||
|
except Membership.DoesNotExist:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_object_project(obj):
|
||||||
|
project = None
|
||||||
|
|
||||||
|
if isinstance(obj, Project):
|
||||||
|
project = obj
|
||||||
|
elif obj and hasattr(obj, 'project'):
|
||||||
|
project = obj.project
|
||||||
|
return project
|
||||||
|
|
||||||
|
|
||||||
|
def is_project_owner(user, obj):
|
||||||
|
if user.is_superuser:
|
||||||
|
return True
|
||||||
|
|
||||||
|
project = _get_object_project(obj)
|
||||||
|
|
||||||
|
if project:
|
||||||
|
return project.owner == user
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def user_has_perm(user, perm, obj=None):
|
||||||
|
project = _get_object_project(obj)
|
||||||
|
|
||||||
|
if not project:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return perm in get_user_project_permissions(user, project)
|
||||||
|
|
||||||
|
|
||||||
|
def role_has_perm(role, perm):
|
||||||
|
return perm in role.permissions
|
||||||
|
|
||||||
|
|
||||||
|
def _get_membership_permissions(membership):
|
||||||
|
if membership and membership.role and membership.role.permissions:
|
||||||
|
return membership.role.permissions
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_project_permissions(user, project):
|
||||||
|
membership = _get_user_project_membership(user, project)
|
||||||
|
if user.is_superuser:
|
||||||
|
owner_permissions = list(map(lambda perm: perm[0], OWNERS_PERMISSIONS))
|
||||||
|
members_permissions = list(map(lambda perm: perm[0], MEMBERS_PERMISSIONS))
|
||||||
|
anon_permissions = list(map(lambda perm: perm[0], ANON_PERMISSIONS))
|
||||||
|
public_permissions = list(map(lambda perm: perm[0], USER_PERMISSIONS))
|
||||||
|
return set(owner_permissions + members_permissions + public_permissions + anon_permissions)
|
||||||
|
elif project.owner == user:
|
||||||
|
owner_permissions = list(map(lambda perm: perm[0], OWNERS_PERMISSIONS))
|
||||||
|
members_permissions = list(map(lambda perm: perm[0], MEMBERS_PERMISSIONS))
|
||||||
|
return set(project.anon_permissions + project.public_permissions + members_permissions + owner_permissions)
|
||||||
|
elif membership:
|
||||||
|
if membership.is_owner:
|
||||||
|
owner_permissions = list(map(lambda perm: perm[0], OWNERS_PERMISSIONS))
|
||||||
|
return set(project.anon_permissions + project.public_permissions + _get_membership_permissions(membership) + owner_permissions)
|
||||||
|
else:
|
||||||
|
return set(project.anon_permissions + project.public_permissions + _get_membership_permissions(membership))
|
||||||
|
elif user.is_authenticated():
|
||||||
|
return set(project.anon_permissions + project.public_permissions)
|
||||||
|
else:
|
||||||
|
return set(project.anon_permissions)
|
|
@ -20,7 +20,6 @@ from django.db.models import Q
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.exceptions import ParseError
|
from rest_framework.exceptions import ParseError
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
@ -30,9 +29,11 @@ from djmail.template_mail import MagicMailBuilder
|
||||||
|
|
||||||
from taiga.base import filters
|
from taiga.base import filters
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
from taiga.base.decorators import list_route, detail_route
|
from taiga.base.decorators import list_route
|
||||||
from taiga.base.permissions import has_project_perm
|
from taiga.base.decorators import detail_route
|
||||||
from taiga.base.api import ModelCrudViewSet, ModelListViewSet, RetrieveModelMixin
|
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
|
||||||
|
from taiga.base.api.mixins import RetrieveModelMixin
|
||||||
|
from taiga.base.api.permissions import IsAuthenticatedPermission, AllowAnyPermission
|
||||||
from taiga.base.utils.slug import slugify_uniquely
|
from taiga.base.utils.slug import slugify_uniquely
|
||||||
from taiga.users.models import Role
|
from taiga.users.models import Role
|
||||||
|
|
||||||
|
@ -40,20 +41,95 @@ from . import serializers
|
||||||
from . import models
|
from . import models
|
||||||
from . import permissions
|
from . import permissions
|
||||||
from . import services
|
from . import services
|
||||||
|
|
||||||
from .votes.utils import attach_votescount_to_queryset
|
from .votes.utils import attach_votescount_to_queryset
|
||||||
from .votes import services as votes_service
|
from .votes import services as votes_service
|
||||||
from .votes import serializers as votes_serializers
|
from .votes import serializers as votes_serializers
|
||||||
|
|
||||||
|
|
||||||
class ProjectAdminViewSet(ModelCrudViewSet):
|
class ProjectViewSet(ModelCrudViewSet):
|
||||||
serializer_class = serializers.ProjectDetailSerializer
|
serializer_class = serializers.ProjectDetailSerializer
|
||||||
list_serializer_class = serializers.ProjectSerializer
|
list_serializer_class = serializers.ProjectSerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.ProjectAdminPermission)
|
permission_classes = (permissions.ProjectPermission, )
|
||||||
|
filter_backends = (filters.CanViewProjectObjFilterBackend,)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = models.Project.objects.all()
|
qs = models.Project.objects.all()
|
||||||
qs = attach_votescount_to_queryset(qs, as_field="stars_count")
|
return attach_votescount_to_queryset(qs, as_field="stars_count")
|
||||||
return qs
|
|
||||||
|
@detail_route(methods=['get'])
|
||||||
|
def stats(self, request, pk=None):
|
||||||
|
project = self.get_object()
|
||||||
|
self.check_permissions(request, 'stats', project)
|
||||||
|
return Response(services.get_stats_for_project(project))
|
||||||
|
|
||||||
|
@detail_route(methods=['post'])
|
||||||
|
def star(self, request, pk=None):
|
||||||
|
project = self.get_object()
|
||||||
|
self.check_permissions(request, 'star', project)
|
||||||
|
votes_service.add_vote(project, user=request.user)
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@detail_route(methods=['post'])
|
||||||
|
def unstar(self, request, pk=None):
|
||||||
|
project = self.get_object()
|
||||||
|
self.check_permissions(request, 'unstar', project)
|
||||||
|
votes_service.remove_vote(project, user=request.user)
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@detail_route(methods=['get'])
|
||||||
|
def issues_stats(self, request, pk=None):
|
||||||
|
project = self.get_object()
|
||||||
|
self.check_permissions(request, 'issues_stats', project)
|
||||||
|
return Response(services.get_stats_for_project_issues(project))
|
||||||
|
|
||||||
|
@detail_route(methods=['get'])
|
||||||
|
def issue_filters_data(self, request, pk=None):
|
||||||
|
project = self.get_object()
|
||||||
|
self.check_permissions(request, 'issues_filters_data', project)
|
||||||
|
return Response(services.get_issues_filters_data(project))
|
||||||
|
|
||||||
|
@detail_route(methods=['get'])
|
||||||
|
def tags(self, request, pk=None):
|
||||||
|
project = self.get_object()
|
||||||
|
self.check_permissions(request, 'tags', project)
|
||||||
|
return Response(services.get_all_tags(project))
|
||||||
|
|
||||||
|
@detail_route(methods=['get'])
|
||||||
|
def fans(self, request, pk=None):
|
||||||
|
project = self.get_object()
|
||||||
|
self.check_permissions(request, 'fans', project)
|
||||||
|
|
||||||
|
voters = votes_service.get_voters(project)
|
||||||
|
voters_data = votes_serializers.VoterSerializer(voters, many=True)
|
||||||
|
return Response(voters_data.data)
|
||||||
|
|
||||||
|
@detail_route(methods=["POST"])
|
||||||
|
def create_template(self, request, **kwargs):
|
||||||
|
template_name = request.DATA.get('template_name', None)
|
||||||
|
template_description = request.DATA.get('template_description', None)
|
||||||
|
|
||||||
|
if not template_name:
|
||||||
|
raise ParseError("Not valid template name")
|
||||||
|
|
||||||
|
if not template_description:
|
||||||
|
raise ParseError("Not valid template description")
|
||||||
|
|
||||||
|
template_slug = slugify_uniquely(template_name, models.ProjectTemplate)
|
||||||
|
|
||||||
|
project = self.get_object()
|
||||||
|
|
||||||
|
self.check_permissions(request, 'create_template', project)
|
||||||
|
|
||||||
|
template = models.ProjectTemplate(
|
||||||
|
name=template_name,
|
||||||
|
slug=template_slug,
|
||||||
|
description=template_description,
|
||||||
|
)
|
||||||
|
|
||||||
|
template.load_data_from_project(project)
|
||||||
|
template.save()
|
||||||
|
return Response(serializers.ProjectTemplateSerializer(template).data, status=201)
|
||||||
|
|
||||||
def pre_save(self, obj):
|
def pre_save(self, obj):
|
||||||
if not obj.id:
|
if not obj.id:
|
||||||
|
@ -66,61 +142,11 @@ class ProjectAdminViewSet(ModelCrudViewSet):
|
||||||
super().pre_save(obj)
|
super().pre_save(obj)
|
||||||
|
|
||||||
|
|
||||||
class ProjectViewSet(ModelCrudViewSet):
|
|
||||||
serializer_class = serializers.ProjectDetailSerializer
|
|
||||||
list_serializer_class = serializers.ProjectSerializer
|
|
||||||
permission_classes = (IsAuthenticated, permissions.ProjectPermission)
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
qs = models.Project.objects.all()
|
|
||||||
qs = attach_votescount_to_queryset(qs, as_field="stars_count")
|
|
||||||
qs = qs.filter(Q(owner=self.request.user) |
|
|
||||||
Q(members=self.request.user))
|
|
||||||
return qs.distinct()
|
|
||||||
|
|
||||||
@detail_route(methods=['get'])
|
|
||||||
def stats(self, request, pk=None):
|
|
||||||
project = self.get_object()
|
|
||||||
return Response(services.get_stats_for_project(project))
|
|
||||||
|
|
||||||
@detail_route(methods=['post'], permission_classes=(IsAuthenticated,))
|
|
||||||
def star(self, request, pk=None):
|
|
||||||
project = self.get_object()
|
|
||||||
votes_service.add_vote(project, user=request.user)
|
|
||||||
return Response(status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
@detail_route(methods=['post'], permission_classes=(IsAuthenticated,))
|
|
||||||
def unstar(self, request, pk=None):
|
|
||||||
project = self.get_object()
|
|
||||||
votes_service.remove_vote(project, user=request.user)
|
|
||||||
return Response(status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
@detail_route(methods=['get'])
|
|
||||||
def issues_stats(self, request, pk=None):
|
|
||||||
project = self.get_object()
|
|
||||||
return Response(services.get_stats_for_project_issues(project))
|
|
||||||
|
|
||||||
@detail_route(methods=['get'])
|
|
||||||
def issue_filters_data(self, request, pk=None):
|
|
||||||
project = self.get_object()
|
|
||||||
return Response(services.get_issues_filters_data(project))
|
|
||||||
|
|
||||||
@detail_route(methods=['get'])
|
|
||||||
def tags(self, request, pk=None):
|
|
||||||
project = self.get_object()
|
|
||||||
return Response(services.get_all_tags(project))
|
|
||||||
|
|
||||||
def pre_save(self, obj):
|
|
||||||
if not obj.id:
|
|
||||||
obj.owner = self.request.user
|
|
||||||
|
|
||||||
super().pre_save(obj)
|
|
||||||
|
|
||||||
|
|
||||||
class MembershipViewSet(ModelCrudViewSet):
|
class MembershipViewSet(ModelCrudViewSet):
|
||||||
model = models.Membership
|
model = models.Membership
|
||||||
serializer_class = serializers.MembershipSerializer
|
serializer_class = serializers.MembershipSerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.MembershipPermission)
|
permission_classes = (permissions.MembershipPermission,)
|
||||||
|
filter_backends = (filters.CanViewProjectFilterBackend,)
|
||||||
filter_fields = ("project", "role")
|
filter_fields = ("project", "role")
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
|
@ -164,14 +190,14 @@ class MembershipViewSet(ModelCrudViewSet):
|
||||||
email.send()
|
email.send()
|
||||||
|
|
||||||
|
|
||||||
class InvitationViewSet(RetrieveModelMixin, viewsets.ReadOnlyModelViewSet):
|
class InvitationViewSet(ModelListViewSet):
|
||||||
"""
|
"""
|
||||||
Only used by front for get invitation by it token.
|
Only used by front for get invitation by it token.
|
||||||
"""
|
"""
|
||||||
queryset = models.Membership.objects.filter(user__isnull=True)
|
queryset = models.Membership.objects.filter(user__isnull=True)
|
||||||
serializer_class = serializers.MembershipSerializer
|
serializer_class = serializers.MembershipSerializer
|
||||||
lookup_field = "token"
|
lookup_field = "token"
|
||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAnyPermission,)
|
||||||
|
|
||||||
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."))
|
||||||
|
@ -180,13 +206,10 @@ class InvitationViewSet(RetrieveModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||||
class RolesViewSet(ModelCrudViewSet):
|
class RolesViewSet(ModelCrudViewSet):
|
||||||
model = Role
|
model = Role
|
||||||
serializer_class = serializers.RoleSerializer
|
serializer_class = serializers.RoleSerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.RolesPermission)
|
permission_classes = (permissions.RolesPermission, )
|
||||||
filter_backends = (filters.IsProjectMemberFilterBackend,)
|
filter_backends = (filters.CanViewProjectFilterBackend,)
|
||||||
filter_fields = ('project',)
|
filter_fields = ('project',)
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return self.model.objects.all().prefetch_related('permissions')
|
|
||||||
|
|
||||||
|
|
||||||
# User Stories commin ViewSets
|
# User Stories commin ViewSets
|
||||||
|
|
||||||
|
@ -213,139 +236,93 @@ class BulkUpdateOrderMixin(object):
|
||||||
|
|
||||||
project = get_object_or_404(models.Project, id=project_id)
|
project = get_object_or_404(models.Project, id=project_id)
|
||||||
|
|
||||||
if request.user != project.owner and not has_project_perm(request.user, project, self.bulk_update_perm):
|
self.check_permissions(request, 'bulk_update_order', project)
|
||||||
raise exc.PermissionDenied(_("You don't have permisions %s.") % self.bulk_update_perm)
|
|
||||||
|
|
||||||
self.bulk_update_order(project, request.user, bulk_data)
|
self.__class__.bulk_update_order_action(project, request.user, bulk_data)
|
||||||
return Response(data=None, status=status.HTTP_204_NO_CONTENT)
|
return Response(data=None, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
class PointsViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
|
class PointsViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
|
||||||
model = models.Points
|
model = models.Points
|
||||||
serializer_class = serializers.PointsSerializer
|
serializer_class = serializers.PointsSerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.PointsPermission)
|
permission_classes = (permissions.PointsPermission,)
|
||||||
filter_backends = (filters.IsProjectMemberFilterBackend,)
|
filter_backends = (filters.CanViewProjectFilterBackend,)
|
||||||
filter_fields = ('project',)
|
filter_fields = ('project',)
|
||||||
bulk_update_param = "bulk_points"
|
bulk_update_param = "bulk_points"
|
||||||
bulk_update_perm = "change_points"
|
bulk_update_perm = "change_points"
|
||||||
bulk_update_order = services.bulk_update_points_order
|
bulk_update_order_action = services.bulk_update_points_order
|
||||||
|
|
||||||
|
|
||||||
class UserStoryStatusViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
|
class UserStoryStatusViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
|
||||||
model = models.UserStoryStatus
|
model = models.UserStoryStatus
|
||||||
serializer_class = serializers.UserStoryStatusSerializer
|
serializer_class = serializers.UserStoryStatusSerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.UserStoryStatusPermission)
|
permission_classes = (permissions.UserStoryStatusPermission,)
|
||||||
filter_backends = (filters.IsProjectMemberFilterBackend,)
|
filter_backends = (filters.CanViewProjectFilterBackend,)
|
||||||
filter_fields = ('project',)
|
filter_fields = ('project',)
|
||||||
bulk_update_param = "bulk_userstory_statuses"
|
bulk_update_param = "bulk_userstory_statuses"
|
||||||
bulk_update_perm = "change_userstorystatus"
|
bulk_update_perm = "change_userstorystatus"
|
||||||
bulk_update_order = services.bulk_update_userstory_status_order
|
bulk_update_order_action = services.bulk_update_userstory_status_order
|
||||||
|
|
||||||
|
|
||||||
class TaskStatusViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
|
class TaskStatusViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
|
||||||
model = models.TaskStatus
|
model = models.TaskStatus
|
||||||
serializer_class = serializers.TaskStatusSerializer
|
serializer_class = serializers.TaskStatusSerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.TaskStatusPermission)
|
permission_classes = (permissions.TaskStatusPermission,)
|
||||||
filter_backends = (filters.IsProjectMemberFilterBackend,)
|
filter_backends = (filters.CanViewProjectFilterBackend,)
|
||||||
filter_fields = ("project",)
|
filter_fields = ("project",)
|
||||||
bulk_update_param = "bulk_task_statuses"
|
bulk_update_param = "bulk_task_statuses"
|
||||||
bulk_update_perm = "change_taskstatus"
|
bulk_update_perm = "change_taskstatus"
|
||||||
bulk_update_order = services.bulk_update_task_status_order
|
bulk_update_order_action = services.bulk_update_task_status_order
|
||||||
|
|
||||||
|
|
||||||
class SeverityViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
|
class SeverityViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
|
||||||
model = models.Severity
|
model = models.Severity
|
||||||
serializer_class = serializers.SeveritySerializer
|
serializer_class = serializers.SeveritySerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.SeverityPermission)
|
permission_classes = (permissions.SeverityPermission,)
|
||||||
filter_backends = (filters.IsProjectMemberFilterBackend,)
|
filter_backends = (filters.CanViewProjectFilterBackend,)
|
||||||
filter_fields = ("project",)
|
filter_fields = ("project",)
|
||||||
bulk_update_param = "bulk_severities"
|
bulk_update_param = "bulk_severities"
|
||||||
bulk_update_perm = "change_severity"
|
bulk_update_perm = "change_severity"
|
||||||
bulk_update_order = services.bulk_update_severity_order
|
bulk_update_order_action = services.bulk_update_severity_order
|
||||||
|
|
||||||
|
|
||||||
class PriorityViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
|
class PriorityViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
|
||||||
model = models.Priority
|
model = models.Priority
|
||||||
serializer_class = serializers.PrioritySerializer
|
serializer_class = serializers.PrioritySerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.PriorityPermission)
|
permission_classes = (permissions.PriorityPermission,)
|
||||||
filter_backends = (filters.IsProjectMemberFilterBackend,)
|
filter_backends = (filters.CanViewProjectFilterBackend,)
|
||||||
filter_fields = ("project",)
|
filter_fields = ("project",)
|
||||||
bulk_update_param = "bulk_priorities"
|
bulk_update_param = "bulk_priorities"
|
||||||
bulk_update_perm = "change_priority"
|
bulk_update_perm = "change_priority"
|
||||||
bulk_update_order = services.bulk_update_priority_order
|
bulk_update_order_action = services.bulk_update_priority_order
|
||||||
|
|
||||||
|
|
||||||
class IssueTypeViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
|
class IssueTypeViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
|
||||||
model = models.IssueType
|
model = models.IssueType
|
||||||
serializer_class = serializers.IssueTypeSerializer
|
serializer_class = serializers.IssueTypeSerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.IssueTypePermission)
|
permission_classes = (permissions.IssueTypePermission,)
|
||||||
filter_backends = (filters.IsProjectMemberFilterBackend,)
|
filter_backends = (filters.CanViewProjectFilterBackend,)
|
||||||
filter_fields = ("project",)
|
filter_fields = ("project",)
|
||||||
bulk_update_param = "bulk_issue_types"
|
bulk_update_param = "bulk_issue_types"
|
||||||
bulk_update_perm = "change_issuetype"
|
bulk_update_perm = "change_issuetype"
|
||||||
bulk_update_order = services.bulk_update_issue_type_order
|
bulk_update_order_action = services.bulk_update_issue_type_order
|
||||||
|
|
||||||
|
|
||||||
class IssueStatusViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
|
class IssueStatusViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
|
||||||
model = models.IssueStatus
|
model = models.IssueStatus
|
||||||
serializer_class = serializers.IssueStatusSerializer
|
serializer_class = serializers.IssueStatusSerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.IssueStatusPermission)
|
permission_classes = (permissions.IssueStatusPermission,)
|
||||||
filter_backends = (filters.IsProjectMemberFilterBackend,)
|
filter_backends = (filters.CanViewProjectFilterBackend,)
|
||||||
filter_fields = ("project",)
|
filter_fields = ("project",)
|
||||||
bulk_update_param = "bulk_issue_statuses"
|
bulk_update_param = "bulk_issue_statuses"
|
||||||
bulk_update_perm = "change_issuestatus"
|
bulk_update_perm = "change_issuestatus"
|
||||||
bulk_update_order = services.bulk_update_issue_status_order
|
bulk_update_order_action = services.bulk_update_issue_status_order
|
||||||
|
|
||||||
|
|
||||||
class ProjectTemplateViewSet(ModelCrudViewSet):
|
class ProjectTemplateViewSet(ModelCrudViewSet):
|
||||||
model = models.ProjectTemplate
|
model = models.ProjectTemplate
|
||||||
serializer_class = serializers.ProjectTemplateSerializer
|
serializer_class = serializers.ProjectTemplateSerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.ProjectTemplatePermission)
|
permission_classes = (permissions.ProjectTemplatePermission,)
|
||||||
|
|
||||||
@list_route(methods=["POST"])
|
|
||||||
def create_from_project(self, request, **kwargs):
|
|
||||||
project_id = request.DATA.get('project_id', None)
|
|
||||||
template_name = request.DATA.get('template_name', None)
|
|
||||||
template_description = request.DATA.get('template_description', None)
|
|
||||||
|
|
||||||
if not template_name:
|
|
||||||
raise ParseError("Not valid template name")
|
|
||||||
|
|
||||||
template_slug = slugify_uniquely(template_name, models.ProjectTemplate)
|
|
||||||
|
|
||||||
try:
|
|
||||||
project = models.Project.objects.get(pk=project_id)
|
|
||||||
except models.Project.DoesNotExist:
|
|
||||||
raise ParseError("Not valid project_id")
|
|
||||||
|
|
||||||
template = models.ProjectTemplate(
|
|
||||||
name=template_name,
|
|
||||||
slug=template_slug,
|
|
||||||
description=template_description,
|
|
||||||
)
|
|
||||||
|
|
||||||
template.load_data_from_project(project)
|
|
||||||
template.save()
|
|
||||||
return Response(self.serializer_class(template).data, status=201)
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return models.ProjectTemplate.objects.all()
|
return models.ProjectTemplate.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class FansViewSet(ModelCrudViewSet):
|
|
||||||
serializer_class = votes_serializers.VoterSerializer
|
|
||||||
list_serializer_class = votes_serializers.VoterSerializer
|
|
||||||
permission_classes = (IsAuthenticated,)
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
project = models.Project.objects.get(pk=self.kwargs.get("project_id"))
|
|
||||||
return votes_service.get_voters(project)
|
|
||||||
|
|
||||||
|
|
||||||
class StarredViewSet(ModelCrudViewSet):
|
|
||||||
serializer_class = serializers.StarredSerializer
|
|
||||||
list_serializer_class = serializers.StarredSerializer
|
|
||||||
permission_classes = (IsAuthenticated,)
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return votes_service.get_voted(self.kwargs.get("user_id"), model=models.Project)
|
|
||||||
|
|
|
@ -14,15 +14,18 @@
|
||||||
# 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/>.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from django.conf import settings
|
||||||
|
from django import http
|
||||||
|
|
||||||
from taiga.base.api import ModelCrudViewSet
|
from taiga.base.api import ModelCrudViewSet
|
||||||
|
from taiga.base.api import generics
|
||||||
from taiga.base import filters
|
from taiga.base import filters
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
from taiga.projects.history.services import take_snapshot
|
|
||||||
|
|
||||||
from taiga.projects.notifications import WatchedResourceMixin
|
from taiga.projects.notifications import WatchedResourceMixin
|
||||||
from taiga.projects.history import HistoryResourceMixin
|
from taiga.projects.history import HistoryResourceMixin
|
||||||
|
@ -36,9 +39,6 @@ from . import models
|
||||||
class BaseAttachmentViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
|
class BaseAttachmentViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
|
||||||
model = models.Attachment
|
model = models.Attachment
|
||||||
serializer_class = serializers.AttachmentSerializer
|
serializer_class = serializers.AttachmentSerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.AttachmentPermission,)
|
|
||||||
|
|
||||||
filter_backends = (filters.IsProjectMemberFilterBackend,)
|
|
||||||
filter_fields = ["project", "object_id"]
|
filter_fields = ["project", "object_id"]
|
||||||
|
|
||||||
content_type = None
|
content_type = None
|
||||||
|
@ -47,12 +47,6 @@ class BaseAttachmentViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCru
|
||||||
app_name, model = self.content_type.split(".", 1)
|
app_name, model = self.content_type.split(".", 1)
|
||||||
return get_object_or_404(ContentType, app_label=app_name, model=model)
|
return get_object_or_404(ContentType, app_label=app_name, model=model)
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
ct = self.get_content_type()
|
|
||||||
qs = super().get_queryset()
|
|
||||||
qs = qs.filter(content_type=ct)
|
|
||||||
return qs.distinct()
|
|
||||||
|
|
||||||
def pre_save(self, obj):
|
def pre_save(self, obj):
|
||||||
if not obj.id:
|
if not obj.id:
|
||||||
obj.content_type = self.get_content_type()
|
obj.content_type = self.get_content_type()
|
||||||
|
@ -60,19 +54,13 @@ class BaseAttachmentViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCru
|
||||||
|
|
||||||
super().pre_save(obj)
|
super().pre_save(obj)
|
||||||
|
|
||||||
def pre_conditions_on_save(self, obj):
|
|
||||||
super().pre_conditions_on_save(obj)
|
|
||||||
|
|
||||||
if (obj.project.owner != self.request.user and
|
|
||||||
obj.project.memberships.filter(user=self.request.user).count() == 0):
|
|
||||||
raise exc.PermissionDenied(_("You don't have permissions for "
|
|
||||||
"add attachments to this user story"))
|
|
||||||
|
|
||||||
def get_object_for_snapshot(self, obj):
|
def get_object_for_snapshot(self, obj):
|
||||||
return obj.content_object
|
return obj.content_object
|
||||||
|
|
||||||
|
|
||||||
class UserStoryAttachmentViewSet(BaseAttachmentViewSet):
|
class UserStoryAttachmentViewSet(BaseAttachmentViewSet):
|
||||||
|
permission_classes = (permissions.UserStoryAttachmentPermission,)
|
||||||
|
filter_backends = (filters.CanViewUserStoryAttachmentFilterBackend,)
|
||||||
content_type = "userstories.userstory"
|
content_type = "userstories.userstory"
|
||||||
create_notification_template = "create_userstory_notification"
|
create_notification_template = "create_userstory_notification"
|
||||||
update_notification_template = "update_userstory_notification"
|
update_notification_template = "update_userstory_notification"
|
||||||
|
@ -80,6 +68,8 @@ class UserStoryAttachmentViewSet(BaseAttachmentViewSet):
|
||||||
|
|
||||||
|
|
||||||
class IssueAttachmentViewSet(BaseAttachmentViewSet):
|
class IssueAttachmentViewSet(BaseAttachmentViewSet):
|
||||||
|
permission_classes = (permissions.IssueAttachmentPermission,)
|
||||||
|
filter_backends = (filters.CanViewIssueAttachmentFilterBackend,)
|
||||||
content_type = "issues.issue"
|
content_type = "issues.issue"
|
||||||
create_notification_template = "create_issue_notification"
|
create_notification_template = "create_issue_notification"
|
||||||
update_notification_template = "update_issue_notification"
|
update_notification_template = "update_issue_notification"
|
||||||
|
@ -87,6 +77,8 @@ class IssueAttachmentViewSet(BaseAttachmentViewSet):
|
||||||
|
|
||||||
|
|
||||||
class TaskAttachmentViewSet(BaseAttachmentViewSet):
|
class TaskAttachmentViewSet(BaseAttachmentViewSet):
|
||||||
|
permission_classes = (permissions.TaskAttachmentPermission,)
|
||||||
|
filter_backends = (filters.CanViewTaskAttachmentFilterBackend,)
|
||||||
content_type = "tasks.task"
|
content_type = "tasks.task"
|
||||||
create_notification_template = "create_task_notification"
|
create_notification_template = "create_task_notification"
|
||||||
update_notification_template = "update_task_notification"
|
update_notification_template = "update_task_notification"
|
||||||
|
@ -94,7 +86,31 @@ class TaskAttachmentViewSet(BaseAttachmentViewSet):
|
||||||
|
|
||||||
|
|
||||||
class WikiAttachmentViewSet(BaseAttachmentViewSet):
|
class WikiAttachmentViewSet(BaseAttachmentViewSet):
|
||||||
|
permission_classes = (permissions.WikiAttachmentPermission,)
|
||||||
|
filter_backends = (filters.CanViewWikiAttachmentFilterBackend,)
|
||||||
content_type = "wiki.wikipage"
|
content_type = "wiki.wikipage"
|
||||||
create_notification_template = "create_wiki_notification"
|
create_notification_template = "create_wiki_notification"
|
||||||
update_notification_template = "update_wiki_notification"
|
update_notification_template = "update_wiki_notification"
|
||||||
destroy_notification_template = "destroy_wiki_notification"
|
destroy_notification_template = "destroy_wiki_notification"
|
||||||
|
|
||||||
|
|
||||||
|
class RawAttachmentView(generics.RetrieveAPIView):
|
||||||
|
queryset = models.Attachment.objects.all()
|
||||||
|
permission_classes = (permissions.RawAttachmentPermission,)
|
||||||
|
|
||||||
|
def _serve_attachment(self, attachment):
|
||||||
|
if settings.IN_DEVELOPMENT_SERVER:
|
||||||
|
return http.HttpResponseRedirect(attachment.url)
|
||||||
|
|
||||||
|
name = attachment.name
|
||||||
|
response = http.HttpResponse()
|
||||||
|
response['X-Accel-Redirect'] = "/{filepath}".format(filepath=name)
|
||||||
|
response['Content-Disposition'] = 'attachment;filename={filename}'.format(
|
||||||
|
filename=os.path.basename(name))
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def retrieve(self, request, *args, **kwargs):
|
||||||
|
self.object = self.get_object()
|
||||||
|
self.check_permissions(request, 'retrieve', self.object)
|
||||||
|
return self._serve_attachment(self.object.attached_file)
|
||||||
|
|
|
@ -15,14 +15,62 @@
|
||||||
# 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.permissions import BasePermission
|
from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
|
||||||
|
IsProjectOwner, AllowAny, PermissionComponent)
|
||||||
|
|
||||||
|
|
||||||
class AttachmentPermission(BasePermission):
|
class IsAttachmentOwnerPerm(PermissionComponent):
|
||||||
get_permission = "view_attachment"
|
def check_permissions(self, request, view, obj=None):
|
||||||
post_permission = "add_attachment"
|
if obj and obj.owner and request.user.is_authenticated():
|
||||||
put_permission = "change_attachment"
|
return request.user == obj.owner
|
||||||
patch_permission = "change_attachment"
|
return False
|
||||||
delete_permission = "delete_attachment"
|
|
||||||
safe_methods = ["HEAD", "OPTIONS"]
|
|
||||||
path_to_project = ["project"]
|
class UserStoryAttachmentPermission(ResourcePermission):
|
||||||
|
retrieve_perms = HasProjectPerm('view_us') | IsAttachmentOwnerPerm()
|
||||||
|
create_perms = HasProjectPerm('modify_us')
|
||||||
|
update_perms = HasProjectPerm('modify_us') | IsAttachmentOwnerPerm()
|
||||||
|
destroy_perms = HasProjectPerm('modify_us') | IsAttachmentOwnerPerm()
|
||||||
|
list_perms = AllowAny()
|
||||||
|
|
||||||
|
|
||||||
|
class TaskAttachmentPermission(ResourcePermission):
|
||||||
|
retrieve_perms = HasProjectPerm('view_tasks') | IsAttachmentOwnerPerm()
|
||||||
|
create_perms = HasProjectPerm('modify_task')
|
||||||
|
update_perms = HasProjectPerm('modify_task') | IsAttachmentOwnerPerm()
|
||||||
|
destroy_perms = HasProjectPerm('modify_task') | IsAttachmentOwnerPerm()
|
||||||
|
list_perms = AllowAny()
|
||||||
|
|
||||||
|
|
||||||
|
class IssueAttachmentPermission(ResourcePermission):
|
||||||
|
retrieve_perms = HasProjectPerm('view_issues') | IsAttachmentOwnerPerm()
|
||||||
|
create_perms = HasProjectPerm('modify_issue')
|
||||||
|
update_perms = HasProjectPerm('modify_issue') | IsAttachmentOwnerPerm()
|
||||||
|
destroy_perms = HasProjectPerm('modify_issue') | IsAttachmentOwnerPerm()
|
||||||
|
list_perms = AllowAny()
|
||||||
|
|
||||||
|
|
||||||
|
class WikiAttachmentPermission(ResourcePermission):
|
||||||
|
retrieve_perms = HasProjectPerm('view_wiki_pages') | IsAttachmentOwnerPerm()
|
||||||
|
create_perms = HasProjectPerm('modify_wiki_page')
|
||||||
|
update_perms = HasProjectPerm('modify_wiki_page') | IsAttachmentOwnerPerm()
|
||||||
|
destroy_perms = HasProjectPerm('modify_wiki_page') | IsAttachmentOwnerPerm()
|
||||||
|
list_perms = AllowAny()
|
||||||
|
|
||||||
|
|
||||||
|
class RawAttachmentPerm(PermissionComponent):
|
||||||
|
def check_permissions(self, request, view, obj=None):
|
||||||
|
is_owner = IsAttachmentOwnerPerm().check_permissions(request, view, obj)
|
||||||
|
if obj.content_type.app_label == "userstories" and obj.content_type.model == "userstory":
|
||||||
|
return UserStoryAttachmentPermission(request, view).check_permissions('retrieve', obj) or is_owner
|
||||||
|
elif obj.content_type.app_label == "tasks" and obj.content_type.model == "task":
|
||||||
|
return TaskAttachmentPermission(request, view).check_permissions('retrieve', obj) or is_owner
|
||||||
|
elif obj.content_type.app_label == "issues" and obj.content_type.model == "issue":
|
||||||
|
return IssueAttachmentPermission(request, view).check_permissions('retrieve', obj) or is_owner
|
||||||
|
elif obj.content_type.app_label == "wiki" and obj.content_type.model == "wikipage":
|
||||||
|
return WikiAttachmentPermission(request, view).check_permissions('retrieve', obj) or is_owner
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class RawAttachmentPermission(ResourcePermission):
|
||||||
|
retrieve_perms = RawAttachmentPerm()
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
import os
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django import http
|
|
||||||
|
|
||||||
from rest_framework import generics
|
|
||||||
from rest_framework.permissions import IsAuthenticated
|
|
||||||
|
|
||||||
from . import permissions
|
|
||||||
from . import models
|
|
||||||
|
|
||||||
|
|
||||||
def serve_attachment(request, attachment):
|
|
||||||
if settings.IN_DEVELOPMENT_SERVER:
|
|
||||||
return http.HttpResponseRedirect(attachment.url)
|
|
||||||
|
|
||||||
name = attachment.name
|
|
||||||
response = http.HttpResponse()
|
|
||||||
response['X-Accel-Redirect'] = "/{filepath}".format(filepath=name)
|
|
||||||
response['Content-Disposition'] = 'attachment;filename={filename}'.format(
|
|
||||||
filename=os.path.basename(name))
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
class RawAttachmentView(generics.RetrieveAPIView):
|
|
||||||
queryset = models.Attachment.objects.all()
|
|
||||||
permission_classes = (IsAuthenticated, permissions.AttachmentPermission,)
|
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
|
||||||
self.object = self.get_object()
|
|
||||||
return serve_attachment(request, self.object.attached_file)
|
|
|
@ -31,8 +31,6 @@ from . import services
|
||||||
# TODO: add specific permission for view history?
|
# TODO: add specific permission for view history?
|
||||||
|
|
||||||
class HistoryViewSet(GenericViewSet):
|
class HistoryViewSet(GenericViewSet):
|
||||||
filter_backends = (IsProjectMemberFilterBackend,)
|
|
||||||
permission_classes = (IsAuthenticated, permissions.HistoryPermission)
|
|
||||||
serializer_class = serializers.HistoryEntrySerializer
|
serializer_class = serializers.HistoryEntrySerializer
|
||||||
|
|
||||||
content_type = None
|
content_type = None
|
||||||
|
@ -41,13 +39,13 @@ class HistoryViewSet(GenericViewSet):
|
||||||
app_name, model = self.content_type.split(".", 1)
|
app_name, model = self.content_type.split(".", 1)
|
||||||
return get_object_or_404(ContentType, app_label=app_name, model=model)
|
return get_object_or_404(ContentType, app_label=app_name, model=model)
|
||||||
|
|
||||||
def get_object(self):
|
def get_queryset(self):
|
||||||
ct = self.get_content_type()
|
ct = self.get_content_type()
|
||||||
model_cls = ct.model_class()
|
model_cls = ct.model_class()
|
||||||
|
|
||||||
qs = model_cls.objects.all()
|
qs = model_cls.objects.all()
|
||||||
filtered_qs = self.filter_queryset(qs)
|
filtered_qs = self.filter_queryset(qs)
|
||||||
return super().get_object(queryset=filtered_qs)
|
return filtered_qs
|
||||||
|
|
||||||
def response_for_queryset(self, queryset):
|
def response_for_queryset(self, queryset):
|
||||||
# Switch between paginated or standard style responses
|
# Switch between paginated or standard style responses
|
||||||
|
@ -66,21 +64,27 @@ class HistoryViewSet(GenericViewSet):
|
||||||
|
|
||||||
def retrieve(self, request, pk):
|
def retrieve(self, request, pk):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
|
self.check_permissions(request, "retrieve", obj)
|
||||||
|
|
||||||
qs = services.get_history_queryset_by_model_instance(obj)
|
qs = services.get_history_queryset_by_model_instance(obj)
|
||||||
return self.response_for_queryset(qs)
|
return self.response_for_queryset(qs)
|
||||||
|
|
||||||
|
|
||||||
class UserStoryHistory(HistoryViewSet):
|
class UserStoryHistory(HistoryViewSet):
|
||||||
content_type = "userstories.userstory"
|
content_type = "userstories.userstory"
|
||||||
|
permission_classes = (permissions.UserStoryHistoryPermission,)
|
||||||
|
|
||||||
|
|
||||||
class TaskHistory(HistoryViewSet):
|
class TaskHistory(HistoryViewSet):
|
||||||
content_type = "tasks.task"
|
content_type = "tasks.task"
|
||||||
|
permission_classes = (permissions.TaskHistoryPermission,)
|
||||||
|
|
||||||
|
|
||||||
class IssueHistory(HistoryViewSet):
|
class IssueHistory(HistoryViewSet):
|
||||||
content_type = "issues.issue"
|
content_type = "issues.issue"
|
||||||
|
permission_classes = (permissions.IssueHistoryPermission,)
|
||||||
|
|
||||||
|
|
||||||
class WikiHistory(HistoryViewSet):
|
class WikiHistory(HistoryViewSet):
|
||||||
content_type = "wiki.wiki"
|
content_type = "wiki.wikipage"
|
||||||
|
permission_classes = (permissions.WikiHistoryPermission,)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
@ -12,9 +14,21 @@
|
||||||
# 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.permissions import Permission
|
from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
|
||||||
|
IsProjectOwner, AllowAny)
|
||||||
|
|
||||||
class HistoryPermission(Permission):
|
|
||||||
def has_object_permission(self, request, view, obj):
|
class UserStoryHistoryPermission(ResourcePermission):
|
||||||
# TODO: change this.
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
return True
|
|
||||||
|
|
||||||
|
class TaskHistoryPermission(ResourcePermission):
|
||||||
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
|
|
||||||
|
|
||||||
|
class IssueHistoryPermission(ResourcePermission):
|
||||||
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
|
|
||||||
|
|
||||||
|
class WikiHistoryPermission(ResourcePermission):
|
||||||
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
|
|
|
@ -15,19 +15,21 @@
|
||||||
# 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 django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.http import Http404
|
||||||
|
|
||||||
from rest_framework.permissions import IsAuthenticated
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework import filters
|
|
||||||
|
|
||||||
from taiga.base import filters
|
from taiga.base import filters
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
from taiga.base.decorators import list_route, detail_route
|
from taiga.base.decorators import detail_route
|
||||||
from taiga.base.api import ModelCrudViewSet
|
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
|
||||||
from taiga.base import tags
|
from taiga.base import tags
|
||||||
|
|
||||||
|
from taiga.users.models import User
|
||||||
|
|
||||||
from taiga.projects.notifications import WatchedResourceMixin
|
from taiga.projects.notifications import WatchedResourceMixin
|
||||||
from taiga.projects.occ import OCCResourceMixin
|
from taiga.projects.occ import OCCResourceMixin
|
||||||
from taiga.projects.history import HistoryResourceMixin
|
from taiga.projects.history import HistoryResourceMixin
|
||||||
|
@ -42,7 +44,7 @@ from . import serializers
|
||||||
|
|
||||||
|
|
||||||
class IssuesFilter(filters.FilterBackend):
|
class IssuesFilter(filters.FilterBackend):
|
||||||
filter_fields = ( "status", "severity", "priority", "owner", "assigned_to", "tags", "type")
|
filter_fields = ("status", "severity", "priority", "owner", "assigned_to", "tags", "type")
|
||||||
_special_values_dict = {
|
_special_values_dict = {
|
||||||
'true': True,
|
'true': True,
|
||||||
'false': False,
|
'false': False,
|
||||||
|
@ -80,7 +82,7 @@ class IssuesFilter(filters.FilterBackend):
|
||||||
|
|
||||||
for name, value in filter(lambda x: x[0] != "tags", filterdata.items()):
|
for name, value in filter(lambda x: x[0] != "tags", filterdata.items()):
|
||||||
if None in value:
|
if None in value:
|
||||||
qs_in_kwargs = {"{0}__in".format(name): [v for v in value if v != None]}
|
qs_in_kwargs = {"{0}__in".format(name): [v for v in value if v is not None]}
|
||||||
qs_isnull_kwargs = {"{0}__isnull".format(name): True}
|
qs_isnull_kwargs = {"{0}__isnull".format(name): True}
|
||||||
queryset = queryset.filter(Q(**qs_in_kwargs) | Q(**qs_isnull_kwargs))
|
queryset = queryset.filter(Q(**qs_in_kwargs) | Q(**qs_isnull_kwargs))
|
||||||
else:
|
else:
|
||||||
|
@ -104,9 +106,9 @@ class IssuesOrdering(filters.FilterBackend):
|
||||||
class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
|
class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
|
||||||
serializer_class = serializers.IssueNeighborsSerializer
|
serializer_class = serializers.IssueNeighborsSerializer
|
||||||
list_serializer_class = serializers.IssueSerializer
|
list_serializer_class = serializers.IssueSerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.IssuePermission)
|
permission_classes = (permissions.IssuePermission, )
|
||||||
|
|
||||||
filter_backends = (filters.IsProjectMemberFilterBackend, IssuesFilter, IssuesOrdering)
|
filter_backends = (filters.CanViewIssuesFilterBackend, IssuesFilter, IssuesOrdering)
|
||||||
retrieve_exclude_filters = (IssuesFilter,)
|
retrieve_exclude_filters = (IssuesFilter,)
|
||||||
|
|
||||||
filter_fields = ("project",)
|
filter_fields = ("project",)
|
||||||
|
@ -133,42 +135,65 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
||||||
def pre_conditions_on_save(self, obj):
|
def pre_conditions_on_save(self, obj):
|
||||||
super().pre_conditions_on_save(obj)
|
super().pre_conditions_on_save(obj)
|
||||||
|
|
||||||
if (obj.project.owner != self.request.user and
|
|
||||||
obj.project.memberships.filter(user=self.request.user).count() == 0):
|
|
||||||
raise exc.PermissionDenied(_("You don't have permissions for add/modify this issue."))
|
|
||||||
|
|
||||||
if obj.milestone and obj.milestone.project != obj.project:
|
if obj.milestone and obj.milestone.project != obj.project:
|
||||||
raise exc.PermissionDenied(_("You don't have permissions for add/modify this issue."))
|
raise exc.PermissionDenied(_("You don't have permissions to set this milestone to this issue."))
|
||||||
|
|
||||||
if obj.status and obj.status.project != obj.project:
|
if obj.status and obj.status.project != obj.project:
|
||||||
raise exc.PermissionDenied(_("You don't have permissions for add/modify this issue."))
|
raise exc.PermissionDenied(_("You don't have permissions to set this status to this issue."))
|
||||||
|
|
||||||
if obj.severity and obj.severity.project != obj.project:
|
if obj.severity and obj.severity.project != obj.project:
|
||||||
raise exc.PermissionDenied(_("You don't have permissions for add/modify this issue."))
|
raise exc.PermissionDenied(_("You don't have permissions to set this severity to this issue."))
|
||||||
|
|
||||||
if obj.priority and obj.priority.project != obj.project:
|
if obj.priority and obj.priority.project != obj.project:
|
||||||
raise exc.PermissionDenied(_("You don't have permissions for add/modify this issue."))
|
raise exc.PermissionDenied(_("You don't have permissions to set this priority to this issue."))
|
||||||
|
|
||||||
if obj.type and obj.type.project != obj.project:
|
if obj.type and obj.type.project != obj.project:
|
||||||
raise exc.PermissionDenied(_("You don't have permissions for add/modify this issue."))
|
raise exc.PermissionDenied(_("You don't have permissions to set this type to this issue."))
|
||||||
|
|
||||||
@detail_route(methods=['post'], permission_classes=(IsAuthenticated,))
|
@detail_route(methods=['post'])
|
||||||
def upvote(self, request, pk=None):
|
def upvote(self, request, pk=None):
|
||||||
issue = self.get_object()
|
issue = get_object_or_404(models.Issue, pk=pk)
|
||||||
|
|
||||||
|
self.check_permissions(request, 'upvote', issue)
|
||||||
|
|
||||||
votes_service.add_vote(issue, user=request.user)
|
votes_service.add_vote(issue, user=request.user)
|
||||||
return Response(status=status.HTTP_200_OK)
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
@detail_route(methods=['post'], permission_classes=(IsAuthenticated,))
|
@detail_route(methods=['post'])
|
||||||
def downvote(self, request, pk=None):
|
def downvote(self, request, pk=None):
|
||||||
issue = self.get_object()
|
issue = get_object_or_404(models.Issue, pk=pk)
|
||||||
|
|
||||||
|
self.check_permissions(request, 'downvote', issue)
|
||||||
|
|
||||||
votes_service.remove_vote(issue, user=request.user)
|
votes_service.remove_vote(issue, user=request.user)
|
||||||
return Response(status=status.HTTP_200_OK)
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class VotersViewSet(ModelCrudViewSet):
|
class VotersViewSet(ModelListViewSet):
|
||||||
serializer_class = votes_serializers.VoterSerializer
|
serializer_class = votes_serializers.VoterSerializer
|
||||||
list_serializer_class = votes_serializers.VoterSerializer
|
list_serializer_class = votes_serializers.VoterSerializer
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (permissions.IssueVotersPermission, )
|
||||||
|
|
||||||
|
def retrieve(self, request, *args, **kwargs):
|
||||||
|
pk = kwargs.get("pk", None)
|
||||||
|
issue_id = kwargs.get("issue_id", None)
|
||||||
|
issue = get_object_or_404(models.Issue, pk=issue_id)
|
||||||
|
|
||||||
|
self.check_permissions(request, 'retrieve', issue)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.object = votes_service.get_voters(issue).get(pk=pk)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
serializer = self.get_serializer(self.object)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
issue_id = kwargs.get("issue_id", None)
|
||||||
|
issue = get_object_or_404(models.Issue, pk=issue_id)
|
||||||
|
self.check_permissions(request, 'list', issue)
|
||||||
|
return super().list(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
issue = models.Issue.objects.get(pk=self.kwargs.get("issue_id"))
|
issue = models.Issue.objects.get(pk=self.kwargs.get("issue_id"))
|
||||||
|
|
|
@ -15,14 +15,36 @@
|
||||||
# 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.permissions import BasePermission
|
from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
|
||||||
|
IsProjectOwner, PermissionComponent,
|
||||||
|
AllowAny, IsAuthenticated)
|
||||||
|
|
||||||
|
|
||||||
class IssuePermission(BasePermission):
|
class IssuePermission(ResourcePermission):
|
||||||
get_permission = "view_issue"
|
enought_perms = IsProjectOwner()
|
||||||
post_permission = "add_issue"
|
global_perms = None
|
||||||
put_permission = "change_issue"
|
retrieve_perms = HasProjectPerm('view_issues')
|
||||||
patch_permission = "change_issue"
|
create_perms = HasProjectPerm('add_issue')
|
||||||
delete_permission = "delete_issue"
|
update_perms = HasProjectPerm('modify_issue')
|
||||||
safe_methods = ["HEAD", "OPTIONS"]
|
destroy_perms = HasProjectPerm('delete_issue')
|
||||||
path_to_project = ["project"]
|
list_perms = AllowAny()
|
||||||
|
upvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues')
|
||||||
|
downvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues')
|
||||||
|
|
||||||
|
|
||||||
|
class HasIssueIdUrlParam(PermissionComponent):
|
||||||
|
def check_permissions(self, request, view, obj=None):
|
||||||
|
param = view.kwargs.get('issue_id', None)
|
||||||
|
if param:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class IssueVotersPermission(ResourcePermission):
|
||||||
|
enought_perms = IsProjectOwner()
|
||||||
|
global_perms = None
|
||||||
|
retrieve_perms = HasProjectPerm('view_issues')
|
||||||
|
create_perms = HasProjectPerm('add_issue')
|
||||||
|
update_perms = HasProjectPerm('modify_issue')
|
||||||
|
destroy_perms = HasProjectPerm('delete_issue')
|
||||||
|
list_perms = HasProjectPerm('view_issues')
|
||||||
|
|
|
@ -38,8 +38,8 @@ import datetime
|
||||||
|
|
||||||
class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
|
class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
|
||||||
serializer_class = serializers.MilestoneSerializer
|
serializer_class = serializers.MilestoneSerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.MilestonePermission)
|
permission_classes = (permissions.MilestonePermission,)
|
||||||
filter_backends = (filters.IsProjectMemberFilterBackend,)
|
filter_backends = (filters.CanViewMilestonesFilterBackend,)
|
||||||
filter_fields = ("project",)
|
filter_fields = ("project",)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
@ -51,13 +51,6 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudView
|
||||||
qs = qs.order_by("-estimated_start")
|
qs = qs.order_by("-estimated_start")
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def pre_conditions_on_save(self, obj):
|
|
||||||
super().pre_conditions_on_save(obj)
|
|
||||||
|
|
||||||
if (obj.project.owner != self.request.user and
|
|
||||||
obj.project.memberships.filter(user=self.request.user).count() == 0):
|
|
||||||
raise exc.PreconditionError(_("You must not add a new milestone to this project."))
|
|
||||||
|
|
||||||
def pre_save(self, obj):
|
def pre_save(self, obj):
|
||||||
if not obj.id:
|
if not obj.id:
|
||||||
obj.owner = self.request.user
|
obj.owner = self.request.user
|
||||||
|
@ -67,6 +60,9 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudView
|
||||||
@detail_route(methods=['get'])
|
@detail_route(methods=['get'])
|
||||||
def stats(self, request, pk=None):
|
def stats(self, request, pk=None):
|
||||||
milestone = get_object_or_404(models.Milestone, pk=pk)
|
milestone = get_object_or_404(models.Milestone, pk=pk)
|
||||||
|
|
||||||
|
self.check_permissions(request, "stats", milestone)
|
||||||
|
|
||||||
total_points = milestone.total_points
|
total_points = milestone.total_points
|
||||||
milestone_stats = {
|
milestone_stats = {
|
||||||
'name': milestone.name,
|
'name': milestone.name,
|
||||||
|
|
|
@ -14,15 +14,16 @@
|
||||||
# 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.permissions import BasePermission
|
from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
|
||||||
|
IsProjectOwner, AllowAny)
|
||||||
|
|
||||||
|
|
||||||
class MilestonePermission(BasePermission):
|
class MilestonePermission(ResourcePermission):
|
||||||
get_permission = "view_milestone"
|
enought_perms = IsProjectOwner()
|
||||||
post_permission = "add_milestone"
|
global_perms = None
|
||||||
put_permission = "change_milestone"
|
retrieve_perms = HasProjectPerm('view_milestones')
|
||||||
patch_permission = "change_milestone"
|
create_perms = HasProjectPerm('add_milestone')
|
||||||
delete_permission = "delete_milestone"
|
update_perms = HasProjectPerm('modify_milestone')
|
||||||
safe_methods = ["HEAD", "OPTIONS"]
|
destroy_perms = HasProjectPerm('delete_milestone')
|
||||||
path_to_project = ["project"]
|
list_perms = AllowAny()
|
||||||
|
stats_perms = HasProjectPerm('view_milestones')
|
||||||
|
|
|
@ -52,7 +52,7 @@ class MilestoneSerializer(serializers.ModelSerializer):
|
||||||
qs = None
|
qs = None
|
||||||
# If the milestone exists:
|
# If the milestone exists:
|
||||||
if self.object and attrs.get("name", None):
|
if self.object and attrs.get("name", None):
|
||||||
qs = models.Milestone.objects.filter(project=self.object.project, name=attrs[source])
|
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):
|
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])
|
qs = models.Milestone.objects.filter(project=attrs["project"], name=attrs[source])
|
||||||
|
|
|
@ -23,12 +23,13 @@ from django.db.models.loading import get_model
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import Permission
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from picklefield.fields import PickledObjectField
|
from picklefield.fields import PickledObjectField
|
||||||
from django_pgjson.fields import JsonField
|
from django_pgjson.fields import JsonField
|
||||||
|
from djorm_pgarray.fields import TextArrayField
|
||||||
|
from taiga.permissions.permissions import ANON_PERMISSIONS, USER_PERMISSIONS
|
||||||
|
|
||||||
from taiga.base.tags import TaggedMixin
|
from taiga.base.tags import TaggedMixin
|
||||||
from taiga.users.models import Role
|
from taiga.users.models import Role
|
||||||
|
@ -43,6 +44,7 @@ VIDEOCONFERENCES_CHOICES = (
|
||||||
('talky', 'Talky'),
|
('talky', 'Talky'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Membership(models.Model):
|
class Membership(models.Model):
|
||||||
# This model stores all project memberships. Also
|
# This model stores all project memberships. Also
|
||||||
# stores invitations to memberships that does not have
|
# stores invitations to memberships that does not have
|
||||||
|
@ -152,6 +154,16 @@ class Project(ProjectDefaults, TaggedMixin, models.Model):
|
||||||
related_name="projects", null=True,
|
related_name="projects", null=True,
|
||||||
blank=True, default=None,
|
blank=True, default=None,
|
||||||
verbose_name=_("creation template"))
|
verbose_name=_("creation template"))
|
||||||
|
anon_permissions = TextArrayField(blank=True, null=True,
|
||||||
|
default=[],
|
||||||
|
verbose_name=_("anonymous permissions"),
|
||||||
|
choices=ANON_PERMISSIONS)
|
||||||
|
public_permissions = TextArrayField(blank=True, null=True,
|
||||||
|
default=[],
|
||||||
|
verbose_name=_("user permissions"),
|
||||||
|
choices=USER_PERMISSIONS)
|
||||||
|
is_private = models.BooleanField(default=False, null=False, blank=True,
|
||||||
|
verbose_name=_("is private"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "project"
|
verbose_name = "project"
|
||||||
|
@ -193,7 +205,10 @@ class Project(ProjectDefaults, TaggedMixin, models.Model):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get point instance that represent a null/undefined
|
# Get point instance that represent a null/undefined
|
||||||
|
try:
|
||||||
null_points_value = self.points.get(value=None)
|
null_points_value = self.points.get(value=None)
|
||||||
|
except Points.DoesNotExist:
|
||||||
|
null_points_value = None
|
||||||
|
|
||||||
# Iter over all project user stories and create
|
# Iter over all project user stories and create
|
||||||
# role point instance for new created roles.
|
# role point instance for new created roles.
|
||||||
|
@ -513,7 +528,6 @@ class ProjectTemplate(models.Model):
|
||||||
"severity": getattr(project.default_severity, "name", None)
|
"severity": getattr(project.default_severity, "name", None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
self.us_statuses = []
|
self.us_statuses = []
|
||||||
for us_status in project.us_statuses.all():
|
for us_status in project.us_statuses.all():
|
||||||
self.us_statuses.append({
|
self.us_statuses.append({
|
||||||
|
@ -576,17 +590,19 @@ class ProjectTemplate(models.Model):
|
||||||
|
|
||||||
self.roles = []
|
self.roles = []
|
||||||
for role in project.roles.all():
|
for role in project.roles.all():
|
||||||
permissions = [p.codename for p in role.permissions.all()]
|
|
||||||
self.roles.append({
|
self.roles.append({
|
||||||
"name": role.name,
|
"name": role.name,
|
||||||
"slug": role.slug,
|
"slug": role.slug,
|
||||||
"permissions": permissions,
|
"permissions": role.permissions,
|
||||||
"order": role.order,
|
"order": role.order,
|
||||||
"computable": role.computable
|
"computable": role.computable
|
||||||
})
|
})
|
||||||
|
|
||||||
|
try:
|
||||||
owner_membership = Membership.objects.get(project=project, user=project.owner)
|
owner_membership = Membership.objects.get(project=project, user=project.owner)
|
||||||
self.default_owner_role = owner_membership.role.slug
|
self.default_owner_role = owner_membership.role.slug
|
||||||
|
except Membership.DoesNotExist:
|
||||||
|
self.default_owner_role = self.roles[0].get("slug", None)
|
||||||
|
|
||||||
def apply_to_project(self, project):
|
def apply_to_project(self, project):
|
||||||
if project.id is None:
|
if project.id is None:
|
||||||
|
@ -661,16 +677,14 @@ class ProjectTemplate(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
for role in self.roles:
|
for role in self.roles:
|
||||||
newRoleInstance = Role.objects.create(
|
Role.objects.create(
|
||||||
name=role["name"],
|
name=role["name"],
|
||||||
slug=role["slug"],
|
slug=role["slug"],
|
||||||
order=role["order"],
|
order=role["order"],
|
||||||
computable=role["computable"],
|
computable=role["computable"],
|
||||||
project=project
|
project=project,
|
||||||
|
permissions=role['permissions']
|
||||||
)
|
)
|
||||||
permissions = [Permission.objects.get(codename=codename) for codename in role["permissions"]]
|
|
||||||
for permission in permissions:
|
|
||||||
newRoleInstance.permissions.add(permission)
|
|
||||||
|
|
||||||
if self.points:
|
if self.points:
|
||||||
project.default_points = Points.objects.get(name=self.default_options["points"],
|
project.default_points = Points.objects.get(name=self.default_options["points"],
|
||||||
|
@ -696,7 +710,6 @@ class ProjectTemplate(models.Model):
|
||||||
if self.severities:
|
if self.severities:
|
||||||
project.default_severity = Severity.objects.get(name=self.default_options["severity"], project=project)
|
project.default_severity = Severity.objects.get(name=self.default_options["severity"], project=project)
|
||||||
|
|
||||||
|
|
||||||
if self.default_owner_role:
|
if self.default_owner_role:
|
||||||
# FIXME: is operation should to be on template apply method?
|
# FIXME: is operation should to be on template apply method?
|
||||||
Membership.objects.create(user=project.owner,
|
Membership.objects.create(user=project.owner,
|
||||||
|
|
|
@ -14,129 +14,117 @@
|
||||||
# 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.permissions import BasePermission
|
from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
|
||||||
|
IsAuthenticated, IsProjectOwner,
|
||||||
|
AllowAny, IsSuperUser)
|
||||||
|
|
||||||
|
|
||||||
class ProjectPermission(BasePermission):
|
class ProjectPermission(ResourcePermission):
|
||||||
get_permission = "view_project"
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
post_permission = None
|
create_perms = IsAuthenticated()
|
||||||
put_permission = "change_project"
|
update_perms = IsProjectOwner()
|
||||||
patch_permission = "change_project"
|
destroy_perms = IsProjectOwner()
|
||||||
delete_permission = None
|
list_perms = AllowAny()
|
||||||
safe_methods = ["HEAD", "OPTIONS"]
|
stats_perms = AllowAny()
|
||||||
path_to_project = []
|
star_perms = IsAuthenticated()
|
||||||
|
unstar_perms = IsAuthenticated()
|
||||||
|
issues_stats_perms = AllowAny()
|
||||||
|
issues_filters_data_perms = AllowAny()
|
||||||
|
tags_perms = AllowAny()
|
||||||
|
fans_perms = HasProjectPerm('view_project')
|
||||||
|
create_template_perms = IsSuperUser()
|
||||||
|
|
||||||
|
|
||||||
class ProjectAdminPermission(BasePermission):
|
class MembershipPermission(ResourcePermission):
|
||||||
def has_permission(self, request, view):
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
if request.method in self.safe_methods:
|
create_perms = IsProjectOwner()
|
||||||
return True
|
update_perms = IsProjectOwner()
|
||||||
return super().has_permission(request, view)
|
destroy_perms = IsProjectOwner()
|
||||||
|
list_perms = AllowAny()
|
||||||
def has_object_permission(self, request, view, obj):
|
|
||||||
if request.method in self.safe_methods:
|
|
||||||
return True
|
|
||||||
return super().has_object_permission(request, view, obj)
|
|
||||||
|
|
||||||
|
|
||||||
class MembershipPermission(BasePermission):
|
|
||||||
get_permission = "view_membership"
|
|
||||||
post_permission = "add_membership"
|
|
||||||
put_permission = "change_membership"
|
|
||||||
patch_permission = "change_membership"
|
|
||||||
delete_permission = "delete_membership"
|
|
||||||
safe_methods = ["HEAD", "OPTIONS"]
|
|
||||||
path_to_project = ["project"]
|
|
||||||
|
|
||||||
|
|
||||||
# User Stories
|
# User Stories
|
||||||
|
|
||||||
class PointsPermission(BasePermission):
|
class PointsPermission(ResourcePermission):
|
||||||
get_permission = "view_points"
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
post_permission = "add_points"
|
create_perms = IsProjectOwner()
|
||||||
put_permission = "change_points"
|
update_perms = IsProjectOwner()
|
||||||
patch_permission = "change_points"
|
destroy_perms = IsProjectOwner()
|
||||||
delete_permission = "delete_points"
|
list_perms = AllowAny()
|
||||||
safe_methods = ["HEAD", "OPTIONS"]
|
bulk_update_order_perms = IsProjectOwner()
|
||||||
path_to_project = ["project"]
|
|
||||||
|
|
||||||
|
|
||||||
class UserStoryStatusPermission(BasePermission):
|
class UserStoryStatusPermission(ResourcePermission):
|
||||||
get_permission = "view_userstorystatus"
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
post_permission = "add_userstorystatus"
|
create_perms = IsProjectOwner()
|
||||||
put_permission = "change_userstorystatus"
|
update_perms = IsProjectOwner()
|
||||||
patch_permission = "change_userstorystatus"
|
destroy_perms = IsProjectOwner()
|
||||||
delete_permission = "delete_userstorystatus"
|
list_perms = AllowAny()
|
||||||
safe_methods = ["HEAD", "OPTIONS"]
|
bulk_update_order_perms = IsProjectOwner()
|
||||||
path_to_project = ["project"]
|
|
||||||
|
|
||||||
|
|
||||||
# Tasks
|
# Tasks
|
||||||
|
|
||||||
class TaskStatusPermission(BasePermission):
|
class TaskStatusPermission(ResourcePermission):
|
||||||
get_permission = "view_taskstatus"
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
post_permission = "ade_taskstatus"
|
create_perms = IsProjectOwner()
|
||||||
put_permission = "change_taskstatus"
|
update_perms = IsProjectOwner()
|
||||||
patch_permission = "change_taskstatus"
|
destroy_perms = IsProjectOwner()
|
||||||
delete_permission = "delete_taskstatus"
|
list_perms = AllowAny()
|
||||||
safe_methods = ["HEAD", "OPTIONS"]
|
bulk_update_order_perms = IsProjectOwner()
|
||||||
path_to_project = ["project"]
|
|
||||||
|
|
||||||
|
|
||||||
# Issues
|
# Issues
|
||||||
|
|
||||||
class SeverityPermission(BasePermission):
|
class SeverityPermission(ResourcePermission):
|
||||||
get_permission = "view_severity"
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
post_permission = "add_severity"
|
create_perms = IsProjectOwner()
|
||||||
put_permission = "change_severity"
|
update_perms = IsProjectOwner()
|
||||||
patch_permission = "change_severity"
|
destroy_perms = IsProjectOwner()
|
||||||
delete_permission = "delete_severity"
|
list_perms = AllowAny()
|
||||||
safe_methods = ["HEAD", "OPTIONS"]
|
bulk_update_order_perms = IsProjectOwner()
|
||||||
path_to_project = ["project"]
|
|
||||||
|
|
||||||
|
|
||||||
class PriorityPermission(BasePermission):
|
class PriorityPermission(ResourcePermission):
|
||||||
get_permission = "view_priority"
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
post_permission = "add_priority"
|
create_perms = IsProjectOwner()
|
||||||
put_permission = "change_priority"
|
update_perms = IsProjectOwner()
|
||||||
patch_permission = "change_priority"
|
destroy_perms = IsProjectOwner()
|
||||||
delete_permission = "delete_priority"
|
list_perms = AllowAny()
|
||||||
safe_methods = ["HEAD", "OPTIONS"]
|
bulk_update_order_perms = IsProjectOwner()
|
||||||
path_to_project = ["project"]
|
|
||||||
|
|
||||||
|
|
||||||
class IssueStatusPermission(BasePermission):
|
class IssueStatusPermission(ResourcePermission):
|
||||||
get_permission = "view_issuestatus"
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
post_permission = "add_issuestatus"
|
create_perms = IsProjectOwner()
|
||||||
put_permission = "change_issuestatus"
|
update_perms = IsProjectOwner()
|
||||||
patch_permission = "change_issuestatus"
|
destroy_perms = IsProjectOwner()
|
||||||
delete_permission = "delete_issuestatus"
|
list_perms = AllowAny()
|
||||||
safe_methods = ["HEAD", "OPTIONS"]
|
bulk_update_order_perms = IsProjectOwner()
|
||||||
path_to_project = ["project"]
|
|
||||||
|
|
||||||
|
|
||||||
class IssueTypePermission(BasePermission):
|
class IssueTypePermission(ResourcePermission):
|
||||||
get_permission = "view_issuetype"
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
post_permission = "add_issuetype"
|
create_perms = IsProjectOwner()
|
||||||
put_permission = "change_issuetype"
|
update_perms = IsProjectOwner()
|
||||||
patch_permission = "change_issuetype"
|
destroy_perms = IsProjectOwner()
|
||||||
delete_permission = "delete_issuetype"
|
list_perms = AllowAny()
|
||||||
safe_methods = ["HEAD", "OPTIONS"]
|
bulk_update_order_perms = IsProjectOwner()
|
||||||
path_to_project = ["project"]
|
|
||||||
|
|
||||||
|
|
||||||
class RolesPermission(BasePermission):
|
class RolesPermission(ResourcePermission):
|
||||||
get_permission = "view_role"
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
post_permission = "add_role"
|
create_perms = IsProjectOwner()
|
||||||
put_permission = "change_role"
|
update_perms = IsProjectOwner()
|
||||||
patch_permission = "change_role"
|
destroy_perms = IsProjectOwner()
|
||||||
delete_permission = "delete_role"
|
list_perms = AllowAny()
|
||||||
safe_methods = ["HEAD", "OPTIONS"]
|
|
||||||
path_to_project = ["project"]
|
|
||||||
|
|
||||||
|
|
||||||
# Project Templates
|
# Project Templates
|
||||||
|
|
||||||
class ProjectTemplatePermission(BasePermission):
|
class ProjectTemplatePermission(ResourcePermission):
|
||||||
# TODO: should be improved in permissions refactor
|
retrieve_perms = AllowAny()
|
||||||
pass
|
create_perms = IsSuperUser()
|
||||||
|
update_perms = IsSuperUser()
|
||||||
|
destroy_perms = IsSuperUser()
|
||||||
|
list_perms = AllowAny()
|
||||||
|
|
|
@ -18,15 +18,17 @@ from django.db.models.loading import get_model
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import viewsets
|
|
||||||
from rest_framework.permissions import IsAuthenticated
|
|
||||||
|
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
|
from taiga.base.api import viewsets
|
||||||
from .serializers import ResolverSerializer
|
from .serializers import ResolverSerializer
|
||||||
|
from taiga.permissions.service import user_has_perm
|
||||||
|
|
||||||
|
from . import permissions
|
||||||
|
|
||||||
|
|
||||||
class ResolverViewSet(viewsets.ViewSet):
|
class ResolverViewSet(viewsets.ViewSet):
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (permissions.ResolverPermission,)
|
||||||
|
|
||||||
def list(self, request, **kwargs):
|
def list(self, request, **kwargs):
|
||||||
serializer = ResolverSerializer(data=request.QUERY_PARAMS)
|
serializer = ResolverSerializer(data=request.QUERY_PARAMS)
|
||||||
|
@ -38,17 +40,19 @@ class ResolverViewSet(viewsets.ViewSet):
|
||||||
project_model = get_model("projects", "Project")
|
project_model = get_model("projects", "Project")
|
||||||
project = get_object_or_404(project_model, slug=data["project"])
|
project = get_object_or_404(project_model, slug=data["project"])
|
||||||
|
|
||||||
|
self.check_permissions(request, "list", project)
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"project": project.pk
|
"project": project.pk
|
||||||
}
|
}
|
||||||
|
|
||||||
if data["us"]:
|
if data["us"] and user_has_perm(request.user, "view_us", project):
|
||||||
result["us"] = get_object_or_404(project.user_stories.all(), ref=data["us"]).pk
|
result["us"] = get_object_or_404(project.user_stories.all(), ref=data["us"]).pk
|
||||||
if data["task"]:
|
if data["task"] and user_has_perm(request.user, "view_tasks", project):
|
||||||
result["task"] = get_object_or_404(project.tasks.all(), ref=data["task"]).pk
|
result["task"] = get_object_or_404(project.tasks.all(), ref=data["task"]).pk
|
||||||
if data["issue"]:
|
if data["issue"] and user_has_perm(request.user, "view_issues", project):
|
||||||
result["issue"] = get_object_or_404(project.issues.all(), ref=data["issue"]).pk
|
result["issue"] = get_object_or_404(project.issues.all(), ref=data["issue"]).pk
|
||||||
if data["milestone"]:
|
if data["milestone"] and user_has_perm(request.user, "view_milestones", project):
|
||||||
result["milestone"] = get_object_or_404(project.milestones.all(), slug=data["milestone"]).pk
|
result["milestone"] = get_object_or_404(project.milestones.all(), slug=data["milestone"]).pk
|
||||||
|
|
||||||
return Response(result)
|
return Response(result)
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
||||||
|
# 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 taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
|
||||||
|
IsProjectOwner, AllowAny)
|
||||||
|
|
||||||
|
|
||||||
|
class ResolverPermission(ResourcePermission):
|
||||||
|
list_perms = HasProjectPerm('view_project')
|
|
@ -18,10 +18,10 @@ from os import path
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from taiga.base.serializers import PickleField, JsonField
|
from taiga.base.serializers import JsonField, PgArrayField
|
||||||
from taiga.users.serializers import UserSerializer
|
|
||||||
from taiga.users.models import Role, User
|
from taiga.users.models import Role, User
|
||||||
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 . import models
|
from . import models
|
||||||
|
|
||||||
|
@ -131,6 +131,9 @@ class ProjectMembershipSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class ProjectSerializer(serializers.ModelSerializer):
|
class ProjectSerializer(serializers.ModelSerializer):
|
||||||
|
tags = PgArrayField(required=False)
|
||||||
|
anon_permissions = PgArrayField(required=False)
|
||||||
|
public_permissions = PgArrayField(required=False)
|
||||||
stars = serializers.SerializerMethodField("get_stars_number")
|
stars = serializers.SerializerMethodField("get_stars_number")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -143,10 +146,16 @@ class ProjectSerializer(serializers.ModelSerializer):
|
||||||
return getattr(obj, "stars_count", 0)
|
return getattr(obj, "stars_count", 0)
|
||||||
|
|
||||||
def validate_slug(self, attrs, source):
|
def validate_slug(self, attrs, source):
|
||||||
|
if self.object:
|
||||||
|
project_with_slug = models.Project.objects.filter(slug=attrs[source]).exclude(pk=self.object.pk)
|
||||||
|
else:
|
||||||
project_with_slug = models.Project.objects.filter(slug=attrs[source])
|
project_with_slug = models.Project.objects.filter(slug=attrs[source])
|
||||||
|
|
||||||
if source == "slug" and project_with_slug.exists():
|
if source == "slug" and project_with_slug.exists():
|
||||||
raise serializers.ValidationError(_("Slug duplicated for the project"))
|
raise serializers.ValidationError(_("Slug duplicated for the project"))
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
class ProjectDetailSerializer(ProjectSerializer):
|
class ProjectDetailSerializer(ProjectSerializer):
|
||||||
roles = serializers.SerializerMethodField("get_list_of_roles")
|
roles = serializers.SerializerMethodField("get_list_of_roles")
|
||||||
|
@ -187,6 +196,8 @@ class ProjectRoleSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class RoleSerializer(serializers.ModelSerializer):
|
class RoleSerializer(serializers.ModelSerializer):
|
||||||
|
permissions = PgArrayField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Role
|
model = Role
|
||||||
fields = ('id', 'name', 'permissions', 'computable', 'project', 'order')
|
fields = ('id', 'name', 'permissions', 'computable', 'project', 'order')
|
||||||
|
|
|
@ -31,6 +31,7 @@ def bulk_update_userstory_status_order(project, user, data):
|
||||||
for id, order in data:
|
for id, order in data:
|
||||||
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
||||||
(order, id, project.id))
|
(order, id, project.id))
|
||||||
|
cursor.execute("DEALLOCATE bulk_update_order")
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,6 +49,7 @@ def bulk_update_points_order(project, user, data):
|
||||||
for id, order in data:
|
for id, order in data:
|
||||||
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
||||||
(order, id, project.id))
|
(order, id, project.id))
|
||||||
|
cursor.execute("DEALLOCATE bulk_update_order")
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,6 +67,7 @@ def bulk_update_task_status_order(project, user, data):
|
||||||
for id, order in data:
|
for id, order in data:
|
||||||
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
||||||
(order, id, project.id))
|
(order, id, project.id))
|
||||||
|
cursor.execute("DEALLOCATE bulk_update_order")
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,6 +85,7 @@ def bulk_update_issue_status_order(project, user, data):
|
||||||
for id, order in data:
|
for id, order in data:
|
||||||
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
||||||
(order, id, project.id))
|
(order, id, project.id))
|
||||||
|
cursor.execute("DEALLOCATE bulk_update_order")
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,6 +103,7 @@ def bulk_update_issue_type_order(project, user, data):
|
||||||
for id, order in data:
|
for id, order in data:
|
||||||
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
||||||
(order, id, project.id))
|
(order, id, project.id))
|
||||||
|
cursor.execute("DEALLOCATE bulk_update_order")
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,6 +121,7 @@ def bulk_update_priority_order(project, user, data):
|
||||||
for id, order in data:
|
for id, order in data:
|
||||||
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
||||||
(order, id, project.id))
|
(order, id, project.id))
|
||||||
|
cursor.execute("DEALLOCATE bulk_update_order")
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
|
@ -133,4 +139,5 @@ def bulk_update_severity_order(project, user, data):
|
||||||
for id, order in data:
|
for id, order in data:
|
||||||
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
||||||
(order, id, project.id))
|
(order, id, project.id))
|
||||||
|
cursor.execute("DEALLOCATE bulk_update_order")
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
|
@ -27,12 +27,19 @@ def _get_milestones_stats_for_backlog(project):
|
||||||
current_evolution = 0
|
current_evolution = 0
|
||||||
current_team_increment = 0
|
current_team_increment = 0
|
||||||
current_client_increment = 0
|
current_client_increment = 0
|
||||||
optimal_points_per_sprint = project.total_story_points / (project.total_milestones)
|
|
||||||
|
optimal_points_per_sprint = 0
|
||||||
|
if project.total_story_points and project.total_milestones:
|
||||||
|
optimal_points_per_sprint = project.total_story_points / project.total_milestones
|
||||||
|
|
||||||
future_team_increment = sum(project.future_team_increment.values())
|
future_team_increment = sum(project.future_team_increment.values())
|
||||||
future_client_increment = sum(project.future_client_increment.values())
|
future_client_increment = sum(project.future_client_increment.values())
|
||||||
|
|
||||||
milestones = project.milestones.order_by('estimated_start')
|
milestones = project.milestones.order_by('estimated_start')
|
||||||
|
|
||||||
|
optimal_points = 0
|
||||||
|
team_increment = 0
|
||||||
|
client_increment = 0
|
||||||
for current_milestone in range(0, max(milestones.count(), project.total_milestones)):
|
for current_milestone in range(0, max(milestones.count(), project.total_milestones)):
|
||||||
optimal_points = (project.total_story_points -
|
optimal_points = (project.total_story_points -
|
||||||
(optimal_points_per_sprint * current_milestone))
|
(optimal_points_per_sprint * current_milestone))
|
||||||
|
@ -65,7 +72,7 @@ def _get_milestones_stats_for_backlog(project):
|
||||||
|
|
||||||
optimal_points -= optimal_points_per_sprint
|
optimal_points -= optimal_points_per_sprint
|
||||||
evolution = (project.total_story_points - current_evolution
|
evolution = (project.total_story_points - current_evolution
|
||||||
if current_evolution is not None else None)
|
if current_evolution is not None and project.total_story_points else None)
|
||||||
yield {
|
yield {
|
||||||
'name': 'Project End',
|
'name': 'Project End',
|
||||||
'optimal': optimal_points,
|
'optimal': optimal_points,
|
||||||
|
|
|
@ -44,8 +44,8 @@ class TaskViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
||||||
model = models.Task
|
model = models.Task
|
||||||
serializer_class = serializers.TaskNeighborsSerializer
|
serializer_class = serializers.TaskNeighborsSerializer
|
||||||
list_serializer_class = serializers.TaskSerializer
|
list_serializer_class = serializers.TaskSerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.TaskPermission)
|
permission_classes = (permissions.TaskPermission,)
|
||||||
filter_backends = (filters.IsProjectMemberFilterBackend,)
|
filter_backends = (filters.CanViewTasksFilterBackend,)
|
||||||
filter_fields = ["user_story", "milestone", "project"]
|
filter_fields = ["user_story", "milestone", "project"]
|
||||||
|
|
||||||
def pre_save(self, obj):
|
def pre_save(self, obj):
|
||||||
|
@ -58,10 +58,6 @@ class TaskViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
||||||
def pre_conditions_on_save(self, obj):
|
def pre_conditions_on_save(self, obj):
|
||||||
super().pre_conditions_on_save(obj)
|
super().pre_conditions_on_save(obj)
|
||||||
|
|
||||||
if (obj.project.owner != self.request.user and
|
|
||||||
obj.project.memberships.filter(user=self.request.user).count() == 0):
|
|
||||||
raise exc.PermissionDenied(_("You don't have permissions for add/modify this task."))
|
|
||||||
|
|
||||||
if obj.milestone and obj.milestone.project != obj.project:
|
if obj.milestone and obj.milestone.project != obj.project:
|
||||||
raise exc.PermissionDenied(_("You don't have permissions for add/modify this task."))
|
raise exc.PermissionDenied(_("You don't have permissions for add/modify this task."))
|
||||||
|
|
||||||
|
@ -88,8 +84,7 @@ class TaskViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
||||||
project = get_object_or_404(Project, id=project_id)
|
project = get_object_or_404(Project, id=project_id)
|
||||||
user_story = get_object_or_404(UserStory, id=us_id)
|
user_story = get_object_or_404(UserStory, id=us_id)
|
||||||
|
|
||||||
if request.user != project.owner and not has_project_perm(request.user, project, 'add_task'):
|
self.check_permissions(request, 'bulk_create', project)
|
||||||
raise exc.PermissionDenied(_("You don't have permisions to create tasks."))
|
|
||||||
|
|
||||||
tasks = services.create_tasks_in_bulk(bulk_tasks, callback=self.post_save, project=project,
|
tasks = services.create_tasks_in_bulk(bulk_tasks, callback=self.post_save, project=project,
|
||||||
user_story=user_story, owner=request.user,
|
user_story=user_story, owner=request.user,
|
||||||
|
|
|
@ -14,14 +14,16 @@
|
||||||
# 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.permissions import BasePermission
|
from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
|
||||||
|
IsProjectOwner, AllowAny)
|
||||||
|
|
||||||
|
|
||||||
class TaskPermission(BasePermission):
|
class TaskPermission(ResourcePermission):
|
||||||
get_permission = "view_task"
|
enought_perms = IsProjectOwner()
|
||||||
post_permission = "add_task"
|
global_perms = None
|
||||||
put_permission = "change_task"
|
retrieve_perms = HasProjectPerm('view_tasks')
|
||||||
patch_permission = "change_task"
|
create_perms = HasProjectPerm('add_task')
|
||||||
delete_permission = "delete_task"
|
update_perms = HasProjectPerm('modify_task')
|
||||||
safe_methods = ["HEAD", "OPTIONS"]
|
destroy_perms = HasProjectPerm('delete_task')
|
||||||
path_to_project = ["project"]
|
list_perms = AllowAny()
|
||||||
|
bulk_create_perms = HasProjectPerm('add_task')
|
||||||
|
|
|
@ -46,9 +46,9 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
|
||||||
model = models.UserStory
|
model = models.UserStory
|
||||||
serializer_class = serializers.UserStoryNeighborsSerializer
|
serializer_class = serializers.UserStoryNeighborsSerializer
|
||||||
list_serializer_class = serializers.UserStorySerializer
|
list_serializer_class = serializers.UserStorySerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.UserStoryPermission)
|
permission_classes = (permissions.UserStoryPermission,)
|
||||||
|
|
||||||
filter_backends = (filters.IsProjectMemberFilterBackend, filters.TagsFilter)
|
filter_backends = (filters.CanViewUsFilterBackend, filters.TagsFilter)
|
||||||
retrieve_exclude_filters = (filters.TagsFilter,)
|
retrieve_exclude_filters = (filters.TagsFilter,)
|
||||||
filter_fields = ['project', 'milestone', 'milestone__isnull', 'status', 'is_archived']
|
filter_fields = ['project', 'milestone', 'milestone__isnull', 'status', 'is_archived']
|
||||||
|
|
||||||
|
@ -76,8 +76,7 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
|
||||||
|
|
||||||
project = get_object_or_404(Project, id=project_id)
|
project = get_object_or_404(Project, id=project_id)
|
||||||
|
|
||||||
if request.user != project.owner and not has_project_perm(request.user, project, 'add_userstory'):
|
self.check_permissions(request, 'bulk_create', project)
|
||||||
raise exc.PermissionDenied(_("You don't have permisions to create user stories."))
|
|
||||||
|
|
||||||
user_stories = services.create_userstories_in_bulk(
|
user_stories = services.create_userstories_in_bulk(
|
||||||
bulk_stories, callback=self.post_save, project=project, owner=request.user)
|
bulk_stories, callback=self.post_save, project=project, owner=request.user)
|
||||||
|
@ -103,8 +102,7 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
|
||||||
|
|
||||||
project = get_object_or_404(Project, id=project_id)
|
project = get_object_or_404(Project, id=project_id)
|
||||||
|
|
||||||
if request.user != project.owner and not has_project_perm(request.user, project, 'change_userstory'):
|
self.check_permissions(request, 'bulk_update_order', project)
|
||||||
raise exc.PermissionDenied(_("You don't have permisions to create user stories."))
|
|
||||||
|
|
||||||
services.update_userstories_order_in_bulk(bulk_stories)
|
services.update_userstories_order_in_bulk(bulk_stories)
|
||||||
|
|
||||||
|
@ -134,16 +132,3 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
|
||||||
obj.owner = self.request.user
|
obj.owner = self.request.user
|
||||||
|
|
||||||
super().pre_save(obj)
|
super().pre_save(obj)
|
||||||
|
|
||||||
def pre_conditions_on_save(self, obj):
|
|
||||||
super().pre_conditions_on_save(obj)
|
|
||||||
|
|
||||||
if (obj.project.owner != self.request.user and
|
|
||||||
obj.project.memberships.filter(user=self.request.user).count() == 0):
|
|
||||||
raise exc.PermissionDenied(_("You don't have permissions for add/modify this user story"))
|
|
||||||
|
|
||||||
if obj.milestone and obj.milestone.project != obj.project:
|
|
||||||
raise exc.PermissionDenied(_("You don't have permissions for add/modify this user story"))
|
|
||||||
|
|
||||||
if obj.status and obj.status.project != obj.project:
|
|
||||||
raise exc.PermissionDenied(_("You don't have permissions for add/modify this user story"))
|
|
||||||
|
|
|
@ -14,14 +14,16 @@
|
||||||
# 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.permissions import BasePermission
|
from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
|
||||||
|
IsAuthenticated, IsProjectOwner,
|
||||||
|
AllowAny, IsSuperUser)
|
||||||
|
|
||||||
|
|
||||||
class UserStoryPermission(BasePermission):
|
class UserStoryPermission(ResourcePermission):
|
||||||
get_permission = "view_userstory"
|
retrieve_perms = HasProjectPerm('view_us')
|
||||||
post_permission = "add_userstory"
|
create_perms = HasProjectPerm('add_us_to_project') | HasProjectPerm('add_us')
|
||||||
put_permission = "change_userstory"
|
update_perms = HasProjectPerm('modify_us')
|
||||||
patch_permission = "change_userstory"
|
destroy_perms = HasProjectPerm('delete_us')
|
||||||
delete_permission = "delete_userstory"
|
list_perms = AllowAny()
|
||||||
safe_methods = ["HEAD", "OPTIONS"]
|
bulk_create_perms = IsAuthenticated() & (HasProjectPerm('add_us_to_project') | HasProjectPerm('add_us'))
|
||||||
path_to_project = ["project"]
|
bulk_update_order_perms = HasProjectPerm('modify_us')
|
||||||
|
|
|
@ -55,7 +55,6 @@ class UserStorySerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
points_modelcls = get_model("projects", "Points")
|
points_modelcls = get_model("projects", "Points")
|
||||||
|
|
||||||
obj.project.update_role_points()
|
|
||||||
if role_points:
|
if role_points:
|
||||||
for role_id, points_id in role_points.items():
|
for role_id, points_id in role_points.items():
|
||||||
role_points = obj.role_points.get(role__id=role_id)
|
role_points = obj.role_points.get(role__id=role_id)
|
||||||
|
|
|
@ -32,6 +32,12 @@ class Votes(models.Model):
|
||||||
verbose_name_plural = _("Votes")
|
verbose_name_plural = _("Votes")
|
||||||
unique_together = ("content_type", "object_id")
|
unique_together = ("content_type", "object_id")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def project(self):
|
||||||
|
if hasattr(self.content_object, 'project'):
|
||||||
|
return self.content_object.project
|
||||||
|
return None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.count
|
return self.count
|
||||||
|
|
||||||
|
@ -48,5 +54,11 @@ class Vote(models.Model):
|
||||||
verbose_name_plural = _("Votes")
|
verbose_name_plural = _("Votes")
|
||||||
unique_together = ("content_type", "object_id", "user")
|
unique_together = ("content_type", "object_id", "user")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def project(self):
|
||||||
|
if hasattr(self.content_object, 'project'):
|
||||||
|
return self.content_object.project
|
||||||
|
return None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.user
|
return self.user
|
||||||
|
|
|
@ -34,7 +34,7 @@ def add_vote(obj, user):
|
||||||
"""
|
"""
|
||||||
obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(obj)
|
obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(obj)
|
||||||
with atomic():
|
with atomic():
|
||||||
_, created = Vote.objects.get_or_create(content_type=obj_type, object_id=obj.id, user=user)
|
vote, created = Vote.objects.get_or_create(content_type=obj_type, object_id=obj.id, user=user)
|
||||||
|
|
||||||
if not created:
|
if not created:
|
||||||
return
|
return
|
||||||
|
@ -42,6 +42,7 @@ def add_vote(obj, user):
|
||||||
votes, _ = Votes.objects.get_or_create(content_type=obj_type, object_id=obj.id)
|
votes, _ = Votes.objects.get_or_create(content_type=obj_type, object_id=obj.id)
|
||||||
votes.count = F('count') + 1
|
votes.count = F('count') + 1
|
||||||
votes.save()
|
votes.save()
|
||||||
|
return vote
|
||||||
|
|
||||||
|
|
||||||
def remove_vote(obj, user):
|
def remove_vote(obj, user):
|
||||||
|
@ -74,7 +75,6 @@ def get_voters(obj):
|
||||||
:return: User queryset object representing the users that voted the object.
|
:return: User queryset object representing the users that voted the object.
|
||||||
"""
|
"""
|
||||||
obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(obj)
|
obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(obj)
|
||||||
|
|
||||||
return get_user_model().objects.filter(votes__content_type=obj_type, votes__object_id=obj.id)
|
return get_user_model().objects.filter(votes__content_type=obj_type, votes__object_id=obj.id)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -41,8 +41,8 @@ from . import serializers
|
||||||
class WikiViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
|
class WikiViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
|
||||||
model = models.WikiPage
|
model = models.WikiPage
|
||||||
serializer_class = serializers.WikiPageSerializer
|
serializer_class = serializers.WikiPageSerializer
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (permissions.WikiPagePermission,)
|
||||||
filter_backends = (filters.IsProjectMemberFilterBackend,)
|
filter_backends = (filters.CanViewWikiPagesFilterBackend,)
|
||||||
filter_fields = ("project", "slug")
|
filter_fields = ("project", "slug")
|
||||||
|
|
||||||
@list_route(methods=["POST"])
|
@list_route(methods=["POST"])
|
||||||
|
@ -57,16 +57,12 @@ class WikiViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
||||||
raise exc.WrongArguments({"project_id": "No project_id parameter"})
|
raise exc.WrongArguments({"project_id": "No project_id parameter"})
|
||||||
|
|
||||||
project = get_object_or_404(Project, pk=project_id)
|
project = get_object_or_404(Project, pk=project_id)
|
||||||
|
|
||||||
|
self.check_permissions(request, "render", project)
|
||||||
|
|
||||||
data = mdrender(project, content)
|
data = mdrender(project, content)
|
||||||
return Response({"data": data})
|
return Response({"data": data})
|
||||||
|
|
||||||
def pre_conditions_on_save(self, obj):
|
|
||||||
super().pre_conditions_on_save(obj)
|
|
||||||
|
|
||||||
if (obj.project.owner != self.request.user and
|
|
||||||
obj.project.memberships.filter(user=self.request.user).count() == 0):
|
|
||||||
raise exc.PermissionDenied(_("You don't haver permissions for add/modify "
|
|
||||||
"this wiki page."))
|
|
||||||
def pre_save(self, obj):
|
def pre_save(self, obj):
|
||||||
if not obj.owner:
|
if not obj.owner:
|
||||||
obj.owner = self.request.user
|
obj.owner = self.request.user
|
||||||
|
@ -77,6 +73,6 @@ class WikiViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
||||||
class WikiLinkViewSet(ModelCrudViewSet):
|
class WikiLinkViewSet(ModelCrudViewSet):
|
||||||
model = models.WikiLink
|
model = models.WikiLink
|
||||||
serializer_class = serializers.WikiLinkSerializer
|
serializer_class = serializers.WikiLinkSerializer
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (permissions.WikiLinkPermission,)
|
||||||
filter_backends = (filters.IsProjectMemberFilterBackend,)
|
filter_backends = (filters.CanViewWikiPagesFilterBackend,)
|
||||||
filter_fields = ["project"]
|
filter_fields = ["project"]
|
||||||
|
|
|
@ -14,14 +14,25 @@
|
||||||
# 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.permissions import BasePermission
|
from taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
|
||||||
|
IsProjectOwner, AllowAny)
|
||||||
|
|
||||||
|
|
||||||
class WikiPagePermission(BasePermission):
|
class WikiPagePermission(ResourcePermission):
|
||||||
get_permission = "view_wikipage"
|
enought_perms = IsProjectOwner()
|
||||||
post_permission = "add_wikipage"
|
global_perms = None
|
||||||
put_permission = "change_wikipage"
|
retrieve_perms = HasProjectPerm('view_wiki_pages')
|
||||||
patch_permission = "change_wikipage"
|
create_perms = HasProjectPerm('add_wiki_page')
|
||||||
delete_permission = "delete_wikipage"
|
update_perms = HasProjectPerm('modify_wiki_page')
|
||||||
safe_methods = ["HEAD", "OPTIONS"]
|
destroy_perms = HasProjectPerm('delete_wiki_page')
|
||||||
path_to_project = ["project"]
|
list_perms = AllowAny()
|
||||||
|
render_perms = AllowAny()
|
||||||
|
|
||||||
|
class WikiLinkPermission(ResourcePermission):
|
||||||
|
enought_perms = IsProjectOwner()
|
||||||
|
global_perms = None
|
||||||
|
retrieve_perms = HasProjectPerm('view_wiki_links')
|
||||||
|
create_perms = HasProjectPerm('add_wiki_link')
|
||||||
|
update_perms = HasProjectPerm('modify_wiki_link')
|
||||||
|
destroy_perms = HasProjectPerm('delete_wiki_link')
|
||||||
|
list_perms = AllowAny()
|
||||||
|
|
|
@ -44,10 +44,6 @@ from taiga.searches.api import SearchViewSet
|
||||||
router.register(r"search", SearchViewSet, base_name="search")
|
router.register(r"search", SearchViewSet, base_name="search")
|
||||||
|
|
||||||
|
|
||||||
from taiga.projects.api import ProjectAdminViewSet
|
|
||||||
router.register(r"site-projects", ProjectAdminViewSet, base_name="site-projects")
|
|
||||||
|
|
||||||
|
|
||||||
# Projects & Types
|
# Projects & Types
|
||||||
from taiga.projects.api import RolesViewSet
|
from taiga.projects.api import RolesViewSet
|
||||||
from taiga.projects.api import ProjectViewSet
|
from taiga.projects.api import ProjectViewSet
|
||||||
|
@ -61,13 +57,9 @@ from taiga.projects.api import IssueTypeViewSet
|
||||||
from taiga.projects.api import PriorityViewSet
|
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
|
||||||
from taiga.projects.api import FansViewSet
|
|
||||||
from taiga.projects.api import StarredViewSet
|
|
||||||
|
|
||||||
router.register(r"roles", RolesViewSet, base_name="roles")
|
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"projects/(?P<project_id>\d+)/fans", FansViewSet, base_name="project-fans")
|
|
||||||
router.register(r"users/(?P<user_id>\d+)/starred", StarredViewSet, base_name="user-starred")
|
|
||||||
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")
|
||||||
router.register(r"invitations", InvitationViewSet, base_name="invitations")
|
router.register(r"invitations", InvitationViewSet, base_name="invitations")
|
||||||
|
|
|
@ -24,6 +24,7 @@ from taiga.projects.userstories.serializers import UserStorySerializer
|
||||||
from taiga.projects.tasks.serializers import TaskSerializer
|
from taiga.projects.tasks.serializers import TaskSerializer
|
||||||
from taiga.projects.issues.serializers import IssueSerializer
|
from taiga.projects.issues.serializers import IssueSerializer
|
||||||
from taiga.projects.wiki.serializers import WikiPageSerializer
|
from taiga.projects.wiki.serializers import WikiPageSerializer
|
||||||
|
from taiga.permissions.service import user_has_perm
|
||||||
|
|
||||||
from . import services
|
from . import services
|
||||||
|
|
||||||
|
@ -40,22 +41,22 @@ class SearchViewSet(viewsets.ViewSet):
|
||||||
except (project_model.DoesNotExist, TypeError):
|
except (project_model.DoesNotExist, TypeError):
|
||||||
raise excp.PermissionDenied({"detail": "Wrong project id"})
|
raise excp.PermissionDenied({"detail": "Wrong project id"})
|
||||||
|
|
||||||
result = {
|
result = {}
|
||||||
"userstories": self._search_user_stories(project, text),
|
if user_has_perm(request.user, "view_us", project):
|
||||||
"tasks": self._search_tasks(project, text),
|
result["userstories"] = self._search_user_stories(project, text)
|
||||||
"issues": self._search_issues(project, text),
|
if user_has_perm(request.user, "view_tasks", project):
|
||||||
"wikipages": self._search_wiki_pages(project, text)
|
result["tasks"] = self._search_tasks(project, text)
|
||||||
}
|
if user_has_perm(request.user, "view_issues", project):
|
||||||
|
result["issues"] = self._search_issues(project, text)
|
||||||
|
if user_has_perm(request.user, "view_wiki_pages", project):
|
||||||
|
result["wikipages"] = self._search_wiki_pages(project, text)
|
||||||
|
|
||||||
result["count"] = sum(map(lambda x: len(x), result.values()))
|
result["count"] = sum(map(lambda x: len(x), result.values()))
|
||||||
return Response(result)
|
return Response(result)
|
||||||
|
|
||||||
def _get_project(self, project_id):
|
def _get_project(self, project_id):
|
||||||
project_model = get_model("projects", "Project")
|
project_model = get_model("projects", "Project")
|
||||||
own_projects = (project_model.objects
|
return project_model.objects.get(pk=project_id)
|
||||||
.filter(members=self.request.user))
|
|
||||||
|
|
||||||
return own_projects.get(pk=project_id)
|
|
||||||
|
|
||||||
def _search_user_stories(self, project, text):
|
def _search_user_stories(self, project, text):
|
||||||
queryset = services.search_user_stories(project, text)
|
queryset = services.search_user_stories(project, text)
|
||||||
|
|
|
@ -23,8 +23,8 @@ from taiga.base.api import GenericViewSet
|
||||||
|
|
||||||
from . import serializers
|
from . import serializers
|
||||||
from . import service
|
from . import service
|
||||||
|
from . import permissions
|
||||||
# TODO: Set Timelines permissions
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
class TimelineViewSet(GenericViewSet):
|
class TimelineViewSet(GenericViewSet):
|
||||||
|
@ -36,13 +36,13 @@ class TimelineViewSet(GenericViewSet):
|
||||||
app_name, model = self.content_type.split(".", 1)
|
app_name, model = self.content_type.split(".", 1)
|
||||||
return get_object_or_404(ContentType, app_label=app_name, model=model)
|
return get_object_or_404(ContentType, app_label=app_name, model=model)
|
||||||
|
|
||||||
def get_object(self):
|
def get_queryset(self):
|
||||||
ct = self.get_content_type()
|
ct = self.get_content_type()
|
||||||
model_cls = ct.model_class()
|
model_cls = ct.model_class()
|
||||||
|
|
||||||
qs = model_cls.objects.all()
|
qs = model_cls.objects.all()
|
||||||
filtered_qs = self.filter_queryset(qs)
|
filtered_qs = self.filter_queryset(qs)
|
||||||
return super().get_object(queryset=filtered_qs)
|
return filtered_qs
|
||||||
|
|
||||||
def response_for_queryset(self, queryset):
|
def response_for_queryset(self, queryset):
|
||||||
# Switch between paginated or standard style responses
|
# Switch between paginated or standard style responses
|
||||||
|
@ -61,13 +61,17 @@ class TimelineViewSet(GenericViewSet):
|
||||||
|
|
||||||
def retrieve(self, request, pk):
|
def retrieve(self, request, pk):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
|
self.check_permissions(request, "retrieve", obj)
|
||||||
|
|
||||||
qs = service.get_timeline(obj)
|
qs = service.get_timeline(obj)
|
||||||
return self.response_for_queryset(qs)
|
return self.response_for_queryset(qs)
|
||||||
|
|
||||||
|
|
||||||
class UserTimeline(TimelineViewSet):
|
class UserTimeline(TimelineViewSet):
|
||||||
content_type = "users.user"
|
content_type = "users.user"
|
||||||
|
permission_classes = (permissions.UserTimelinePermission,)
|
||||||
|
|
||||||
|
|
||||||
class ProjectTimeline(TimelineViewSet):
|
class ProjectTimeline(TimelineViewSet):
|
||||||
content_type = "projects.project"
|
content_type = "projects.project"
|
||||||
|
permission_classes = (permissions.ProjectTimelinePermission,)
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
||||||
|
# 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 taiga.base.api.permissions import (ResourcePermission, HasProjectPerm,
|
||||||
|
IsProjectOwner, AllowAny)
|
||||||
|
|
||||||
|
|
||||||
|
class UserTimelinePermission(ResourcePermission):
|
||||||
|
retrieve_perms = AllowAny()
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectTimelinePermission(ResourcePermission):
|
||||||
|
retrieve_perms = HasProjectPerm('view_project')
|
|
@ -20,7 +20,7 @@ from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .routers import router
|
from .routers import router
|
||||||
from .projects.attachments.views import RawAttachmentView
|
from .projects.attachments.api import RawAttachmentView
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ class RoleAdmin(admin.ModelAdmin):
|
||||||
db_field, request=request, **kwargs)
|
db_field, request=request, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Role, RoleAdmin)
|
# admin.site.register(Role, RoleAdmin)
|
||||||
|
|
||||||
|
|
||||||
class UserAdmin(DjangoUserAdmin):
|
class UserAdmin(DjangoUserAdmin):
|
||||||
|
|
|
@ -25,16 +25,21 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.filters import BaseFilterBackend
|
from rest_framework.filters import BaseFilterBackend
|
||||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||||
from rest_framework import status, viewsets
|
from rest_framework import status
|
||||||
|
|
||||||
from djmail.template_mail import MagicMailBuilder
|
from djmail.template_mail import MagicMailBuilder
|
||||||
|
|
||||||
from taiga.base.decorators import list_route, action
|
from taiga.base.decorators import list_route, detail_route
|
||||||
|
from taiga.base.decorators import action
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
from taiga.base.api import ModelCrudViewSet, RetrieveModelMixin, ModelListViewSet
|
from taiga.base.api import ModelCrudViewSet
|
||||||
|
from taiga.base.api import ModelListViewSet
|
||||||
|
from taiga.projects.votes import services as votes_service
|
||||||
|
from taiga.projects.serializers import StarredSerializer
|
||||||
|
|
||||||
from .models import User, Role
|
from . import models
|
||||||
from .serializers import UserSerializer, RecoverySerializer
|
from . import serializers
|
||||||
|
from . import permissions
|
||||||
|
|
||||||
|
|
||||||
class MembersFilterBackend(BaseFilterBackend):
|
class MembersFilterBackend(BaseFilterBackend):
|
||||||
|
@ -55,31 +60,45 @@ class MembersFilterBackend(BaseFilterBackend):
|
||||||
|
|
||||||
|
|
||||||
class UsersViewSet(ModelCrudViewSet):
|
class UsersViewSet(ModelCrudViewSet):
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (permissions.UserPermission,)
|
||||||
serializer_class = UserSerializer
|
serializer_class = serializers.UserSerializer
|
||||||
queryset = User.objects.all()
|
queryset = models.User.objects.all()
|
||||||
filter_backends = (MembersFilterBackend,)
|
|
||||||
|
|
||||||
def pre_conditions_on_save(self, obj):
|
def pre_conditions_on_save(self, obj):
|
||||||
if not self.request.user.is_superuser and obj.id != self.request.user.id:
|
if self.request.user.is_superuser:
|
||||||
|
return
|
||||||
|
|
||||||
|
if obj.id == self.request.user.id:
|
||||||
|
return
|
||||||
|
|
||||||
|
if obj.id is None:
|
||||||
|
return
|
||||||
|
|
||||||
raise exc.PreconditionError()
|
raise exc.PreconditionError()
|
||||||
|
|
||||||
def pre_conditions_on_delete(self, obj):
|
def pre_conditions_on_delete(self, obj):
|
||||||
if not self.request.user.is_superuser and obj.id != self.request.user.id:
|
if self.request.user.is_superuser:
|
||||||
|
return
|
||||||
|
|
||||||
|
if obj.id == self.request.user.id:
|
||||||
|
return
|
||||||
|
|
||||||
raise exc.PreconditionError()
|
raise exc.PreconditionError()
|
||||||
|
|
||||||
@list_route(permission_classes=[AllowAny], methods=["POST"])
|
@list_route(methods=["POST"])
|
||||||
def password_recovery(self, request, pk=None):
|
def password_recovery(self, request, pk=None):
|
||||||
username_or_email = request.DATA.get('username', None)
|
username_or_email = request.DATA.get('username', None)
|
||||||
|
|
||||||
|
self.check_permissions(request, "password_recovery", None)
|
||||||
|
|
||||||
if not username_or_email:
|
if not username_or_email:
|
||||||
raise exc.WrongArguments(_("Invalid username or email"))
|
raise exc.WrongArguments(_("Invalid username or email"))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
queryset = User.objects.all()
|
queryset = models.User.objects.all()
|
||||||
user = queryset.get(Q(username=username_or_email) |
|
user = queryset.get(Q(username=username_or_email) |
|
||||||
Q(email=username_or_email))
|
Q(email=username_or_email))
|
||||||
except User.DoesNotExist:
|
except models.User.DoesNotExist:
|
||||||
raise exc.WrongArguments(_("Invalid username or email"))
|
raise exc.WrongArguments(_("Invalid username or email"))
|
||||||
|
|
||||||
user.token = str(uuid.uuid1())
|
user.token = str(uuid.uuid1())
|
||||||
|
@ -92,27 +111,36 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
return Response({"detail": _("Mail sended successful!"),
|
return Response({"detail": _("Mail sended successful!"),
|
||||||
"email": user.email})
|
"email": user.email})
|
||||||
|
|
||||||
@list_route(permission_classes=[AllowAny], methods=["POST"])
|
@list_route(methods=["POST"])
|
||||||
def change_password_from_recovery(self, request, pk=None):
|
def change_password_from_recovery(self, request, pk=None):
|
||||||
"""
|
"""
|
||||||
Change password with token (from password recovery step).
|
Change password with token (from password recovery step).
|
||||||
"""
|
"""
|
||||||
serializer = RecoverySerializer(data=request.DATA, many=False)
|
|
||||||
|
self.check_permissions(request, "change_password_from_recovery", None)
|
||||||
|
|
||||||
|
serializer = serializers.RecoverySerializer(data=request.DATA, many=False)
|
||||||
if not serializer.is_valid():
|
if not serializer.is_valid():
|
||||||
raise exc.WrongArguments(_("Token is invalid"))
|
raise exc.WrongArguments(_("Token is invalid"))
|
||||||
|
|
||||||
user = User.objects.get(token=serializer.data["token"])
|
try:
|
||||||
|
user = models.User.objects.get(token=serializer.data["token"])
|
||||||
|
except models.User.DoesNotExist:
|
||||||
|
raise exc.WrongArguments(_("Token is invalid"))
|
||||||
|
|
||||||
user.set_password(serializer.data["password"])
|
user.set_password(serializer.data["password"])
|
||||||
user.token = None
|
user.token = None
|
||||||
user.save(update_fields=["password", "token"])
|
user.save(update_fields=["password", "token"])
|
||||||
|
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
@list_route(permission_classes=[IsAuthenticated], methods=["POST"])
|
@list_route(methods=["POST"])
|
||||||
def change_password(self, request, pk=None):
|
def change_password(self, request, pk=None):
|
||||||
"""
|
"""
|
||||||
Change password to current logged user.
|
Change password to current logged user.
|
||||||
"""
|
"""
|
||||||
|
self.check_permissions(request, "change_password", None)
|
||||||
|
|
||||||
password = request.DATA.get("password")
|
password = request.DATA.get("password")
|
||||||
|
|
||||||
if not password:
|
if not password:
|
||||||
|
@ -124,3 +152,12 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
request.user.set_password(password)
|
request.user.set_password(password)
|
||||||
request.user.save(update_fields=["password"])
|
request.user.save(update_fields=["password"])
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
@detail_route(methods=["GET"])
|
||||||
|
def starred(self, request, pk=None):
|
||||||
|
user = self.get_object()
|
||||||
|
self.check_permissions(request, 'starred', user)
|
||||||
|
|
||||||
|
stars = votes_service.get_voted(user.pk, model=get_model('projects', 'Project'))
|
||||||
|
stars_data = StarredSerializer(stars, many=True)
|
||||||
|
return Response(stars_data.data)
|
||||||
|
|
|
@ -21,7 +21,10 @@ from django.contrib.auth.models import UserManager, AbstractBaseUser
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from djorm_pgarray.fields import TextArrayField
|
||||||
|
|
||||||
from taiga.base.utils.slug import slugify_uniquely
|
from taiga.base.utils.slug import slugify_uniquely
|
||||||
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS
|
||||||
|
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
@ -124,8 +127,10 @@ class Role(models.Model):
|
||||||
verbose_name=_("name"))
|
verbose_name=_("name"))
|
||||||
slug = models.SlugField(max_length=250, null=False, blank=True,
|
slug = models.SlugField(max_length=250, null=False, blank=True,
|
||||||
verbose_name=_("slug"))
|
verbose_name=_("slug"))
|
||||||
permissions = models.ManyToManyField("auth.Permission", related_name="roles",
|
permissions = TextArrayField(blank=True, null=True,
|
||||||
verbose_name=_("permissions"))
|
default=[],
|
||||||
|
verbose_name=_("permissions"),
|
||||||
|
choices=MEMBERS_PERMISSIONS)
|
||||||
order = models.IntegerField(default=10, null=False, blank=False,
|
order = models.IntegerField(default=10, null=False, blank=False,
|
||||||
verbose_name=_("order"))
|
verbose_name=_("order"))
|
||||||
project = models.ForeignKey("projects.Project", null=False, blank=False,
|
project = models.ForeignKey("projects.Project", null=False, blank=False,
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
||||||
|
# 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 taiga.base.api.permissions import (ResourcePermission, IsSuperUser,
|
||||||
|
AllowAny, PermissionComponent,
|
||||||
|
IsAuthenticated)
|
||||||
|
|
||||||
|
|
||||||
|
class IsTheSameUser(PermissionComponent):
|
||||||
|
def check_permissions(self, request, view, obj=None):
|
||||||
|
return request.user.is_authenticated() and request.user.pk == obj.pk
|
||||||
|
|
||||||
|
|
||||||
|
class UserPermission(ResourcePermission):
|
||||||
|
enought_perms = IsSuperUser()
|
||||||
|
global_perms = None
|
||||||
|
retrieve_perms = AllowAny()
|
||||||
|
create_perms = AllowAny()
|
||||||
|
update_perms = IsTheSameUser()
|
||||||
|
destroy_perms = IsTheSameUser()
|
||||||
|
list_perms = AllowAny()
|
||||||
|
password_recovery_perms = AllowAny()
|
||||||
|
change_password_from_recovery_perms = AllowAny()
|
||||||
|
change_password_perms = IsAuthenticated()
|
||||||
|
starred_perms = AllowAny()
|
|
@ -42,12 +42,3 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
class RecoverySerializer(serializers.Serializer):
|
class RecoverySerializer(serializers.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)
|
||||||
|
|
||||||
def validate_token(self, attrs, source):
|
|
||||||
token = attrs[source]
|
|
||||||
try:
|
|
||||||
user = User.objects.get(token=token)
|
|
||||||
except User.DoesNotExist:
|
|
||||||
raise serializers.ValidationError(_("invalid token"))
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
|
|
|
@ -32,13 +32,16 @@ class StorageEntriesViewSet(ModelCrudViewSet):
|
||||||
model = models.StorageEntry
|
model = models.StorageEntry
|
||||||
filter_backends = (filters.StorageEntriesFilterBackend,)
|
filter_backends = (filters.StorageEntriesFilterBackend,)
|
||||||
serializer_class = serializers.StorageEntrySerializer
|
serializer_class = serializers.StorageEntrySerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.StorageEntriesPermission)
|
permission_classes = [permissions.StorageEntriesPermission]
|
||||||
lookup_field = "key"
|
lookup_field = "key"
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
if self.request.user.is_anonymous():
|
||||||
|
return self.model.objects.none()
|
||||||
return self.request.user.storage_entries.all()
|
return self.request.user.storage_entries.all()
|
||||||
|
|
||||||
def pre_save(self, obj):
|
def pre_save(self, obj):
|
||||||
|
if self.request.user.is_authenticated():
|
||||||
obj.owner = self.request.user
|
obj.owner = self.request.user
|
||||||
|
|
||||||
def create(self, *args, **kwargs):
|
def create(self, *args, **kwargs):
|
||||||
|
|
|
@ -14,9 +14,9 @@
|
||||||
# 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.permissions import Permission
|
from taiga.base.api.permissions import ResourcePermission, IsAuthenticated, DenyAll
|
||||||
|
|
||||||
|
|
||||||
class StorageEntriesPermission(Permission):
|
class StorageEntriesPermission(ResourcePermission):
|
||||||
def has_object_permission(self, request, view, obj):
|
enought_perms = IsAuthenticated()
|
||||||
return request.user == obj.owner
|
global_perms = DenyAll()
|
||||||
|
|
|
@ -44,6 +44,7 @@ class ProjectTemplateFactory(Factory):
|
||||||
|
|
||||||
name = "Template name"
|
name = "Template name"
|
||||||
slug = settings.DEFAULT_PROJECT_TEMPLATE
|
slug = settings.DEFAULT_PROJECT_TEMPLATE
|
||||||
|
description = factory.Sequence(lambda n: "Description {}".format(n))
|
||||||
|
|
||||||
us_statuses = []
|
us_statuses = []
|
||||||
points = []
|
points = []
|
||||||
|
@ -80,6 +81,28 @@ class PointsFactory(Factory):
|
||||||
project = factory.SubFactory("tests.factories.ProjectFactory")
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
|
||||||
|
|
||||||
|
class AttachmentFactory(Factory):
|
||||||
|
FACTORY_FOR = get_model("attachments", "Attachment")
|
||||||
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
owner = factory.SubFactory("tests.factories.UserFactory")
|
||||||
|
|
||||||
|
|
||||||
|
class UserStoryAttachmentFactory(AttachmentFactory):
|
||||||
|
content_object = factory.SubFactory("tests.factories.UserStoryFactory")
|
||||||
|
|
||||||
|
|
||||||
|
class TaskAttachmentFactory(AttachmentFactory):
|
||||||
|
content_object = factory.SubFactory("tests.factories.TaskFactory")
|
||||||
|
|
||||||
|
|
||||||
|
class IssueAttachmentFactory(AttachmentFactory):
|
||||||
|
content_object = factory.SubFactory("tests.factories.IssueFactory")
|
||||||
|
|
||||||
|
|
||||||
|
class WikiAttachmentFactory(AttachmentFactory):
|
||||||
|
content_object = factory.SubFactory("tests.factories.WikiFactory")
|
||||||
|
|
||||||
|
|
||||||
class RolePointsFactory(Factory):
|
class RolePointsFactory(Factory):
|
||||||
FACTORY_FOR = get_model("userstories", "RolePoints")
|
FACTORY_FOR = get_model("userstories", "RolePoints")
|
||||||
|
|
||||||
|
@ -131,17 +154,6 @@ class UserStoryStatusFactory(Factory):
|
||||||
project = factory.SubFactory("tests.factories.ProjectFactory")
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
|
||||||
|
|
||||||
class TaskFactory(Factory):
|
|
||||||
FACTORY_FOR = get_model("tasks", "Task")
|
|
||||||
|
|
||||||
ref = factory.Sequence(lambda n: n)
|
|
||||||
owner = factory.SubFactory("tests.factories.UserFactory")
|
|
||||||
subject = factory.Sequence(lambda n: "Task {}".format(n))
|
|
||||||
user_story = factory.SubFactory("tests.factories.UserStoryFactory")
|
|
||||||
status = factory.SubFactory("tests.factories.TaskStatusFactory")
|
|
||||||
project = factory.SubFactory("tests.factories.ProjectFactory")
|
|
||||||
milestone = factory.SubFactory("tests.factories.MilestoneFactory")
|
|
||||||
|
|
||||||
|
|
||||||
class TaskStatusFactory(Factory):
|
class TaskStatusFactory(Factory):
|
||||||
FACTORY_FOR = get_model("projects", "TaskStatus")
|
FACTORY_FOR = get_model("projects", "TaskStatus")
|
||||||
|
@ -177,12 +189,14 @@ class IssueFactory(Factory):
|
||||||
class TaskFactory(Factory):
|
class TaskFactory(Factory):
|
||||||
FACTORY_FOR = get_model("tasks", "Task")
|
FACTORY_FOR = get_model("tasks", "Task")
|
||||||
|
|
||||||
|
ref = factory.Sequence(lambda n: n)
|
||||||
subject = factory.Sequence(lambda n: "Task {}".format(n))
|
subject = factory.Sequence(lambda n: "Task {}".format(n))
|
||||||
description = factory.Sequence(lambda n: "Task {} description".format(n))
|
description = factory.Sequence(lambda n: "Task {} description".format(n))
|
||||||
owner = factory.SubFactory("tests.factories.UserFactory")
|
owner = factory.SubFactory("tests.factories.UserFactory")
|
||||||
project = factory.SubFactory("tests.factories.ProjectFactory")
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
status = factory.SubFactory("tests.factories.TaskStatusFactory")
|
status = factory.SubFactory("tests.factories.TaskStatusFactory")
|
||||||
milestone = factory.SubFactory("tests.factories.MilestoneFactory")
|
milestone = factory.SubFactory("tests.factories.MilestoneFactory")
|
||||||
|
user_story = factory.SubFactory("tests.factories.UserStoryFactory")
|
||||||
|
|
||||||
|
|
||||||
class WikiPageFactory(Factory):
|
class WikiPageFactory(Factory):
|
||||||
|
@ -194,6 +208,15 @@ class WikiPageFactory(Factory):
|
||||||
content = factory.Sequence(lambda n: "Wiki Page {} content".format(n))
|
content = factory.Sequence(lambda n: "Wiki Page {} content".format(n))
|
||||||
|
|
||||||
|
|
||||||
|
class WikiLinkFactory(Factory):
|
||||||
|
FACTORY_FOR = get_model("wiki", "WikiLink")
|
||||||
|
|
||||||
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
title = factory.Sequence(lambda n: "Wiki Link {} title".format(n))
|
||||||
|
href = factory.Sequence(lambda n: "link-{}".format(n))
|
||||||
|
order = factory.Sequence(lambda n: n)
|
||||||
|
|
||||||
|
|
||||||
class IssueStatusFactory(Factory):
|
class IssueStatusFactory(Factory):
|
||||||
FACTORY_FOR = get_model("projects", "IssueStatus")
|
FACTORY_FOR = get_model("projects", "IssueStatus")
|
||||||
|
|
||||||
|
@ -201,10 +224,10 @@ class IssueStatusFactory(Factory):
|
||||||
project = factory.SubFactory("tests.factories.ProjectFactory")
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
|
||||||
|
|
||||||
class TaskStatusFactory(Factory):
|
class UserStoryStatusFactory(Factory):
|
||||||
FACTORY_FOR = get_model("projects", "TaskStatus")
|
FACTORY_FOR = get_model("projects", "UserStoryStatus")
|
||||||
|
|
||||||
name = factory.Sequence(lambda n: "Issue Status {}".format(n))
|
name = factory.Sequence(lambda n: "User Story Status {}".format(n))
|
||||||
project = factory.SubFactory("tests.factories.ProjectFactory")
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,594 @@
|
||||||
|
import pytest
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
|
||||||
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||||
|
from taiga.projects.attachments.serializers import AttachmentSerializer
|
||||||
|
|
||||||
|
from tests import factories as f
|
||||||
|
from tests.utils import helper_test_http_method, helper_test_http_method_and_count, disconnect_signals, reconnect_signals
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def setup_module(module):
|
||||||
|
disconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_module(module):
|
||||||
|
reconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data():
|
||||||
|
m = type("Models", (object,), {})
|
||||||
|
|
||||||
|
m.registered_user = f.UserFactory.create()
|
||||||
|
m.project_member_with_perms = f.UserFactory.create()
|
||||||
|
m.project_member_without_perms = f.UserFactory.create()
|
||||||
|
m.project_owner = f.UserFactory.create()
|
||||||
|
m.other_user = f.UserFactory.create()
|
||||||
|
|
||||||
|
m.public_project = f.ProjectFactory(is_private=False,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project1 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project2 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=[],
|
||||||
|
public_permissions=[],
|
||||||
|
owner=m.project_owner)
|
||||||
|
|
||||||
|
m.public_membership = f.MembershipFactory(project=m.public_project,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.public_project,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=[])
|
||||||
|
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=[])
|
||||||
|
|
||||||
|
m.public_user_story = f.UserStoryFactory(project=m.public_project, ref=1)
|
||||||
|
m.public_task = f.TaskFactory(project=m.public_project, ref=2)
|
||||||
|
m.public_issue = f.IssueFactory(project=m.public_project, ref=3)
|
||||||
|
m.public_wiki = f.WikiPageFactory(project=m.public_project, slug=4)
|
||||||
|
|
||||||
|
m.public_user_story_attachment = f.UserStoryAttachmentFactory(project=m.public_project, content_object=m.public_user_story)
|
||||||
|
m.public_task_attachment = f.TaskAttachmentFactory(project=m.public_project, content_object=m.public_task)
|
||||||
|
m.public_issue_attachment = f.IssueAttachmentFactory(project=m.public_project, content_object=m.public_issue)
|
||||||
|
m.public_wiki_attachment = f.WikiAttachmentFactory(project=m.public_project, content_object=m.public_wiki)
|
||||||
|
|
||||||
|
m.private_user_story1 = f.UserStoryFactory(project=m.private_project1, ref=5)
|
||||||
|
m.private_task1 = f.TaskFactory(project=m.private_project1, ref=6)
|
||||||
|
m.private_issue1 = f.IssueFactory(project=m.private_project1, ref=7)
|
||||||
|
m.private_wiki1 = f.WikiPageFactory(project=m.private_project1, slug=8)
|
||||||
|
|
||||||
|
m.private_user_story1_attachment = f.UserStoryAttachmentFactory(project=m.private_project1, content_object=m.private_user_story1)
|
||||||
|
m.private_task1_attachment = f.TaskAttachmentFactory(project=m.private_project1, content_object=m.private_task1)
|
||||||
|
m.private_issue1_attachment = f.IssueAttachmentFactory(project=m.private_project1, content_object=m.private_issue1)
|
||||||
|
m.private_wiki1_attachment = f.WikiAttachmentFactory(project=m.private_project1, content_object=m.private_wiki1)
|
||||||
|
|
||||||
|
m.private_user_story2 = f.UserStoryFactory(project=m.private_project2, ref=9)
|
||||||
|
m.private_task2 = f.TaskFactory(project=m.private_project2, ref=10)
|
||||||
|
m.private_issue2 = f.IssueFactory(project=m.private_project2, ref=11)
|
||||||
|
m.private_wiki2 = f.WikiPageFactory(project=m.private_project2, slug=12)
|
||||||
|
|
||||||
|
m.private_user_story2_attachment = f.UserStoryAttachmentFactory(project=m.private_project2, content_object=m.private_user_story2)
|
||||||
|
m.private_task2_attachment = f.TaskAttachmentFactory(project=m.private_project2, content_object=m.private_task2)
|
||||||
|
m.private_issue2_attachment = f.IssueAttachmentFactory(project=m.private_project2, content_object=m.private_issue2)
|
||||||
|
m.private_wiki2_attachment = f.WikiAttachmentFactory(project=m.private_project2, content_object=m.private_wiki2)
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_story_attachment_retrieve(client, data):
|
||||||
|
public_url = reverse('userstory-attachments-detail', kwargs={"pk": data.public_user_story_attachment.pk})
|
||||||
|
private_url1 = reverse('userstory-attachments-detail', kwargs={"pk": data.private_user_story1_attachment.pk})
|
||||||
|
private_url2 = reverse('userstory-attachments-detail', kwargs={"pk": data.private_user_story2_attachment.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_attachment_retrieve(client, data):
|
||||||
|
public_url = reverse('task-attachments-detail', kwargs={"pk": data.public_task_attachment.pk})
|
||||||
|
private_url1 = reverse('task-attachments-detail', kwargs={"pk": data.private_task1_attachment.pk})
|
||||||
|
private_url2 = reverse('task-attachments-detail', kwargs={"pk": data.private_task2_attachment.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_attachment_retrieve(client, data):
|
||||||
|
public_url = reverse('issue-attachments-detail', kwargs={"pk": data.public_issue_attachment.pk})
|
||||||
|
private_url1 = reverse('issue-attachments-detail', kwargs={"pk": data.private_issue1_attachment.pk})
|
||||||
|
private_url2 = reverse('issue-attachments-detail', kwargs={"pk": data.private_issue2_attachment.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_attachment_retrieve(client, data):
|
||||||
|
public_url = reverse('wiki-attachments-detail', kwargs={"pk": data.public_wiki_attachment.pk})
|
||||||
|
private_url1 = reverse('wiki-attachments-detail', kwargs={"pk": data.private_wiki1_attachment.pk})
|
||||||
|
private_url2 = reverse('wiki-attachments-detail', kwargs={"pk": data.private_wiki2_attachment.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_story_attachment_update(client, data):
|
||||||
|
public_url = reverse('userstory-attachments-detail', kwargs={"pk": data.public_user_story_attachment.pk})
|
||||||
|
private_url1 = reverse('userstory-attachments-detail', kwargs={"pk": data.private_user_story1_attachment.pk})
|
||||||
|
private_url2 = reverse('userstory-attachments-detail', kwargs={"pk": data.private_user_story2_attachment.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
attachment_data = AttachmentSerializer(data.public_user_story_attachment).data
|
||||||
|
attachment_data["description"] = "test"
|
||||||
|
attachment_data = JSONRenderer().render(attachment_data)
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'put', public_url, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'put', private_url1, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'put', private_url2, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_attachment_update(client, data):
|
||||||
|
public_url = reverse('task-attachments-detail', kwargs={"pk": data.public_task_attachment.pk})
|
||||||
|
private_url1 = reverse('task-attachments-detail', kwargs={"pk": data.private_task1_attachment.pk})
|
||||||
|
private_url2 = reverse('task-attachments-detail', kwargs={"pk": data.private_task2_attachment.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
attachment_data = AttachmentSerializer(data.public_task_attachment).data
|
||||||
|
attachment_data["description"] = "test"
|
||||||
|
attachment_data = JSONRenderer().render(attachment_data)
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'put', public_url, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'put', private_url1, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'put', private_url2, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_attachment_update(client, data):
|
||||||
|
public_url = reverse('issue-attachments-detail', kwargs={"pk": data.public_issue_attachment.pk})
|
||||||
|
private_url1 = reverse('issue-attachments-detail', kwargs={"pk": data.private_issue1_attachment.pk})
|
||||||
|
private_url2 = reverse('issue-attachments-detail', kwargs={"pk": data.private_issue2_attachment.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
attachment_data = AttachmentSerializer(data.public_issue_attachment).data
|
||||||
|
attachment_data["description"] = "test"
|
||||||
|
attachment_data = JSONRenderer().render(attachment_data)
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'put', public_url, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'put', private_url1, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'put', private_url2, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_attachment_update(client, data):
|
||||||
|
public_url = reverse('wiki-attachments-detail', kwargs={"pk": data.public_wiki_attachment.pk})
|
||||||
|
private_url1 = reverse('wiki-attachments-detail', kwargs={"pk": data.private_wiki1_attachment.pk})
|
||||||
|
private_url2 = reverse('wiki-attachments-detail', kwargs={"pk": data.private_wiki2_attachment.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
attachment_data = AttachmentSerializer(data.public_wiki_attachment).data
|
||||||
|
attachment_data["description"] = "test"
|
||||||
|
attachment_data = JSONRenderer().render(attachment_data)
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'put', public_url, attachment_data, users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'put', private_url1, attachment_data, users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'put', private_url2, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_story_attachment_patch(client, data):
|
||||||
|
public_url = reverse('userstory-attachments-detail', kwargs={"pk": data.public_user_story_attachment.pk})
|
||||||
|
private_url1 = reverse('userstory-attachments-detail', kwargs={"pk": data.private_user_story1_attachment.pk})
|
||||||
|
private_url2 = reverse('userstory-attachments-detail', kwargs={"pk": data.private_user_story2_attachment.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
attachment_data = {"description": "test"}
|
||||||
|
attachment_data = JSONRenderer().render(attachment_data)
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'patch', public_url, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url1, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url2, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_attachment_patch(client, data):
|
||||||
|
public_url = reverse('task-attachments-detail', kwargs={"pk": data.public_task_attachment.pk})
|
||||||
|
private_url1 = reverse('task-attachments-detail', kwargs={"pk": data.private_task1_attachment.pk})
|
||||||
|
private_url2 = reverse('task-attachments-detail', kwargs={"pk": data.private_task2_attachment.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
attachment_data = {"description": "test"}
|
||||||
|
attachment_data = JSONRenderer().render(attachment_data)
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'patch', public_url, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url1, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url2, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_attachment_patch(client, data):
|
||||||
|
public_url = reverse('issue-attachments-detail', kwargs={"pk": data.public_issue_attachment.pk})
|
||||||
|
private_url1 = reverse('issue-attachments-detail', kwargs={"pk": data.private_issue1_attachment.pk})
|
||||||
|
private_url2 = reverse('issue-attachments-detail', kwargs={"pk": data.private_issue2_attachment.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
attachment_data = {"description": "test"}
|
||||||
|
attachment_data = JSONRenderer().render(attachment_data)
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'patch', public_url, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url1, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url2, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_attachment_patch(client, data):
|
||||||
|
public_url = reverse('wiki-attachments-detail', kwargs={"pk": data.public_wiki_attachment.pk})
|
||||||
|
private_url1 = reverse('wiki-attachments-detail', kwargs={"pk": data.private_wiki1_attachment.pk})
|
||||||
|
private_url2 = reverse('wiki-attachments-detail', kwargs={"pk": data.private_wiki2_attachment.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
attachment_data = {"description": "test"}
|
||||||
|
attachment_data = JSONRenderer().render(attachment_data)
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'patch', public_url, attachment_data, users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url1, attachment_data, users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url2, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_story_attachment_delete(client, data):
|
||||||
|
public_url = reverse('userstory-attachments-detail', kwargs={"pk": data.public_user_story_attachment.pk})
|
||||||
|
private_url1 = reverse('userstory-attachments-detail', kwargs={"pk": data.private_user_story1_attachment.pk})
|
||||||
|
private_url2 = reverse('userstory-attachments-detail', kwargs={"pk": data.private_user_story2_attachment.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'delete', public_url, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url1, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_attachment_delete(client, data):
|
||||||
|
public_url = reverse('task-attachments-detail', kwargs={"pk": data.public_task_attachment.pk})
|
||||||
|
private_url1 = reverse('task-attachments-detail', kwargs={"pk": data.private_task1_attachment.pk})
|
||||||
|
private_url2 = reverse('task-attachments-detail', kwargs={"pk": data.private_task2_attachment.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'delete', public_url, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url1, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_attachment_delete(client, data):
|
||||||
|
public_url = reverse('issue-attachments-detail', kwargs={"pk": data.public_issue_attachment.pk})
|
||||||
|
private_url1 = reverse('issue-attachments-detail', kwargs={"pk": data.private_issue1_attachment.pk})
|
||||||
|
private_url2 = reverse('issue-attachments-detail', kwargs={"pk": data.private_issue2_attachment.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'delete', public_url, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url1, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_attachment_delete(client, data):
|
||||||
|
public_url = reverse('wiki-attachments-detail', kwargs={"pk": data.public_wiki_attachment.pk})
|
||||||
|
private_url1 = reverse('wiki-attachments-detail', kwargs={"pk": data.private_wiki1_attachment.pk})
|
||||||
|
private_url2 = reverse('wiki-attachments-detail', kwargs={"pk": data.private_wiki2_attachment.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'delete', public_url, None, [None, data.registered_user])
|
||||||
|
assert results == [401, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url1, None, [None, data.registered_user])
|
||||||
|
assert results == [401, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
|
||||||
|
def test_user_story_attachment_create(client, data):
|
||||||
|
url = reverse('userstory-attachments-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
attachment_data = AttachmentSerializer(data.public_user_story_attachment).data
|
||||||
|
attachment_data["id"] = None
|
||||||
|
attachment_data["description"] = "test"
|
||||||
|
attachment_data = JSONRenderer().render(attachment_data)
|
||||||
|
results = helper_test_http_method(client, 'post', url, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 201, 201]
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_attachment_create(client, data):
|
||||||
|
url = reverse('task-attachments-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
attachment_data = AttachmentSerializer(data.public_task_attachment).data
|
||||||
|
attachment_data["id"] = None
|
||||||
|
attachment_data["description"] = "test"
|
||||||
|
attachment_data = JSONRenderer().render(attachment_data)
|
||||||
|
results = helper_test_http_method(client, 'post', url, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 201, 201]
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_attachment_create(client, data):
|
||||||
|
url = reverse('issue-attachments-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
attachment_data = AttachmentSerializer(data.public_issue_attachment).data
|
||||||
|
attachment_data["id"] = None
|
||||||
|
attachment_data["description"] = "test"
|
||||||
|
attachment_data = JSONRenderer().render(attachment_data)
|
||||||
|
results = helper_test_http_method(client, 'post', url, attachment_data, users)
|
||||||
|
assert results == [401, 403, 403, 201, 201]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_attachment_create(client, data):
|
||||||
|
url = reverse('wiki-attachments-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
attachment_data = AttachmentSerializer(data.public_wiki_attachment).data
|
||||||
|
attachment_data["id"] = None
|
||||||
|
attachment_data["description"] = "test"
|
||||||
|
attachment_data = JSONRenderer().render(attachment_data)
|
||||||
|
results = helper_test_http_method(client, 'post', url, attachment_data, users)
|
||||||
|
assert results == [401, 201, 201, 201, 201]
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_story_attachment_list(client, data):
|
||||||
|
url = reverse('userstory-attachments-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method_and_count(client, 'get', url, None, users)
|
||||||
|
assert results == [(200, 2), (200, 2), (200, 3), (200, 3), (200, 3)]
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_attachment_list(client, data):
|
||||||
|
url = reverse('task-attachments-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method_and_count(client, 'get', url, None, users)
|
||||||
|
assert results == [(200, 2), (200, 2), (200, 3), (200, 3), (200, 3)]
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_attachment_list(client, data):
|
||||||
|
url = reverse('issue-attachments-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method_and_count(client, 'get', url, None, users)
|
||||||
|
assert results == [(200, 2), (200, 2), (200, 3), (200, 3), (200, 3)]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_attachment_list(client, data):
|
||||||
|
url = reverse('wiki-attachments-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method_and_count(client, 'get', url, None, users)
|
||||||
|
assert results == [(200, 2), (200, 2), (200, 3), (200, 3), (200, 3)]
|
|
@ -0,0 +1,52 @@
|
||||||
|
import pytest
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
|
||||||
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||||
|
|
||||||
|
from tests import factories as f
|
||||||
|
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def setup_module(module):
|
||||||
|
disconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_module(module):
|
||||||
|
reconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
def test_auth_create(client):
|
||||||
|
url = reverse('auth-list')
|
||||||
|
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
|
||||||
|
login_data = json.dumps({
|
||||||
|
"type": "normal",
|
||||||
|
"username": user.username,
|
||||||
|
"password": user.username,
|
||||||
|
})
|
||||||
|
|
||||||
|
result = client.post(url, login_data, content_type="application/json")
|
||||||
|
assert result.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_auth_action_register(client, settings):
|
||||||
|
settings.PUBLIC_REGISTER_ENABLED = True
|
||||||
|
url = reverse('auth-register')
|
||||||
|
|
||||||
|
register_data = json.dumps({
|
||||||
|
"type": "public",
|
||||||
|
"username": "test",
|
||||||
|
"password": "test",
|
||||||
|
"full_name": "test",
|
||||||
|
"email": "test@test.com",
|
||||||
|
})
|
||||||
|
|
||||||
|
result = client.post(url, register_data, content_type="application/json")
|
||||||
|
assert result.status_code == 201
|
|
@ -0,0 +1,167 @@
|
||||||
|
import pytest
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
|
||||||
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||||
|
|
||||||
|
from tests import factories as f
|
||||||
|
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def setup_module(module):
|
||||||
|
disconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_module(module):
|
||||||
|
reconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data():
|
||||||
|
m = type("Models", (object,), {})
|
||||||
|
|
||||||
|
m.registered_user = f.UserFactory.create()
|
||||||
|
m.project_member_with_perms = f.UserFactory.create()
|
||||||
|
m.project_member_without_perms = f.UserFactory.create()
|
||||||
|
m.project_owner = f.UserFactory.create()
|
||||||
|
m.other_user = f.UserFactory.create()
|
||||||
|
|
||||||
|
m.public_project = f.ProjectFactory(is_private=False,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project1 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project2 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=[],
|
||||||
|
public_permissions=[],
|
||||||
|
owner=m.project_owner)
|
||||||
|
|
||||||
|
m.public_membership = f.MembershipFactory(project=m.public_project,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.public_project,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=[])
|
||||||
|
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=[])
|
||||||
|
|
||||||
|
m.public_user_story = f.UserStoryFactory(project=m.public_project, ref=1)
|
||||||
|
m.public_task = f.TaskFactory(project=m.public_project, ref=2)
|
||||||
|
m.public_issue = f.IssueFactory(project=m.public_project, ref=3)
|
||||||
|
m.public_wiki = f.WikiPageFactory(project=m.public_project, slug=4)
|
||||||
|
|
||||||
|
m.private_user_story1 = f.UserStoryFactory(project=m.private_project1, ref=5)
|
||||||
|
m.private_task1 = f.TaskFactory(project=m.private_project1, ref=6)
|
||||||
|
m.private_issue1 = f.IssueFactory(project=m.private_project1, ref=7)
|
||||||
|
m.private_wiki1 = f.WikiPageFactory(project=m.private_project1, slug=8)
|
||||||
|
|
||||||
|
m.private_user_story2 = f.UserStoryFactory(project=m.private_project2, ref=9)
|
||||||
|
m.private_task2 = f.TaskFactory(project=m.private_project2, ref=10)
|
||||||
|
m.private_issue2 = f.IssueFactory(project=m.private_project2, ref=11)
|
||||||
|
m.private_wiki2 = f.WikiPageFactory(project=m.private_project2, slug=12)
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_story_history_retrieve(client, data):
|
||||||
|
public_url = reverse('userstory-history-detail', kwargs={"pk": data.public_user_story.pk})
|
||||||
|
private_url1 = reverse('userstory-history-detail', kwargs={"pk": data.private_user_story1.pk})
|
||||||
|
private_url2 = reverse('userstory-history-detail', kwargs={"pk": data.private_user_story2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_history_retrieve(client, data):
|
||||||
|
public_url = reverse('task-history-detail', kwargs={"pk": data.public_task.pk})
|
||||||
|
private_url1 = reverse('task-history-detail', kwargs={"pk": data.private_task1.pk})
|
||||||
|
private_url2 = reverse('task-history-detail', kwargs={"pk": data.private_task2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_history_retrieve(client, data):
|
||||||
|
public_url = reverse('issue-history-detail', kwargs={"pk": data.public_issue.pk})
|
||||||
|
private_url1 = reverse('issue-history-detail', kwargs={"pk": data.private_issue1.pk})
|
||||||
|
private_url2 = reverse('issue-history-detail', kwargs={"pk": data.private_issue2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_history_retrieve(client, data):
|
||||||
|
public_url = reverse('wiki-history-detail', kwargs={"pk": data.public_wiki.pk})
|
||||||
|
private_url1 = reverse('wiki-history-detail', kwargs={"pk": data.private_wiki1.pk})
|
||||||
|
private_url2 = reverse('wiki-history-detail', kwargs={"pk": data.private_wiki2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
|
@ -0,0 +1,359 @@
|
||||||
|
import pytest
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
|
||||||
|
from taiga.projects.issues.serializers import IssueSerializer
|
||||||
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||||
|
|
||||||
|
from tests import factories as f
|
||||||
|
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
|
||||||
|
from taiga.projects.votes.services import add_vote
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def setup_module(module):
|
||||||
|
disconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_module(module):
|
||||||
|
reconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data():
|
||||||
|
m = type("Models", (object,), {})
|
||||||
|
|
||||||
|
m.registered_user = f.UserFactory.create()
|
||||||
|
m.project_member_with_perms = f.UserFactory.create()
|
||||||
|
m.project_member_without_perms = f.UserFactory.create()
|
||||||
|
m.project_owner = f.UserFactory.create()
|
||||||
|
m.other_user = f.UserFactory.create()
|
||||||
|
|
||||||
|
m.public_project = f.ProjectFactory(is_private=False,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project1 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project2 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=[],
|
||||||
|
public_permissions=[],
|
||||||
|
owner=m.project_owner)
|
||||||
|
|
||||||
|
m.public_membership = f.MembershipFactory(project=m.public_project,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.public_project,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=[])
|
||||||
|
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=[])
|
||||||
|
|
||||||
|
m.public_issue = f.IssueFactory(project=m.public_project,
|
||||||
|
status__project=m.public_project,
|
||||||
|
severity__project=m.public_project,
|
||||||
|
priority__project=m.public_project,
|
||||||
|
type__project=m.public_project,
|
||||||
|
milestone__project=m.public_project)
|
||||||
|
m.private_issue1 = f.IssueFactory(project=m.private_project1,
|
||||||
|
status__project=m.private_project1,
|
||||||
|
severity__project=m.private_project1,
|
||||||
|
priority__project=m.private_project1,
|
||||||
|
type__project=m.private_project1,
|
||||||
|
milestone__project=m.private_project1)
|
||||||
|
m.private_issue2 = f.IssueFactory(project=m.private_project2,
|
||||||
|
status__project=m.private_project2,
|
||||||
|
severity__project=m.private_project2,
|
||||||
|
priority__project=m.private_project2,
|
||||||
|
type__project=m.private_project2,
|
||||||
|
milestone__project=m.private_project2)
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_retrieve(client, data):
|
||||||
|
public_url = reverse('issues-detail', kwargs={"pk": data.public_issue.pk})
|
||||||
|
private_url1 = reverse('issues-detail', kwargs={"pk": data.private_issue1.pk})
|
||||||
|
private_url2 = reverse('issues-detail', kwargs={"pk": data.private_issue2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_update(client, data):
|
||||||
|
public_url = reverse('issues-detail', kwargs={"pk": data.public_issue.pk})
|
||||||
|
private_url1 = reverse('issues-detail', kwargs={"pk": data.private_issue1.pk})
|
||||||
|
private_url2 = reverse('issues-detail', kwargs={"pk": data.private_issue2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
issue_data = IssueSerializer(data.public_issue).data
|
||||||
|
issue_data["subject"] = "test"
|
||||||
|
issue_data = JSONRenderer().render(issue_data)
|
||||||
|
results = helper_test_http_method(client, 'put', public_url, issue_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
issue_data = IssueSerializer(data.private_issue1).data
|
||||||
|
issue_data["subject"] = "test"
|
||||||
|
issue_data = JSONRenderer().render(issue_data)
|
||||||
|
results = helper_test_http_method(client, 'put', private_url1, issue_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
issue_data = IssueSerializer(data.private_issue2).data
|
||||||
|
issue_data["subject"] = "test"
|
||||||
|
issue_data = JSONRenderer().render(issue_data)
|
||||||
|
results = helper_test_http_method(client, 'put', private_url2, issue_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_delete(client, data):
|
||||||
|
public_url = reverse('issues-detail', kwargs={"pk": data.public_issue.pk})
|
||||||
|
private_url1 = reverse('issues-detail', kwargs={"pk": data.private_issue1.pk})
|
||||||
|
private_url2 = reverse('issues-detail', kwargs={"pk": data.private_issue2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
]
|
||||||
|
results = helper_test_http_method(client, 'delete', public_url, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url1, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_list(client, data):
|
||||||
|
url = reverse('issues-list')
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
issues_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(issues_data) == 2
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.registered_user)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
issues_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(issues_data) == 2
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.project_member_with_perms)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
issues_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(issues_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.project_owner)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
issues_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(issues_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_create(client, data):
|
||||||
|
url = reverse('issues-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
create_data = json.dumps({
|
||||||
|
"subject": "test",
|
||||||
|
"ref": 1,
|
||||||
|
"project": data.public_project.pk,
|
||||||
|
"severity": data.public_project.severities.all()[0].pk,
|
||||||
|
"priority": data.public_project.priorities.all()[0].pk,
|
||||||
|
"status": data.public_project.issue_statuses.all()[0].pk,
|
||||||
|
"type": data.public_project.issue_types.all()[0].pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users)
|
||||||
|
assert results == [401, 201, 201, 201, 201]
|
||||||
|
|
||||||
|
create_data = json.dumps({
|
||||||
|
"subject": "test",
|
||||||
|
"ref": 2,
|
||||||
|
"project": data.private_project1.pk,
|
||||||
|
"severity": data.private_project1.severities.all()[0].pk,
|
||||||
|
"priority": data.private_project1.priorities.all()[0].pk,
|
||||||
|
"status": data.private_project1.issue_statuses.all()[0].pk,
|
||||||
|
"type": data.private_project1.issue_types.all()[0].pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users)
|
||||||
|
assert results == [401, 201, 201, 201, 201]
|
||||||
|
|
||||||
|
create_data = json.dumps({
|
||||||
|
"subject": "test",
|
||||||
|
"ref": 3,
|
||||||
|
"project": data.private_project2.pk,
|
||||||
|
"severity": data.private_project2.severities.all()[0].pk,
|
||||||
|
"priority": data.private_project2.priorities.all()[0].pk,
|
||||||
|
"status": data.private_project2.issue_statuses.all()[0].pk,
|
||||||
|
"type": data.private_project2.issue_types.all()[0].pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users)
|
||||||
|
assert results == [401, 403, 403, 201, 201]
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_patch(client, data):
|
||||||
|
public_url = reverse('issues-detail', kwargs={"pk": data.public_issue.pk})
|
||||||
|
private_url1 = reverse('issues-detail', kwargs={"pk": data.private_issue1.pk})
|
||||||
|
private_url2 = reverse('issues-detail', kwargs={"pk": data.private_issue2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"subject": "test", "version": data.public_issue.version})
|
||||||
|
results = helper_test_http_method(client, 'patch', public_url, patch_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"subject": "test", "version": data.private_issue1.version})
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url1, patch_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"subject": "test", "version": data.private_issue2.version})
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url2, patch_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_action_upvote(client, data):
|
||||||
|
public_url = reverse('issues-upvote', kwargs={"pk": data.public_issue.pk})
|
||||||
|
private_url1 = reverse('issues-upvote', kwargs={"pk": data.private_issue1.pk})
|
||||||
|
private_url2 = reverse('issues-upvote', kwargs={"pk": data.private_issue2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'post', public_url, "", users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'post', private_url1, "", users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'post', private_url2, "", users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_action_downvote(client, data):
|
||||||
|
public_url = reverse('issues-downvote', kwargs={"pk": data.public_issue.pk})
|
||||||
|
private_url1 = reverse('issues-downvote', kwargs={"pk": data.private_issue1.pk})
|
||||||
|
private_url2 = reverse('issues-downvote', kwargs={"pk": data.private_issue2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'post', public_url, "", users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'post', private_url1, "", users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'post', private_url2, "", users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_voters_list(client, data):
|
||||||
|
public_url = reverse('issue-voters-list', kwargs={"issue_id": data.public_issue.pk})
|
||||||
|
private_url1 = reverse('issue-voters-list', kwargs={"issue_id": data.private_issue1.pk})
|
||||||
|
private_url2 = reverse('issue-voters-list', kwargs={"issue_id": data.private_issue2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
def test_issue_voters_retrieve(client, data):
|
||||||
|
add_vote(data.public_issue, data.project_owner)
|
||||||
|
public_url = reverse('issue-voters-detail', kwargs={"issue_id": data.public_issue.pk, "pk": data.project_owner.pk})
|
||||||
|
add_vote(data.private_issue1, data.project_owner)
|
||||||
|
private_url1 = reverse('issue-voters-detail', kwargs={"issue_id": data.private_issue1.pk, "pk": data.project_owner.pk})
|
||||||
|
add_vote(data.private_issue2, data.project_owner)
|
||||||
|
private_url2 = reverse('issue-voters-detail', kwargs={"issue_id": data.private_issue2.pk, "pk": data.project_owner.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
|
@ -0,0 +1,263 @@
|
||||||
|
import pytest
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
|
||||||
|
from taiga.projects.milestones.serializers import MilestoneSerializer
|
||||||
|
from taiga.projects.milestones.models import Milestone
|
||||||
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||||
|
|
||||||
|
from tests import factories as f
|
||||||
|
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def setup_module(module):
|
||||||
|
disconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_module(module):
|
||||||
|
reconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data():
|
||||||
|
m = type("Models", (object,), {})
|
||||||
|
|
||||||
|
m.registered_user = f.UserFactory.create()
|
||||||
|
m.project_member_with_perms = f.UserFactory.create()
|
||||||
|
m.project_member_without_perms = f.UserFactory.create()
|
||||||
|
m.project_owner = f.UserFactory.create()
|
||||||
|
m.other_user = f.UserFactory.create()
|
||||||
|
|
||||||
|
m.public_project = f.ProjectFactory(is_private=False,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project1 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project2 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=[],
|
||||||
|
public_permissions=[],
|
||||||
|
owner=m.project_owner)
|
||||||
|
|
||||||
|
m.public_membership = f.MembershipFactory(project=m.public_project,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.public_project,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=[])
|
||||||
|
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=[])
|
||||||
|
|
||||||
|
m.public_milestone = f.MilestoneFactory(project=m.public_project)
|
||||||
|
m.private_milestone1 = f.MilestoneFactory(project=m.private_project1)
|
||||||
|
m.private_milestone2 = f.MilestoneFactory(project=m.private_project2)
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def test_milestone_retrieve(client, data):
|
||||||
|
public_url = reverse('milestones-detail', kwargs={"pk": data.public_milestone.pk})
|
||||||
|
private_url1 = reverse('milestones-detail', kwargs={"pk": data.private_milestone1.pk})
|
||||||
|
private_url2 = reverse('milestones-detail', kwargs={"pk": data.private_milestone2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_milestone_update(client, data):
|
||||||
|
public_url = reverse('milestones-detail', kwargs={"pk": data.public_milestone.pk})
|
||||||
|
private_url1 = reverse('milestones-detail', kwargs={"pk": data.private_milestone1.pk})
|
||||||
|
private_url2 = reverse('milestones-detail', kwargs={"pk": data.private_milestone2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
milestone_data = MilestoneSerializer(data.public_milestone).data
|
||||||
|
milestone_data["name"] = "test"
|
||||||
|
milestone_data = JSONRenderer().render(milestone_data)
|
||||||
|
results = helper_test_http_method(client, 'put', public_url, milestone_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
milestone_data = MilestoneSerializer(data.private_milestone1).data
|
||||||
|
milestone_data["name"] = "test"
|
||||||
|
milestone_data = JSONRenderer().render(milestone_data)
|
||||||
|
results = helper_test_http_method(client, 'put', private_url1, milestone_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
milestone_data = MilestoneSerializer(data.private_milestone2).data
|
||||||
|
milestone_data["name"] = "test"
|
||||||
|
milestone_data = JSONRenderer().render(milestone_data)
|
||||||
|
results = helper_test_http_method(client, 'put', private_url2, milestone_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_milestone_delete(client, data):
|
||||||
|
public_url = reverse('milestones-detail', kwargs={"pk": data.public_milestone.pk})
|
||||||
|
private_url1 = reverse('milestones-detail', kwargs={"pk": data.private_milestone1.pk})
|
||||||
|
private_url2 = reverse('milestones-detail', kwargs={"pk": data.private_milestone2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
]
|
||||||
|
results = helper_test_http_method(client, 'delete', public_url, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url1, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
|
||||||
|
|
||||||
|
def test_milestone_list(client, data):
|
||||||
|
url = reverse('milestones-list')
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
milestones_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(milestones_data) == 2
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.registered_user)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
milestones_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(milestones_data) == 2
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.project_member_with_perms)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
milestones_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(milestones_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.project_owner)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
milestones_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(milestones_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_milestone_create(client, data):
|
||||||
|
url = reverse('milestones-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
create_data = json.dumps({
|
||||||
|
"name": "test",
|
||||||
|
"estimated_start": "2014-12-10",
|
||||||
|
"estimated_finish": "2014-12-24",
|
||||||
|
"project": data.public_project.pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: Milestone.objects.all().delete())
|
||||||
|
assert results == [401, 403, 403, 201, 201]
|
||||||
|
|
||||||
|
create_data = json.dumps({
|
||||||
|
"name": "test",
|
||||||
|
"estimated_start": "2014-12-10",
|
||||||
|
"estimated_finish": "2014-12-24",
|
||||||
|
"project": data.private_project1.pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: Milestone.objects.all().delete())
|
||||||
|
assert results == [401, 403, 403, 201, 201]
|
||||||
|
|
||||||
|
create_data = json.dumps({
|
||||||
|
"name": "test",
|
||||||
|
"estimated_start": "2014-12-10",
|
||||||
|
"estimated_finish": "2014-12-24",
|
||||||
|
"project": data.private_project2.pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: Milestone.objects.all().delete())
|
||||||
|
assert results == [401, 403, 403, 201, 201]
|
||||||
|
|
||||||
|
|
||||||
|
def test_milestone_patch(client, data):
|
||||||
|
public_url = reverse('milestones-detail', kwargs={"pk": data.public_milestone.pk})
|
||||||
|
private_url1 = reverse('milestones-detail', kwargs={"pk": data.private_milestone1.pk})
|
||||||
|
private_url2 = reverse('milestones-detail', kwargs={"pk": data.private_milestone2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"name": "test"})
|
||||||
|
results = helper_test_http_method(client, 'patch', public_url, patch_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"name": "test"})
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url1, patch_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"name": "test"})
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url2, patch_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
def test_milestone_action_stats(client, data):
|
||||||
|
public_url = reverse('milestones-stats', kwargs={"pk": data.public_milestone.pk})
|
||||||
|
private_url1 = reverse('milestones-stats', kwargs={"pk": data.private_milestone1.pk})
|
||||||
|
private_url2 = reverse('milestones-stats', kwargs={"pk": data.private_milestone2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,381 @@
|
||||||
|
import pytest
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.db.models.loading import get_model
|
||||||
|
|
||||||
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
|
||||||
|
from taiga.projects.serializers import ProjectDetailSerializer
|
||||||
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS
|
||||||
|
|
||||||
|
from tests import factories as f
|
||||||
|
from tests.utils import helper_test_http_method, helper_test_http_method_and_count
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data():
|
||||||
|
m = type("Models", (object,), {})
|
||||||
|
m.registered_user = f.UserFactory.create()
|
||||||
|
m.project_member_with_perms = f.UserFactory.create()
|
||||||
|
m.project_member_without_perms = f.UserFactory.create()
|
||||||
|
m.project_owner = f.UserFactory.create()
|
||||||
|
m.other_user = f.UserFactory.create()
|
||||||
|
m.superuser = f.UserFactory.create(is_superuser=True)
|
||||||
|
|
||||||
|
m.public_project = f.ProjectFactory(is_private=False,
|
||||||
|
anon_permissions=['view_project'],
|
||||||
|
public_permissions=['view_project'])
|
||||||
|
m.private_project1 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=['view_project'],
|
||||||
|
public_permissions=['view_project'],
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project2 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=[],
|
||||||
|
public_permissions=[],
|
||||||
|
owner=m.project_owner)
|
||||||
|
|
||||||
|
f.RoleFactory(project=m.public_project)
|
||||||
|
|
||||||
|
m.membership = f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
m.membership = f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=[])
|
||||||
|
m.membership = f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
m.membership = f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=[])
|
||||||
|
|
||||||
|
ContentType = get_model("contenttypes", "ContentType")
|
||||||
|
Project = get_model("projects", "Project")
|
||||||
|
|
||||||
|
project_ct = ContentType.objects.get_for_model(Project)
|
||||||
|
|
||||||
|
f.VoteFactory(content_type=project_ct, object_id=m.public_project.pk, user=m.project_member_with_perms)
|
||||||
|
f.VoteFactory(content_type=project_ct, object_id=m.public_project.pk, user=m.project_owner)
|
||||||
|
f.VoteFactory(content_type=project_ct, object_id=m.private_project1.pk, user=m.project_member_with_perms)
|
||||||
|
f.VoteFactory(content_type=project_ct, object_id=m.private_project1.pk, user=m.project_owner)
|
||||||
|
f.VoteFactory(content_type=project_ct, object_id=m.private_project2.pk, user=m.project_member_with_perms)
|
||||||
|
f.VoteFactory(content_type=project_ct, object_id=m.private_project2.pk, user=m.project_owner)
|
||||||
|
|
||||||
|
f.VotesFactory(content_type=project_ct, object_id=m.public_project.pk, count=2)
|
||||||
|
f.VotesFactory(content_type=project_ct, object_id=m.private_project1.pk, count=2)
|
||||||
|
f.VotesFactory(content_type=project_ct, object_id=m.private_project2.pk, count=2)
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def test_project_retrieve(client, data):
|
||||||
|
public_url = reverse('projects-detail', kwargs={"pk": data.public_project.pk})
|
||||||
|
private1_url = reverse('projects-detail', kwargs={"pk": data.private_project1.pk})
|
||||||
|
private2_url = reverse('projects-detail', kwargs={"pk": data.private_project2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private1_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private2_url, None, users)
|
||||||
|
assert results == [401, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_project_update(client, data):
|
||||||
|
url = reverse('projects-detail', kwargs={"pk": data.private_project2.pk})
|
||||||
|
|
||||||
|
project_data = ProjectDetailSerializer(data.private_project2).data
|
||||||
|
project_data["is_private"] = False
|
||||||
|
project_data = JSONRenderer().render(project_data)
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'put', url, project_data, users)
|
||||||
|
assert results == [401, 403, 403, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_project_delete(client, data):
|
||||||
|
url = reverse('projects-detail', kwargs={"pk": data.private_project2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
results = helper_test_http_method(client, 'delete', url, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
|
||||||
|
|
||||||
|
def test_project_list(client, data):
|
||||||
|
url = reverse('projects-list')
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
projects_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(projects_data) == 2
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.registered_user)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
projects_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(projects_data) == 2
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.project_member_with_perms)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
projects_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(projects_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.project_owner)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
projects_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(projects_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_project_patch(client, data):
|
||||||
|
url = reverse('projects-detail', kwargs={"pk": data.private_project2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
data = json.dumps({"is_private": False})
|
||||||
|
results = helper_test_http_method(client, 'patch', url, data, users)
|
||||||
|
assert results == [401, 403, 403, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_project_action_stats(client, data):
|
||||||
|
public_url = reverse('projects-stats', kwargs={"pk": data.public_project.pk})
|
||||||
|
private1_url = reverse('projects-stats', kwargs={"pk": data.private_project1.pk})
|
||||||
|
private2_url = reverse('projects-stats', kwargs={"pk": data.private_project2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private1_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private2_url, None, users)
|
||||||
|
assert results == [404, 404, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_project_action_star(client, data):
|
||||||
|
public_url = reverse('projects-star', kwargs={"pk": data.public_project.pk})
|
||||||
|
private1_url = reverse('projects-star', kwargs={"pk": data.private_project1.pk})
|
||||||
|
private2_url = reverse('projects-star', kwargs={"pk": data.private_project2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
results = helper_test_http_method(client, 'post', public_url, None, users)
|
||||||
|
assert results == [401, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'post', private1_url, None, users)
|
||||||
|
assert results == [401, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'post', private2_url, None, users)
|
||||||
|
assert results == [404, 404, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_project_action_unstar(client, data):
|
||||||
|
public_url = reverse('projects-unstar', kwargs={"pk": data.public_project.pk})
|
||||||
|
private1_url = reverse('projects-unstar', kwargs={"pk": data.private_project1.pk})
|
||||||
|
private2_url = reverse('projects-unstar', kwargs={"pk": data.private_project2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
results = helper_test_http_method(client, 'post', public_url, None, users)
|
||||||
|
assert results == [401, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'post', private1_url, None, users)
|
||||||
|
assert results == [401, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'post', private2_url, None, users)
|
||||||
|
assert results == [404, 404, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_project_action_issues_stats(client, data):
|
||||||
|
public_url = reverse('projects-issues-stats', kwargs={"pk": data.public_project.pk})
|
||||||
|
private1_url = reverse('projects-issues-stats', kwargs={"pk": data.private_project1.pk})
|
||||||
|
private2_url = reverse('projects-issues-stats', kwargs={"pk": data.private_project2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private1_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private2_url, None, users)
|
||||||
|
assert results == [404, 404, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_project_action_issues_filters_data(client, data):
|
||||||
|
public_url = reverse('projects-issue-filters-data', kwargs={"pk": data.public_project.pk})
|
||||||
|
private1_url = reverse('projects-issue-filters-data', kwargs={"pk": data.private_project1.pk})
|
||||||
|
private2_url = reverse('projects-issue-filters-data', kwargs={"pk": data.private_project2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private1_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private2_url, None, users)
|
||||||
|
assert results == [404, 404, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_project_action_tags(client, data):
|
||||||
|
public_url = reverse('projects-tags', kwargs={"pk": data.public_project.pk})
|
||||||
|
private1_url = reverse('projects-tags', kwargs={"pk": data.private_project1.pk})
|
||||||
|
private2_url = reverse('projects-tags', kwargs={"pk": data.private_project2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private1_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private2_url, None, users)
|
||||||
|
assert results == [404, 404, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_project_action_fans(client, data):
|
||||||
|
public_url = reverse('projects-fans', kwargs={"pk": data.public_project.pk})
|
||||||
|
private1_url = reverse('projects-fans', kwargs={"pk": data.private_project1.pk})
|
||||||
|
private2_url = reverse('projects-fans', kwargs={"pk": data.private_project2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method_and_count(client, 'get', public_url, None, users)
|
||||||
|
assert results == [(200, 2), (200, 2), (200, 2), (200, 2), (200, 2)]
|
||||||
|
results = helper_test_http_method_and_count(client, 'get', private1_url, None, users)
|
||||||
|
assert results == [(200, 2), (200, 2), (200, 2), (200, 2), (200, 2)]
|
||||||
|
results = helper_test_http_method_and_count(client, 'get', private2_url, None, users)
|
||||||
|
assert results == [(404, 1), (404, 1), (403, 2), (200, 2), (200, 2)]
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_action_starred(client, data):
|
||||||
|
url1 = reverse('users-starred', kwargs={"pk": data.project_member_without_perms.pk})
|
||||||
|
url2 = reverse('users-starred', kwargs={"pk": data.project_member_with_perms.pk})
|
||||||
|
url3 = reverse('users-starred', kwargs={"pk": data.project_owner.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method_and_count(client, 'get', url1, None, users)
|
||||||
|
assert results == [(200, 0), (200, 0), (200, 0), (200, 0), (200, 0)]
|
||||||
|
results = helper_test_http_method_and_count(client, 'get', url2, None, users)
|
||||||
|
assert results == [(200, 3), (200, 3), (200, 3), (200, 3), (200, 3)]
|
||||||
|
results = helper_test_http_method_and_count(client, 'get', url3, None, users)
|
||||||
|
assert results == [(200, 3), (200, 3), (200, 3), (200, 3), (200, 3)]
|
||||||
|
|
||||||
|
|
||||||
|
def test_project_action_create_template(client, data):
|
||||||
|
public_url = reverse('projects-create-template', kwargs={"pk": data.public_project.pk})
|
||||||
|
private1_url = reverse('projects-create-template', kwargs={"pk": data.private_project1.pk})
|
||||||
|
private2_url = reverse('projects-create-template', kwargs={"pk": data.private_project2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner,
|
||||||
|
data.superuser,
|
||||||
|
]
|
||||||
|
|
||||||
|
template_data = json.dumps({
|
||||||
|
"template_name": "test",
|
||||||
|
"template_description": "test",
|
||||||
|
})
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'post', public_url, template_data, users)
|
||||||
|
assert results == [401, 403, 403, 403, 403, 201]
|
||||||
|
results = helper_test_http_method(client, 'post', private1_url, template_data, users)
|
||||||
|
assert results == [401, 403, 403, 403, 403, 201]
|
||||||
|
results = helper_test_http_method(client, 'post', private2_url, template_data, users)
|
||||||
|
assert results == [404, 404, 403, 403, 403, 201]
|
||||||
|
|
||||||
|
|
||||||
|
def test_invitations_list(client, data):
|
||||||
|
url = reverse('invitations-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
results = helper_test_http_method(client, 'get', url, None, users)
|
||||||
|
assert results == [403, 403, 403, 403]
|
||||||
|
|
||||||
|
|
||||||
|
def test_invitations_retrieve(client, data):
|
||||||
|
invitation = f.MembershipFactory(user=None)
|
||||||
|
|
||||||
|
url = reverse('invitations-detail', kwargs={'token': invitation.token})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
results = helper_test_http_method(client, 'get', url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200]
|
|
@ -0,0 +1,108 @@
|
||||||
|
import pytest
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
|
||||||
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||||
|
|
||||||
|
from tests import factories as f
|
||||||
|
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def setup_module(module):
|
||||||
|
disconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_module(module):
|
||||||
|
reconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data():
|
||||||
|
m = type("Models", (object,), {})
|
||||||
|
|
||||||
|
m.registered_user = f.UserFactory.create()
|
||||||
|
m.project_member_with_perms = f.UserFactory.create()
|
||||||
|
m.project_member_without_perms = f.UserFactory.create()
|
||||||
|
m.project_owner = f.UserFactory.create()
|
||||||
|
m.other_user = f.UserFactory.create()
|
||||||
|
|
||||||
|
m.public_project = f.ProjectFactory(is_private=False,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner,
|
||||||
|
slug="public")
|
||||||
|
m.private_project1 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner,
|
||||||
|
slug="private1")
|
||||||
|
m.private_project2 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=[],
|
||||||
|
public_permissions=[],
|
||||||
|
owner=m.project_owner,
|
||||||
|
slug="private2")
|
||||||
|
|
||||||
|
m.public_membership = f.MembershipFactory(project=m.public_project,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.public_project,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=[])
|
||||||
|
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=[])
|
||||||
|
|
||||||
|
m.view_only_membership = f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.other_user,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=["view_project"])
|
||||||
|
|
||||||
|
f.UserStoryFactory(project=m.private_project2, ref=1, pk=1)
|
||||||
|
f.TaskFactory(project=m.private_project2, ref=2, pk=1)
|
||||||
|
f.IssueFactory(project=m.private_project2, ref=3, pk=1)
|
||||||
|
m.milestone = f.MilestoneFactory(project=m.private_project2, slug=4, pk=1)
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolver_list(client, data):
|
||||||
|
url = reverse('resolver-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', "{}?project=public".format(url), None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', "{}?project=private1".format(url), None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', "{}?project=private2".format(url), None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
client.login(data.other_user)
|
||||||
|
response = client.get("{}?project=private2&us=1&task=2&issue=3&milestone=4".format(url))
|
||||||
|
assert json.loads(response.content.decode('utf-8')) == {"project": data.private_project2.pk}
|
||||||
|
|
||||||
|
client.login(data.project_owner)
|
||||||
|
response = client.get("{}?project=private2&us=1&task=2&issue=3&milestone=4".format(url))
|
||||||
|
assert json.loads(response.content.decode('utf-8')) == {"project": data.private_project2.pk, "us": 1, "task": 1, "issue": 1, "milestone": data.milestone.pk}
|
|
@ -0,0 +1,109 @@
|
||||||
|
import pytest
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
|
||||||
|
from taiga.projects.issues.serializers import IssueSerializer
|
||||||
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||||
|
|
||||||
|
from tests import factories as f
|
||||||
|
from tests.utils import helper_test_http_method_and_keys, disconnect_signals, reconnect_signals
|
||||||
|
from taiga.projects.votes.services import add_vote
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def setup_module(module):
|
||||||
|
disconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_module(module):
|
||||||
|
reconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data():
|
||||||
|
m = type("Models", (object,), {})
|
||||||
|
|
||||||
|
m.registered_user = f.UserFactory.create()
|
||||||
|
m.project_member_with_perms = f.UserFactory.create()
|
||||||
|
m.project_member_without_perms = f.UserFactory.create()
|
||||||
|
m.project_owner = f.UserFactory.create()
|
||||||
|
m.other_user = f.UserFactory.create()
|
||||||
|
|
||||||
|
m.public_project = f.ProjectFactory(is_private=False,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project1 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project2 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=[],
|
||||||
|
public_permissions=[],
|
||||||
|
owner=m.project_owner)
|
||||||
|
|
||||||
|
m.public_membership = f.MembershipFactory(project=m.public_project,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.public_project,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=[])
|
||||||
|
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=[])
|
||||||
|
|
||||||
|
m.public_issue = f.IssueFactory(project=m.public_project,
|
||||||
|
status__project=m.public_project,
|
||||||
|
severity__project=m.public_project,
|
||||||
|
priority__project=m.public_project,
|
||||||
|
type__project=m.public_project,
|
||||||
|
milestone__project=m.public_project)
|
||||||
|
m.private_issue1 = f.IssueFactory(project=m.private_project1,
|
||||||
|
status__project=m.private_project1,
|
||||||
|
severity__project=m.private_project1,
|
||||||
|
priority__project=m.private_project1,
|
||||||
|
type__project=m.private_project1,
|
||||||
|
milestone__project=m.private_project1)
|
||||||
|
m.private_issue2 = f.IssueFactory(project=m.private_project2,
|
||||||
|
status__project=m.private_project2,
|
||||||
|
severity__project=m.private_project2,
|
||||||
|
priority__project=m.private_project2,
|
||||||
|
type__project=m.private_project2,
|
||||||
|
milestone__project=m.private_project2)
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_list(client, data):
|
||||||
|
url = reverse('search-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method_and_keys(client, 'get', url, {'project': data.public_project.pk}, users)
|
||||||
|
all_keys = set(['count', 'userstories', 'issues', 'tasks', 'wikipages'])
|
||||||
|
assert results == [(200, all_keys), (200, all_keys), (200, all_keys), (200, all_keys), (200, all_keys)]
|
||||||
|
results = helper_test_http_method_and_keys(client, 'get', url, {'project': data.private_project1.pk}, users)
|
||||||
|
assert results == [(200, all_keys), (200, all_keys), (200, all_keys), (200, all_keys), (200, all_keys)]
|
||||||
|
results = helper_test_http_method_and_keys(client, 'get', url, {'project': data.private_project2.pk}, users)
|
||||||
|
assert results == [(200, set(['count'])), (200, set(['count'])), (200, set(['count'])), (200, all_keys), (200, all_keys)]
|
|
@ -0,0 +1,125 @@
|
||||||
|
import pytest
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
|
||||||
|
from taiga.userstorage.serializers import StorageEntrySerializer
|
||||||
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||||
|
|
||||||
|
from tests import factories as f
|
||||||
|
from tests.utils import helper_test_http_method, helper_test_http_method_and_count, disconnect_signals, reconnect_signals
|
||||||
|
from taiga.projects.votes.services import add_vote
|
||||||
|
|
||||||
|
from taiga.userstorage.models import StorageEntry
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def setup_module(module):
|
||||||
|
disconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_module(module):
|
||||||
|
reconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data():
|
||||||
|
m = type("Models", (object,), {})
|
||||||
|
|
||||||
|
m.user1 = f.UserFactory.create()
|
||||||
|
m.user2 = f.UserFactory.create()
|
||||||
|
|
||||||
|
m.storage_user1 = f.StorageEntryFactory(owner=m.user1)
|
||||||
|
m.storage_user2 = f.StorageEntryFactory(owner=m.user2)
|
||||||
|
m.storage2_user2 = f.StorageEntryFactory(owner=m.user2)
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def test_storage_retrieve(client, data):
|
||||||
|
url = reverse('user-storage-detail', kwargs={"key": data.storage_user1.key})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.user1,
|
||||||
|
data.user2,
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', url, None, users)
|
||||||
|
assert results == [401, 200, 404]
|
||||||
|
|
||||||
|
|
||||||
|
def test_storage_update(client, data):
|
||||||
|
url = reverse('user-storage-detail', kwargs={"key": data.storage_user1.key})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.user1,
|
||||||
|
data.user2,
|
||||||
|
]
|
||||||
|
|
||||||
|
storage_data = StorageEntrySerializer(data.storage_user1).data
|
||||||
|
storage_data["key"] = "test"
|
||||||
|
storage_data = JSONRenderer().render(storage_data)
|
||||||
|
results = helper_test_http_method(client, 'put', url, storage_data, users)
|
||||||
|
assert results == [401, 200, 201]
|
||||||
|
|
||||||
|
|
||||||
|
def test_storage_delete(client, data):
|
||||||
|
url = reverse('user-storage-detail', kwargs={"key": data.storage_user1.key})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.user1,
|
||||||
|
data.user2,
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'delete', url, None, users)
|
||||||
|
assert results == [401, 204, 404]
|
||||||
|
|
||||||
|
|
||||||
|
def test_storage_list(client, data):
|
||||||
|
url = reverse('user-storage-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.user1,
|
||||||
|
data.user2,
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method_and_count(client, 'get', url, None, users)
|
||||||
|
assert results == [(200, 0), (200, 1), (200, 2)]
|
||||||
|
|
||||||
|
|
||||||
|
def test_storage_create(client, data):
|
||||||
|
url = reverse('user-storage-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.user1,
|
||||||
|
data.user2,
|
||||||
|
]
|
||||||
|
|
||||||
|
create_data = json.dumps({
|
||||||
|
"key": "test",
|
||||||
|
"value": "test",
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: StorageEntry.objects.all().delete())
|
||||||
|
assert results == [401, 201, 201]
|
||||||
|
|
||||||
|
|
||||||
|
def test_storage_patch(client, data):
|
||||||
|
url = reverse('user-storage-detail', kwargs={"key": data.storage_user1.key})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.user1,
|
||||||
|
data.user2,
|
||||||
|
]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"value": "test"})
|
||||||
|
results = helper_test_http_method(client, 'patch', url, patch_data, users)
|
||||||
|
assert results == [401, 200, 201]
|
|
@ -0,0 +1,291 @@
|
||||||
|
import pytest
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
|
||||||
|
from taiga.projects.tasks.serializers import TaskSerializer
|
||||||
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||||
|
|
||||||
|
from tests import factories as f
|
||||||
|
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def setup_module(module):
|
||||||
|
disconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_module(module):
|
||||||
|
reconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data():
|
||||||
|
m = type("Models", (object,), {})
|
||||||
|
|
||||||
|
m.registered_user = f.UserFactory.create()
|
||||||
|
m.project_member_with_perms = f.UserFactory.create()
|
||||||
|
m.project_member_without_perms = f.UserFactory.create()
|
||||||
|
m.project_owner = f.UserFactory.create()
|
||||||
|
m.other_user = f.UserFactory.create()
|
||||||
|
|
||||||
|
m.public_project = f.ProjectFactory(is_private=False,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project1 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project2 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=[],
|
||||||
|
public_permissions=[],
|
||||||
|
owner=m.project_owner)
|
||||||
|
|
||||||
|
m.public_membership = f.MembershipFactory(project=m.public_project,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.public_project,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=[])
|
||||||
|
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=[])
|
||||||
|
|
||||||
|
m.public_task = f.TaskFactory(project=m.public_project,
|
||||||
|
status__project=m.public_project,
|
||||||
|
milestone__project=m.public_project,
|
||||||
|
user_story__project=m.public_project)
|
||||||
|
m.private_task1 = f.TaskFactory(project=m.private_project1,
|
||||||
|
status__project=m.private_project1,
|
||||||
|
milestone__project=m.private_project1,
|
||||||
|
user_story__project=m.private_project1)
|
||||||
|
m.private_task2 = f.TaskFactory(project=m.private_project2,
|
||||||
|
status__project=m.private_project2,
|
||||||
|
milestone__project=m.private_project2,
|
||||||
|
user_story__project=m.private_project2)
|
||||||
|
|
||||||
|
m.public_project.default_task_status = m.public_task.status
|
||||||
|
m.public_project.save()
|
||||||
|
m.private_project1.default_task_status = m.private_task1.status
|
||||||
|
m.private_project1.save()
|
||||||
|
m.private_project2.default_task_status = m.private_task2.status
|
||||||
|
m.private_project2.save()
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_retrieve(client, data):
|
||||||
|
public_url = reverse('tasks-detail', kwargs={"pk": data.public_task.pk})
|
||||||
|
private_url1 = reverse('tasks-detail', kwargs={"pk": data.private_task1.pk})
|
||||||
|
private_url2 = reverse('tasks-detail', kwargs={"pk": data.private_task2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_update(client, data):
|
||||||
|
public_url = reverse('tasks-detail', kwargs={"pk": data.public_task.pk})
|
||||||
|
private_url1 = reverse('tasks-detail', kwargs={"pk": data.private_task1.pk})
|
||||||
|
private_url2 = reverse('tasks-detail', kwargs={"pk": data.private_task2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
task_data = TaskSerializer(data.public_task).data
|
||||||
|
task_data["subject"] = "test"
|
||||||
|
task_data = JSONRenderer().render(task_data)
|
||||||
|
results = helper_test_http_method(client, 'put', public_url, task_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
task_data = TaskSerializer(data.private_task1).data
|
||||||
|
task_data["subject"] = "test"
|
||||||
|
task_data = JSONRenderer().render(task_data)
|
||||||
|
results = helper_test_http_method(client, 'put', private_url1, task_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
task_data = TaskSerializer(data.private_task2).data
|
||||||
|
task_data["subject"] = "test"
|
||||||
|
task_data = JSONRenderer().render(task_data)
|
||||||
|
results = helper_test_http_method(client, 'put', private_url2, task_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_delete(client, data):
|
||||||
|
public_url = reverse('tasks-detail', kwargs={"pk": data.public_task.pk})
|
||||||
|
private_url1 = reverse('tasks-detail', kwargs={"pk": data.private_task1.pk})
|
||||||
|
private_url2 = reverse('tasks-detail', kwargs={"pk": data.private_task2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
]
|
||||||
|
results = helper_test_http_method(client, 'delete', public_url, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url1, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_list(client, data):
|
||||||
|
url = reverse('tasks-list')
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
tasks_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(tasks_data) == 2
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.registered_user)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
tasks_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(tasks_data) == 2
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.project_member_with_perms)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
tasks_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(tasks_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.project_owner)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
tasks_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(tasks_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_create(client, data):
|
||||||
|
url = reverse('tasks-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
create_data = json.dumps({
|
||||||
|
"subject": "test",
|
||||||
|
"ref": 1,
|
||||||
|
"project": data.public_project.pk,
|
||||||
|
"status": data.public_project.task_statuses.all()[0].pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users)
|
||||||
|
assert results == [401, 403, 403, 201, 201]
|
||||||
|
|
||||||
|
create_data = json.dumps({
|
||||||
|
"subject": "test",
|
||||||
|
"ref": 2,
|
||||||
|
"project": data.private_project1.pk,
|
||||||
|
"status": data.private_project1.task_statuses.all()[0].pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users)
|
||||||
|
assert results == [401, 403, 403, 201, 201]
|
||||||
|
|
||||||
|
create_data = json.dumps({
|
||||||
|
"subject": "test",
|
||||||
|
"ref": 3,
|
||||||
|
"project": data.private_project2.pk,
|
||||||
|
"status": data.private_project2.task_statuses.all()[0].pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users)
|
||||||
|
assert results == [401, 403, 403, 201, 201]
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_patch(client, data):
|
||||||
|
public_url = reverse('tasks-detail', kwargs={"pk": data.public_task.pk})
|
||||||
|
private_url1 = reverse('tasks-detail', kwargs={"pk": data.private_task1.pk})
|
||||||
|
private_url2 = reverse('tasks-detail', kwargs={"pk": data.private_task2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"subject": "test", "version": data.public_task.version})
|
||||||
|
results = helper_test_http_method(client, 'patch', public_url, patch_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"subject": "test", "version": data.private_task1.version})
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url1, patch_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"subject": "test", "version": data.private_task2.version})
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url2, patch_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
def test_task_action_bulk_create(client, data):
|
||||||
|
url = reverse('tasks-bulk-create')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
bulk_data = json.dumps({
|
||||||
|
"bulkTasks": "test1\ntest2",
|
||||||
|
"usId": data.public_task.user_story.pk,
|
||||||
|
"projectId": data.public_task.project.pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, bulk_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
bulk_data = json.dumps({
|
||||||
|
"bulkTasks": "test1\ntest2",
|
||||||
|
"usId": data.private_task1.user_story.pk,
|
||||||
|
"projectId": data.private_task1.project.pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, bulk_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
bulk_data = json.dumps({
|
||||||
|
"bulkTasks": "test1\ntest2",
|
||||||
|
"usId": data.private_task2.user_story.pk,
|
||||||
|
"projectId": data.private_task2.project.pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, bulk_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
|
@ -0,0 +1,104 @@
|
||||||
|
import pytest
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
|
||||||
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||||
|
|
||||||
|
from tests import factories as f
|
||||||
|
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def setup_module(module):
|
||||||
|
disconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_module(module):
|
||||||
|
reconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data():
|
||||||
|
m = type("Models", (object,), {})
|
||||||
|
|
||||||
|
m.registered_user = f.UserFactory.create()
|
||||||
|
m.project_member_with_perms = f.UserFactory.create()
|
||||||
|
m.project_member_without_perms = f.UserFactory.create()
|
||||||
|
m.project_owner = f.UserFactory.create()
|
||||||
|
m.other_user = f.UserFactory.create()
|
||||||
|
|
||||||
|
m.public_project = f.ProjectFactory(is_private=False,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project1 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project2 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=[],
|
||||||
|
public_permissions=[],
|
||||||
|
owner=m.project_owner)
|
||||||
|
|
||||||
|
m.public_membership = f.MembershipFactory(project=m.public_project,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.public_project,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=[])
|
||||||
|
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=[])
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_timeline_retrieve(client, data):
|
||||||
|
url = reverse('user-timeline-detail', kwargs={"pk": data.registered_user.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_project_timeline_retrieve(client, data):
|
||||||
|
public_url = reverse('project-timeline-detail', kwargs={"pk": data.public_project.pk})
|
||||||
|
private_url1 = reverse('project-timeline-detail', kwargs={"pk": data.private_project1.pk})
|
||||||
|
private_url2 = reverse('project-timeline-detail', kwargs={"pk": data.private_project2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
|
@ -0,0 +1,230 @@
|
||||||
|
import pytest
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
|
||||||
|
from taiga.users.serializers import UserSerializer
|
||||||
|
from taiga.users.models import User
|
||||||
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||||
|
|
||||||
|
from tests import factories as f
|
||||||
|
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def setup_module(module):
|
||||||
|
disconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_module(module):
|
||||||
|
reconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data():
|
||||||
|
m = type("Models", (object,), {})
|
||||||
|
|
||||||
|
m.registered_user = f.UserFactory.create()
|
||||||
|
m.other_user = f.UserFactory.create()
|
||||||
|
m.superuser = f.UserFactory.create(is_superuser=True)
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_retrieve(client, data):
|
||||||
|
url = reverse('users-detail', kwargs={"pk": data.registered_user.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.other_user,
|
||||||
|
data.superuser,
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_update(client, data):
|
||||||
|
url = reverse('users-detail', kwargs={"pk": data.registered_user.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.other_user,
|
||||||
|
data.superuser,
|
||||||
|
]
|
||||||
|
|
||||||
|
user_data = UserSerializer(data.registered_user).data
|
||||||
|
user_data["full_name"] = "test"
|
||||||
|
user_data = JSONRenderer().render(user_data)
|
||||||
|
results = helper_test_http_method(client, 'put', url, user_data, users)
|
||||||
|
assert results == [401, 200, 403, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_delete(client, data):
|
||||||
|
url = reverse('users-detail', kwargs={"pk": data.registered_user.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.other_user,
|
||||||
|
data.registered_user,
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'delete', url, None, users)
|
||||||
|
assert results == [401, 403, 204]
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_list(client, data):
|
||||||
|
url = reverse('users-list')
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
users_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(users_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.registered_user)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
users_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(users_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.other_user)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
users_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(users_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.superuser)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
users_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(users_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_create(client, data):
|
||||||
|
url = reverse('users-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.other_user,
|
||||||
|
data.superuser,
|
||||||
|
]
|
||||||
|
|
||||||
|
create_data = json.dumps({
|
||||||
|
"username": "test",
|
||||||
|
"full_name": "test",
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: User.objects.filter(username="test").delete())
|
||||||
|
assert results == [201, 201, 201, 201]
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_patch(client, data):
|
||||||
|
url = reverse('users-detail', kwargs={"pk": data.registered_user.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.other_user,
|
||||||
|
data.superuser,
|
||||||
|
]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"full_name": "test"})
|
||||||
|
results = helper_test_http_method(client, 'patch', url, patch_data, users)
|
||||||
|
assert results == [401, 200, 403, 200]
|
||||||
|
|
||||||
|
def test_user_action_change_password(client, data):
|
||||||
|
url = reverse('users-change-password')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.other_user,
|
||||||
|
data.superuser,
|
||||||
|
]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"password": "test-password"})
|
||||||
|
results = helper_test_http_method(client, 'post', url, patch_data, users)
|
||||||
|
assert results == [401, 204, 204, 204]
|
||||||
|
|
||||||
|
def test_user_action_change_password_from_recovery(client, data):
|
||||||
|
url = reverse('users-change-password-from-recovery')
|
||||||
|
|
||||||
|
new_user = f.UserFactory(token="test-token")
|
||||||
|
|
||||||
|
def reset_token():
|
||||||
|
new_user.token = "test-token"
|
||||||
|
new_user.save()
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.other_user,
|
||||||
|
data.superuser,
|
||||||
|
]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"password": "test-password", "token": "test-token"})
|
||||||
|
results = helper_test_http_method(client, 'post', url, patch_data, users, reset_token)
|
||||||
|
assert results == [204, 204, 204, 204]
|
||||||
|
|
||||||
|
def test_user_action_password_recovery(client, data):
|
||||||
|
url = reverse('users-password-recovery')
|
||||||
|
|
||||||
|
new_user = f.UserFactory.create(username="test")
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.other_user,
|
||||||
|
data.superuser,
|
||||||
|
]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"username": "test"})
|
||||||
|
results = helper_test_http_method(client, 'post', url, patch_data, users)
|
||||||
|
assert results == [200, 200, 200, 200]
|
||||||
|
|
||||||
|
# def test_membership_retrieve(client, data):
|
||||||
|
# assert False
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def test_membership_update(client, data):
|
||||||
|
# assert False
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def test_membership_delete(client, data):
|
||||||
|
# assert False
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def test_membership_list(client, data):
|
||||||
|
# assert False
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def test_membership_patch(client, data):
|
||||||
|
# assert False
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def test_invitation_retrieve(client, data):
|
||||||
|
# assert False
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def test_invitation_update(client, data):
|
||||||
|
# assert False
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def test_invitation_delete(client, data):
|
||||||
|
# assert False
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def test_invitation_list(client, data):
|
||||||
|
# assert False
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def test_invitation_patch(client, data):
|
||||||
|
# assert False
|
|
@ -0,0 +1,296 @@
|
||||||
|
import pytest
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
|
||||||
|
from taiga.projects.userstories.serializers import UserStorySerializer
|
||||||
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||||
|
|
||||||
|
from tests import factories as f
|
||||||
|
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def setup_module(module):
|
||||||
|
disconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_module(module):
|
||||||
|
reconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data():
|
||||||
|
m = type("Models", (object,), {})
|
||||||
|
|
||||||
|
m.registered_user = f.UserFactory.create()
|
||||||
|
m.project_member_with_perms = f.UserFactory.create()
|
||||||
|
m.project_member_without_perms = f.UserFactory.create()
|
||||||
|
m.project_owner = f.UserFactory.create()
|
||||||
|
m.other_user = f.UserFactory.create()
|
||||||
|
|
||||||
|
m.public_project = f.ProjectFactory(is_private=False,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project1 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project2 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=[],
|
||||||
|
public_permissions=[],
|
||||||
|
owner=m.project_owner)
|
||||||
|
|
||||||
|
m.public_membership = f.MembershipFactory(project=m.public_project,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.public_project,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=[])
|
||||||
|
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=[])
|
||||||
|
|
||||||
|
m.public_points = f.PointsFactory(project=m.public_project)
|
||||||
|
m.private_points1 = f.PointsFactory(project=m.private_project1)
|
||||||
|
m.private_points2 = f.PointsFactory(project=m.private_project2)
|
||||||
|
|
||||||
|
m.public_role_points = f.RolePointsFactory(role=m.public_project.roles.all()[0],
|
||||||
|
points=m.public_points,
|
||||||
|
user_story__project=m.public_project)
|
||||||
|
m.private_role_points1 = f.RolePointsFactory(role=m.private_project1.roles.all()[0],
|
||||||
|
points=m.private_points1,
|
||||||
|
user_story__project=m.private_project1)
|
||||||
|
m.private_role_points2 = f.RolePointsFactory(role=m.private_project2.roles.all()[0],
|
||||||
|
points=m.private_points2,
|
||||||
|
user_story__project=m.private_project2)
|
||||||
|
|
||||||
|
m.public_user_story = m.public_role_points.user_story
|
||||||
|
m.private_user_story1 = m.private_role_points1.user_story
|
||||||
|
m.private_user_story2 = m.private_role_points2.user_story
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_story_retrieve(client, data):
|
||||||
|
public_url = reverse('userstories-detail', kwargs={"pk": data.public_user_story.pk})
|
||||||
|
private_url1 = reverse('userstories-detail', kwargs={"pk": data.private_user_story1.pk})
|
||||||
|
private_url2 = reverse('userstories-detail', kwargs={"pk": data.private_user_story2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_story_update(client, data):
|
||||||
|
public_url = reverse('userstories-detail', kwargs={"pk": data.public_user_story.pk})
|
||||||
|
private_url1 = reverse('userstories-detail', kwargs={"pk": data.private_user_story1.pk})
|
||||||
|
private_url2 = reverse('userstories-detail', kwargs={"pk": data.private_user_story2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
user_story_data = UserStorySerializer(data.public_user_story).data
|
||||||
|
user_story_data["subject"] = "test"
|
||||||
|
user_story_data = JSONRenderer().render(user_story_data)
|
||||||
|
results = helper_test_http_method(client, 'put', public_url, user_story_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
user_story_data = UserStorySerializer(data.private_user_story1).data
|
||||||
|
user_story_data["subject"] = "test"
|
||||||
|
user_story_data = JSONRenderer().render(user_story_data)
|
||||||
|
results = helper_test_http_method(client, 'put', private_url1, user_story_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
user_story_data = UserStorySerializer(data.private_user_story2).data
|
||||||
|
user_story_data["subject"] = "test"
|
||||||
|
user_story_data = JSONRenderer().render(user_story_data)
|
||||||
|
results = helper_test_http_method(client, 'put', private_url2, user_story_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_story_delete(client, data):
|
||||||
|
public_url = reverse('userstories-detail', kwargs={"pk": data.public_user_story.pk})
|
||||||
|
private_url1 = reverse('userstories-detail', kwargs={"pk": data.private_user_story1.pk})
|
||||||
|
private_url2 = reverse('userstories-detail', kwargs={"pk": data.private_user_story2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
]
|
||||||
|
results = helper_test_http_method(client, 'delete', public_url, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url1, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_story_list(client, data):
|
||||||
|
url = reverse('userstories-list')
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
userstories_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(userstories_data) == 2
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.registered_user)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
userstories_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(userstories_data) == 2
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.project_member_with_perms)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
userstories_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(userstories_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.project_owner)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
userstories_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(userstories_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_story_create(client, data):
|
||||||
|
url = reverse('userstories-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
create_data = json.dumps({"subject": "test", "ref": 1, "project": data.public_project.pk})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users)
|
||||||
|
assert results == [401, 201, 201, 201, 201]
|
||||||
|
|
||||||
|
create_data = json.dumps({"subject": "test", "ref": 2, "project": data.private_project1.pk})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users)
|
||||||
|
assert results == [401, 201, 201, 201, 201]
|
||||||
|
|
||||||
|
create_data = json.dumps({"subject": "test", "ref": 3, "project": data.private_project2.pk})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users)
|
||||||
|
assert results == [401, 403, 403, 201, 201]
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_story_patch(client, data):
|
||||||
|
public_url = reverse('userstories-detail', kwargs={"pk": data.public_user_story.pk})
|
||||||
|
private_url1 = reverse('userstories-detail', kwargs={"pk": data.private_user_story1.pk})
|
||||||
|
private_url2 = reverse('userstories-detail', kwargs={"pk": data.private_user_story2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"subject": "test", "version": data.public_user_story.version})
|
||||||
|
results = helper_test_http_method(client, 'patch', public_url, patch_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"subject": "test", "version": data.private_user_story1.version})
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url1, patch_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"subject": "test", "version": data.private_user_story2.version})
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url2, patch_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_story_action_bulk_create(client, data):
|
||||||
|
url = reverse('userstories-bulk-create')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
bulk_data = json.dumps({"bulkStories": "test1\ntest2", "projectId": data.public_user_story.project.pk})
|
||||||
|
results = helper_test_http_method(client, 'post', url, bulk_data, users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
bulk_data = json.dumps({"bulkStories": "test1\ntest2", "projectId": data.private_user_story1.project.pk})
|
||||||
|
results = helper_test_http_method(client, 'post', url, bulk_data, users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
bulk_data = json.dumps({"bulkStories": "test1\ntest2", "projectId": data.private_user_story2.project.pk})
|
||||||
|
results = helper_test_http_method(client, 'post', url, bulk_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_story_action_bulk_update_order(client, data):
|
||||||
|
url = reverse('userstories-bulk-update-order')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
post_data = json.dumps({
|
||||||
|
"bulkStories": [(1,2)],
|
||||||
|
"projectId": data.public_project.pk
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, post_data, users)
|
||||||
|
assert results == [401, 403, 403, 204, 204]
|
||||||
|
|
||||||
|
post_data = json.dumps({
|
||||||
|
"bulkStories": [(1,2)],
|
||||||
|
"projectId": data.private_project1.pk
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, post_data, users)
|
||||||
|
assert results == [401, 403, 403, 204, 204]
|
||||||
|
|
||||||
|
post_data = json.dumps({
|
||||||
|
"bulkStories": [(1,2)],
|
||||||
|
"projectId": data.private_project2.pk
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, post_data, users)
|
||||||
|
assert results == [401, 403, 403, 204, 204]
|
|
@ -0,0 +1,422 @@
|
||||||
|
import pytest
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
|
||||||
|
from taiga.projects.wiki.serializers import WikiPageSerializer, WikiLinkSerializer
|
||||||
|
from taiga.projects.wiki.models import WikiPage, WikiLink
|
||||||
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||||
|
|
||||||
|
from tests import factories as f
|
||||||
|
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
|
||||||
|
from taiga.projects.votes.services import add_vote
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def setup_module(module):
|
||||||
|
disconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_module(module):
|
||||||
|
reconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data():
|
||||||
|
m = type("Models", (object,), {})
|
||||||
|
|
||||||
|
m.registered_user = f.UserFactory.create()
|
||||||
|
m.project_member_with_perms = f.UserFactory.create()
|
||||||
|
m.project_member_without_perms = f.UserFactory.create()
|
||||||
|
m.project_owner = f.UserFactory.create()
|
||||||
|
m.other_user = f.UserFactory.create()
|
||||||
|
|
||||||
|
m.public_project = f.ProjectFactory(is_private=False,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project1 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)),
|
||||||
|
owner=m.project_owner)
|
||||||
|
m.private_project2 = f.ProjectFactory(is_private=True,
|
||||||
|
anon_permissions=[],
|
||||||
|
public_permissions=[],
|
||||||
|
owner=m.project_owner)
|
||||||
|
|
||||||
|
m.public_membership = f.MembershipFactory(project=m.public_project,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.public_project,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
m.private_membership1 = f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project1,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project1,
|
||||||
|
role__permissions=[])
|
||||||
|
m.private_membership2 = f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_with_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
f.MembershipFactory(project=m.private_project2,
|
||||||
|
user=m.project_member_without_perms,
|
||||||
|
role__project=m.private_project2,
|
||||||
|
role__permissions=[])
|
||||||
|
|
||||||
|
m.public_wiki_page = f.WikiPageFactory(project=m.public_project)
|
||||||
|
m.private_wiki_page1 = f.WikiPageFactory(project=m.private_project1)
|
||||||
|
m.private_wiki_page2 = f.WikiPageFactory(project=m.private_project2)
|
||||||
|
|
||||||
|
m.public_wiki_link = f.WikiLinkFactory(project=m.public_project)
|
||||||
|
m.private_wiki_link1 = f.WikiLinkFactory(project=m.private_project1)
|
||||||
|
m.private_wiki_link2 = f.WikiLinkFactory(project=m.private_project2)
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_page_retrieve(client, data):
|
||||||
|
public_url = reverse('wiki-detail', kwargs={"pk": data.public_wiki_page.pk})
|
||||||
|
private_url1 = reverse('wiki-detail', kwargs={"pk": data.private_wiki_page1.pk})
|
||||||
|
private_url2 = reverse('wiki-detail', kwargs={"pk": data.private_wiki_page2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_page_update(client, data):
|
||||||
|
public_url = reverse('wiki-detail', kwargs={"pk": data.public_wiki_page.pk})
|
||||||
|
private_url1 = reverse('wiki-detail', kwargs={"pk": data.private_wiki_page1.pk})
|
||||||
|
private_url2 = reverse('wiki-detail', kwargs={"pk": data.private_wiki_page2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
wiki_page_data = WikiPageSerializer(data.public_wiki_page).data
|
||||||
|
wiki_page_data["content"] = "test"
|
||||||
|
wiki_page_data = JSONRenderer().render(wiki_page_data)
|
||||||
|
results = helper_test_http_method(client, 'put', public_url, wiki_page_data, users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
wiki_page_data = WikiPageSerializer(data.private_wiki_page1).data
|
||||||
|
wiki_page_data["content"] = "test"
|
||||||
|
wiki_page_data = JSONRenderer().render(wiki_page_data)
|
||||||
|
results = helper_test_http_method(client, 'put', private_url1, wiki_page_data, users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
wiki_page_data = WikiPageSerializer(data.private_wiki_page2).data
|
||||||
|
wiki_page_data["content"] = "test"
|
||||||
|
wiki_page_data = JSONRenderer().render(wiki_page_data)
|
||||||
|
results = helper_test_http_method(client, 'put', private_url2, wiki_page_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_page_delete(client, data):
|
||||||
|
public_url = reverse('wiki-detail', kwargs={"pk": data.public_wiki_page.pk})
|
||||||
|
private_url1 = reverse('wiki-detail', kwargs={"pk": data.private_wiki_page1.pk})
|
||||||
|
private_url2 = reverse('wiki-detail', kwargs={"pk": data.private_wiki_page2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
]
|
||||||
|
results = helper_test_http_method(client, 'delete', public_url, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url1, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_page_list(client, data):
|
||||||
|
url = reverse('wiki-list')
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
wiki_pages_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(wiki_pages_data) == 2
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.registered_user)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
wiki_pages_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(wiki_pages_data) == 2
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.project_member_with_perms)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
wiki_pages_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(wiki_pages_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.project_owner)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
wiki_pages_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(wiki_pages_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_page_create(client, data):
|
||||||
|
url = reverse('wiki-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
create_data = json.dumps({
|
||||||
|
"content": "test",
|
||||||
|
"slug": "test",
|
||||||
|
"project": data.public_project.pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: WikiPage.objects.all().delete())
|
||||||
|
assert results == [401, 201, 201, 201, 201]
|
||||||
|
|
||||||
|
create_data = json.dumps({
|
||||||
|
"content": "test",
|
||||||
|
"slug": "test",
|
||||||
|
"project": data.private_project1.pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: WikiPage.objects.all().delete())
|
||||||
|
assert results == [401, 201, 201, 201, 201]
|
||||||
|
|
||||||
|
create_data = json.dumps({
|
||||||
|
"content": "test",
|
||||||
|
"slug": "test",
|
||||||
|
"project": data.private_project2.pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: WikiPage.objects.all().delete())
|
||||||
|
assert results == [401, 403, 403, 201, 201]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_page_patch(client, data):
|
||||||
|
public_url = reverse('wiki-detail', kwargs={"pk": data.public_wiki_page.pk})
|
||||||
|
private_url1 = reverse('wiki-detail', kwargs={"pk": data.private_wiki_page1.pk})
|
||||||
|
private_url2 = reverse('wiki-detail', kwargs={"pk": data.private_wiki_page2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"content": "test", "version": data.public_wiki_page.version})
|
||||||
|
results = helper_test_http_method(client, 'patch', public_url, patch_data, users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"content": "test", "version": data.private_wiki_page2.version})
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url1, patch_data, users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"content": "test", "version": data.private_wiki_page2.version})
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url2, patch_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
def test_wiki_page_action_render(client, data):
|
||||||
|
url = reverse('wiki-render')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
post_data = json.dumps({"content": "test", "project_id": data.public_project.pk})
|
||||||
|
results = helper_test_http_method(client, 'post', url, post_data, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_link_retrieve(client, data):
|
||||||
|
public_url = reverse('wiki-links-detail', kwargs={"pk": data.public_wiki_link.pk})
|
||||||
|
private_url1 = reverse('wiki-links-detail', kwargs={"pk": data.private_wiki_link1.pk})
|
||||||
|
private_url2 = reverse('wiki-links-detail', kwargs={"pk": data.private_wiki_link2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'get', public_url, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url1, None, users)
|
||||||
|
assert results == [200, 200, 200, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'get', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_link_update(client, data):
|
||||||
|
public_url = reverse('wiki-links-detail', kwargs={"pk": data.public_wiki_link.pk})
|
||||||
|
private_url1 = reverse('wiki-links-detail', kwargs={"pk": data.private_wiki_link1.pk})
|
||||||
|
private_url2 = reverse('wiki-links-detail', kwargs={"pk": data.private_wiki_link2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
wiki_link_data = WikiLinkSerializer(data.public_wiki_link).data
|
||||||
|
wiki_link_data["title"] = "test"
|
||||||
|
wiki_link_data = JSONRenderer().render(wiki_link_data)
|
||||||
|
results = helper_test_http_method(client, 'put', public_url, wiki_link_data, users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
wiki_link_data = WikiLinkSerializer(data.private_wiki_link1).data
|
||||||
|
wiki_link_data["title"] = "test"
|
||||||
|
wiki_link_data = JSONRenderer().render(wiki_link_data)
|
||||||
|
results = helper_test_http_method(client, 'put', private_url1, wiki_link_data, users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
wiki_link_data = WikiLinkSerializer(data.private_wiki_link2).data
|
||||||
|
wiki_link_data["title"] = "test"
|
||||||
|
wiki_link_data = JSONRenderer().render(wiki_link_data)
|
||||||
|
results = helper_test_http_method(client, 'put', private_url2, wiki_link_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_link_delete(client, data):
|
||||||
|
public_url = reverse('wiki-links-detail', kwargs={"pk": data.public_wiki_link.pk})
|
||||||
|
private_url1 = reverse('wiki-links-detail', kwargs={"pk": data.private_wiki_link1.pk})
|
||||||
|
private_url2 = reverse('wiki-links-detail', kwargs={"pk": data.private_wiki_link2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
]
|
||||||
|
results = helper_test_http_method(client, 'delete', public_url, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url1, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
results = helper_test_http_method(client, 'delete', private_url2, None, users)
|
||||||
|
assert results == [401, 403, 403, 204]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_link_list(client, data):
|
||||||
|
url = reverse('wiki-links-list')
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
wiki_links_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(wiki_links_data) == 2
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.registered_user)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
wiki_links_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(wiki_links_data) == 2
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.project_member_with_perms)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
wiki_links_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(wiki_links_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
client.login(data.project_owner)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
wiki_links_data = json.loads(response.content.decode('utf-8'))
|
||||||
|
assert len(wiki_links_data) == 3
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_link_create(client, data):
|
||||||
|
url = reverse('wiki-links-list')
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
create_data = json.dumps({
|
||||||
|
"title": "test",
|
||||||
|
"href": "test",
|
||||||
|
"project": data.public_project.pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: WikiLink.objects.all().delete())
|
||||||
|
assert results == [401, 201, 201, 201, 201]
|
||||||
|
|
||||||
|
create_data = json.dumps({
|
||||||
|
"title": "test",
|
||||||
|
"href": "test",
|
||||||
|
"project": data.private_project1.pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: WikiLink.objects.all().delete())
|
||||||
|
assert results == [401, 201, 201, 201, 201]
|
||||||
|
|
||||||
|
create_data = json.dumps({
|
||||||
|
"title": "test",
|
||||||
|
"href": "test",
|
||||||
|
"project": data.private_project2.pk,
|
||||||
|
})
|
||||||
|
results = helper_test_http_method(client, 'post', url, create_data, users, lambda: WikiLink.objects.all().delete())
|
||||||
|
assert results == [401, 403, 403, 201, 201]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wiki_link_patch(client, data):
|
||||||
|
public_url = reverse('wiki-links-detail', kwargs={"pk": data.public_wiki_link.pk})
|
||||||
|
private_url1 = reverse('wiki-links-detail', kwargs={"pk": data.private_wiki_link1.pk})
|
||||||
|
private_url2 = reverse('wiki-links-detail', kwargs={"pk": data.private_wiki_link2.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"title": "test"})
|
||||||
|
results = helper_test_http_method(client, 'patch', public_url, patch_data, users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"title": "test"})
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url1, patch_data, users)
|
||||||
|
assert results == [401, 200, 200, 200, 200]
|
||||||
|
|
||||||
|
patch_data = json.dumps({"title": "test"})
|
||||||
|
results = helper_test_http_method(client, 'patch', private_url2, patch_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
|
@ -10,7 +10,7 @@ pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
def test_authentication(client):
|
def test_authentication(client):
|
||||||
"User can't access an attachment if not authenticated"
|
"User can't access an attachment if not authenticated"
|
||||||
attachment = f.AttachmentFactory.create()
|
attachment = f.UserStoryAttachmentFactory.create()
|
||||||
url = reverse("attachment-url", kwargs={"pk": attachment.pk})
|
url = reverse("attachment-url", kwargs={"pk": attachment.pk})
|
||||||
|
|
||||||
response = client.get(url)
|
response = client.get(url)
|
||||||
|
@ -20,7 +20,7 @@ def test_authentication(client):
|
||||||
|
|
||||||
def test_authorization(client):
|
def test_authorization(client):
|
||||||
"User can't access an attachment if not authorized"
|
"User can't access an attachment if not authorized"
|
||||||
attachment = f.AttachmentFactory.create()
|
attachment = f.UserStoryAttachmentFactory.create()
|
||||||
user = f.UserFactory.create()
|
user = f.UserFactory.create()
|
||||||
|
|
||||||
url = reverse("attachment-url", kwargs={"pk": attachment.pk})
|
url = reverse("attachment-url", kwargs={"pk": attachment.pk})
|
||||||
|
@ -34,7 +34,7 @@ def test_authorization(client):
|
||||||
@set_settings(IN_DEVELOPMENT_SERVER=True)
|
@set_settings(IN_DEVELOPMENT_SERVER=True)
|
||||||
def test_attachment_redirect_in_devserver(client):
|
def test_attachment_redirect_in_devserver(client):
|
||||||
"When accessing the attachment in devserver redirect to the real attachment url"
|
"When accessing the attachment in devserver redirect to the real attachment url"
|
||||||
attachment = f.AttachmentFactory.create()
|
attachment = f.UserStoryAttachmentFactory.create(attached_file="test")
|
||||||
|
|
||||||
url = reverse("attachment-url", kwargs={"pk": attachment.pk})
|
url = reverse("attachment-url", kwargs={"pk": attachment.pk})
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ def test_attachment_redirect_in_devserver(client):
|
||||||
@set_settings(IN_DEVELOPMENT_SERVER=False)
|
@set_settings(IN_DEVELOPMENT_SERVER=False)
|
||||||
def test_attachment_redirect(client):
|
def test_attachment_redirect(client):
|
||||||
"When accessing the attachment redirect using X-Accel-Redirect header"
|
"When accessing the attachment redirect using X-Accel-Redirect header"
|
||||||
attachment = f.AttachmentFactory.create()
|
attachment = f.UserStoryAttachmentFactory.create()
|
||||||
|
|
||||||
url = reverse("attachment-url", kwargs={"pk": attachment.pk})
|
url = reverse("attachment-url", kwargs={"pk": attachment.pk})
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from taiga.permissions import service, permissions
|
||||||
|
from django.contrib.auth.models import AnonymousUser
|
||||||
|
|
||||||
|
from .. import factories
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_user_project_role():
|
||||||
|
user1 = factories.UserFactory()
|
||||||
|
user2 = factories.UserFactory()
|
||||||
|
project = factories.ProjectFactory()
|
||||||
|
role = factories.RoleFactory()
|
||||||
|
membership = factories.MembershipFactory(user=user1, project=project, role=role)
|
||||||
|
|
||||||
|
assert service._get_user_project_membership(user1, project) == membership
|
||||||
|
assert service._get_user_project_membership(user2, project) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_anon_get_user_project_permissions():
|
||||||
|
project = factories.ProjectFactory()
|
||||||
|
project.anon_permissions = ["test1"]
|
||||||
|
project.public_permissions = ["test2"]
|
||||||
|
assert service.get_user_project_permissions(AnonymousUser(), project) == set(["test1"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_get_user_project_permissions_on_public_project():
|
||||||
|
user1 = factories.UserFactory()
|
||||||
|
project = factories.ProjectFactory()
|
||||||
|
project.anon_permissions = ["test1"]
|
||||||
|
project.public_permissions = ["test2"]
|
||||||
|
assert service.get_user_project_permissions(user1, project) == set(["test1", "test2"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_get_user_project_permissions_on_private_project():
|
||||||
|
user1 = factories.UserFactory()
|
||||||
|
project = factories.ProjectFactory()
|
||||||
|
project.anon_permissions = ["test1"]
|
||||||
|
project.public_permissions = ["test2"]
|
||||||
|
project.is_private = True
|
||||||
|
assert service.get_user_project_permissions(user1, project) == set(["test1", "test2"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_owner_get_user_project_permissions():
|
||||||
|
user1 = factories.UserFactory()
|
||||||
|
project = factories.ProjectFactory()
|
||||||
|
project.anon_permissions = ["test1"]
|
||||||
|
project.public_permissions = ["test2"]
|
||||||
|
project.owner = user1
|
||||||
|
role = factories.RoleFactory(permissions=["view_us"])
|
||||||
|
factories.MembershipFactory(user=user1, project=project, role=role)
|
||||||
|
|
||||||
|
expected_perms = set(
|
||||||
|
["test1", "test2"] +
|
||||||
|
[x[0] for x in permissions.OWNERS_PERMISSIONS] +
|
||||||
|
[x[0] for x in permissions.MEMBERS_PERMISSIONS]
|
||||||
|
)
|
||||||
|
assert service.get_user_project_permissions(user1, project) == expected_perms
|
||||||
|
|
||||||
|
|
||||||
|
def test_owner_member_get_user_project_permissions():
|
||||||
|
user1 = factories.UserFactory()
|
||||||
|
project = factories.ProjectFactory()
|
||||||
|
project.anon_permissions = ["test1"]
|
||||||
|
project.public_permissions = ["test2"]
|
||||||
|
role = factories.RoleFactory(permissions=["test3"])
|
||||||
|
factories.MembershipFactory(user=user1, project=project, role=role, is_owner=True)
|
||||||
|
|
||||||
|
expected_perms = set(
|
||||||
|
["test1", "test2", "test3"] +
|
||||||
|
[x[0] for x in permissions.OWNERS_PERMISSIONS]
|
||||||
|
)
|
||||||
|
assert service.get_user_project_permissions(user1, project) == expected_perms
|
||||||
|
|
||||||
|
|
||||||
|
def test_member_get_user_project_permissions():
|
||||||
|
user1 = factories.UserFactory()
|
||||||
|
project = factories.ProjectFactory()
|
||||||
|
project.anon_permissions = ["test1"]
|
||||||
|
project.public_permissions = ["test2"]
|
||||||
|
role = factories.RoleFactory(permissions=["test3"])
|
||||||
|
factories.MembershipFactory(user=user1, project=project, role=role)
|
||||||
|
|
||||||
|
assert service.get_user_project_permissions(user1, project) == set(["test1", "test2", "test3"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_anon_user_has_perm():
|
||||||
|
project = factories.ProjectFactory()
|
||||||
|
project.anon_permissions = ["test"]
|
||||||
|
assert service.user_has_perm(AnonymousUser(), "test", project) == True
|
||||||
|
assert service.user_has_perm(AnonymousUser(), "fail", project) == False
|
||||||
|
|
||||||
|
|
||||||
|
def test_authenticated_user_has_perm_on_project():
|
||||||
|
user1 = factories.UserFactory()
|
||||||
|
project = factories.ProjectFactory()
|
||||||
|
project.public_permissions = ["test"]
|
||||||
|
assert service.user_has_perm(user1, "test", project) == True
|
||||||
|
assert service.user_has_perm(user1, "fail", project) == False
|
||||||
|
|
||||||
|
|
||||||
|
def test_authenticated_user_has_perm_on_project_related_object():
|
||||||
|
user1 = factories.UserFactory()
|
||||||
|
project = factories.ProjectFactory()
|
||||||
|
project.public_permissions = ["test"]
|
||||||
|
us = factories.UserStoryFactory(project=project)
|
||||||
|
|
||||||
|
assert service.user_has_perm(user1, "test", us) == True
|
||||||
|
assert service.user_has_perm(user1, "fail", us) == False
|
||||||
|
|
||||||
|
|
||||||
|
def test_authenticated_user_has_perm_on_invalid_object():
|
||||||
|
user1 = factories.UserFactory()
|
||||||
|
assert service.user_has_perm(user1, "test", user1) == False
|
|
@ -28,9 +28,9 @@ pytestmark = pytest.mark.django_db
|
||||||
def test_archived_filter(client):
|
def test_archived_filter(client):
|
||||||
user = f.UserFactory.create()
|
user = f.UserFactory.create()
|
||||||
project = f.ProjectFactory.create(owner=user)
|
project = f.ProjectFactory.create(owner=user)
|
||||||
membership = f.MembershipFactory.create(project=project, user=user)
|
f.MembershipFactory.create(project=project, user=user)
|
||||||
userstory_1 = f.UserStoryFactory.create(project=project)
|
f.UserStoryFactory.create(project=project)
|
||||||
user_story_2 = f.UserStoryFactory.create(is_archived=True, project=project)
|
f.UserStoryFactory.create(is_archived=True, project=project)
|
||||||
|
|
||||||
client.login(user)
|
client.login(user)
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,21 @@ from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
from .. import factories as f
|
from .. import factories as f
|
||||||
|
|
||||||
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS
|
||||||
|
from tests.utils import disconnect_signals, reconnect_signals
|
||||||
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def setup_module(module):
|
||||||
|
disconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_module(module):
|
||||||
|
reconnect_signals()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def searches_initial_data():
|
def searches_initial_data():
|
||||||
m = type("InitialData", (object,), {})()
|
m = type("InitialData", (object,), {})()
|
||||||
|
@ -31,12 +43,33 @@ def searches_initial_data():
|
||||||
m.project1 = f.ProjectFactory.create()
|
m.project1 = f.ProjectFactory.create()
|
||||||
m.project2 = f.ProjectFactory.create()
|
m.project2 = f.ProjectFactory.create()
|
||||||
|
|
||||||
m.member1 = f.MembershipFactory.create(project=m.project1)
|
m.member1 = f.MembershipFactory(project=m.project1,
|
||||||
m.member2 = f.MembershipFactory.create(project=m.project1)
|
role__project=m.project1,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
m.member2 = f.MembershipFactory(project=m.project1,
|
||||||
|
role__project=m.project1,
|
||||||
|
role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
|
||||||
|
|
||||||
|
f.RoleFactory(project=m.project2)
|
||||||
|
|
||||||
|
m.points1 = f.PointsFactory(project=m.project1, value=None)
|
||||||
|
m.points2 = f.PointsFactory(project=m.project2, value=None)
|
||||||
|
|
||||||
|
m.role_points1 = f.RolePointsFactory.create(role=m.project1.roles.all()[0],
|
||||||
|
points=m.points1,
|
||||||
|
user_story__project=m.project1)
|
||||||
|
m.role_points2 = f.RolePointsFactory.create(role=m.project1.roles.all()[0],
|
||||||
|
points=m.points1,
|
||||||
|
user_story__project=m.project1,
|
||||||
|
user_story__description="Back to the future")
|
||||||
|
m.role_points3 = f.RolePointsFactory.create(role=m.project2.roles.all()[0],
|
||||||
|
points=m.points2,
|
||||||
|
user_story__project=m.project2)
|
||||||
|
|
||||||
|
m.us1 = m.role_points1.user_story
|
||||||
|
m.us2 = m.role_points2.user_story
|
||||||
|
m.us3 = m.role_points3.user_story
|
||||||
|
|
||||||
m.us1 = f.UserStoryFactory.create(project=m.project1)
|
|
||||||
m.us2 = f.UserStoryFactory.create(project=m.project1, description="Back to the future")
|
|
||||||
m.us3 = f.UserStoryFactory.create(project=m.project2)
|
|
||||||
|
|
||||||
m.tsk1 = f.TaskFactory.create(project=m.project2)
|
m.tsk1 = f.TaskFactory.create(project=m.project2)
|
||||||
m.tsk2 = f.TaskFactory.create(project=m.project1)
|
m.tsk2 = f.TaskFactory.create(project=m.project1)
|
||||||
|
@ -73,7 +106,8 @@ def test_search_all_objects_in_project_is_not_mine(client, searches_initial_data
|
||||||
client.login(data.member1.user)
|
client.login(data.member1.user)
|
||||||
|
|
||||||
response = client.get(reverse("search-list"), {"project": data.project2.id})
|
response = client.get(reverse("search-list"), {"project": data.project2.id})
|
||||||
assert response.status_code == 403
|
assert response.status_code == 200
|
||||||
|
assert response.data["count"] == 0
|
||||||
|
|
||||||
|
|
||||||
def test_search_text_query_in_my_project(client, searches_initial_data):
|
def test_search_text_query_in_my_project(client, searches_initial_data):
|
||||||
|
|
|
@ -75,7 +75,7 @@ def test_list_project_fans(client):
|
||||||
user = f.UserFactory.create()
|
user = f.UserFactory.create()
|
||||||
project = f.ProjectFactory.create(owner=user)
|
project = f.ProjectFactory.create(owner=user)
|
||||||
fan = f.VoteFactory.create(content_object=project)
|
fan = f.VoteFactory.create(content_object=project)
|
||||||
url = reverse("project-fans-list", args=(project.id,))
|
url = reverse("projects-fans", args=(project.id,))
|
||||||
|
|
||||||
client.login(user)
|
client.login(user)
|
||||||
response = client.get(url)
|
response = client.get(url)
|
||||||
|
@ -84,23 +84,10 @@ def test_list_project_fans(client):
|
||||||
assert response.data[0]['id'] == fan.user.id
|
assert response.data[0]['id'] == fan.user.id
|
||||||
|
|
||||||
|
|
||||||
def test_get_project_fan(client):
|
|
||||||
user = f.UserFactory.create()
|
|
||||||
project = f.ProjectFactory.create(owner=user)
|
|
||||||
fan = f.VoteFactory.create(content_object=project)
|
|
||||||
url = reverse("project-fans-detail", args=(project.id, fan.user.id))
|
|
||||||
|
|
||||||
client.login(user)
|
|
||||||
response = client.get(url)
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.data['id'] == fan.user.id
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_user_starred_projects(client):
|
def test_list_user_starred_projects(client):
|
||||||
user = f.UserFactory.create()
|
user = f.UserFactory.create()
|
||||||
project = f.ProjectFactory()
|
project = f.ProjectFactory()
|
||||||
url = reverse("user-starred-list", args=(user.id,))
|
url = reverse("users-starred", args=(user.id,))
|
||||||
f.VoteFactory.create(user=user, content_object=project)
|
f.VoteFactory.create(user=user, content_object=project)
|
||||||
|
|
||||||
client.login(user)
|
client.login(user)
|
||||||
|
@ -110,19 +97,6 @@ def test_list_user_starred_projects(client):
|
||||||
assert response.data[0]['id'] == project.id
|
assert response.data[0]['id'] == project.id
|
||||||
|
|
||||||
|
|
||||||
def test_get_user_starred_project(client):
|
|
||||||
user = f.UserFactory.create()
|
|
||||||
project = f.ProjectFactory()
|
|
||||||
url = reverse("user-starred-detail", args=(user.id, project.id))
|
|
||||||
f.VoteFactory.create(user=user, content_object=project)
|
|
||||||
|
|
||||||
client.login(user)
|
|
||||||
response = client.get(url)
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.data['id'] == project.id
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_project_stars(client):
|
def test_get_project_stars(client):
|
||||||
user = f.UserFactory.create()
|
user = f.UserFactory.create()
|
||||||
project = f.ProjectFactory.create(owner=user)
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
|
|
@ -25,17 +25,18 @@ from .. import factories
|
||||||
pytestmark = pytest.mark.django_db
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
def test_list_userstories(client):
|
def test_list_userstorage(client):
|
||||||
user1 = factories.UserFactory()
|
user1 = factories.UserFactory()
|
||||||
user2 = factories.UserFactory()
|
user2 = factories.UserFactory()
|
||||||
storage11 = factories.StorageEntryFactory(owner=user1)
|
storage11 = factories.StorageEntryFactory(owner=user1)
|
||||||
storage12 = factories.StorageEntryFactory(owner=user1)
|
factories.StorageEntryFactory(owner=user1)
|
||||||
storage13 = factories.StorageEntryFactory(owner=user1)
|
storage13 = factories.StorageEntryFactory(owner=user1)
|
||||||
storage21 = factories.StorageEntryFactory(owner=user2)
|
factories.StorageEntryFactory(owner=user2)
|
||||||
|
|
||||||
# List by anonumous user
|
# List by anonumous user
|
||||||
response = client.get(reverse("user-storage-list"))
|
response = client.get(reverse("user-storage-list"))
|
||||||
assert response.status_code == 401
|
assert response.status_code == 200
|
||||||
|
assert len(response.data) == 0
|
||||||
|
|
||||||
# List own entries
|
# List own entries
|
||||||
client.login(username=user1.username, password=user1.username)
|
client.login(username=user1.username, password=user1.username)
|
||||||
|
@ -57,8 +58,6 @@ def test_list_userstories(client):
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert len(response.data) == 2
|
assert len(response.data) == 2
|
||||||
|
|
||||||
client.logout()
|
|
||||||
|
|
||||||
|
|
||||||
def test_view_storage_entries(client):
|
def test_view_storage_entries(client):
|
||||||
user1 = factories.UserFactory()
|
user1 = factories.UserFactory()
|
||||||
|
@ -84,12 +83,9 @@ def test_view_storage_entries(client):
|
||||||
response = client.get(reverse("user-storage-detail", args=["foobar"]))
|
response = client.get(reverse("user-storage-detail", args=["foobar"]))
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
client.logout()
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_entries(client):
|
def test_create_entries(client):
|
||||||
user1 = factories.UserFactory()
|
user1 = factories.UserFactory()
|
||||||
user2 = factories.UserFactory()
|
|
||||||
storage11 = factories.StorageEntryFactory(owner=user1)
|
storage11 = factories.StorageEntryFactory(owner=user1)
|
||||||
|
|
||||||
form = {"key": "foo",
|
form = {"key": "foo",
|
||||||
|
@ -119,12 +115,9 @@ def test_create_entries(client):
|
||||||
response = client.post(reverse("user-storage-list"), error_form)
|
response = client.post(reverse("user-storage-list"), error_form)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
client.logout()
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_entries(client):
|
def test_update_entries(client):
|
||||||
user1 = factories.UserFactory()
|
user1 = factories.UserFactory()
|
||||||
user2 = factories.UserFactory()
|
|
||||||
storage11 = factories.StorageEntryFactory(owner=user1)
|
storage11 = factories.StorageEntryFactory(owner=user1)
|
||||||
|
|
||||||
# Update by anonymous user
|
# Update by anonymous user
|
||||||
|
@ -158,9 +151,6 @@ def test_update_entries(client):
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.data["value"] == form["value"]
|
assert response.data["value"] == form["value"]
|
||||||
|
|
||||||
client.logout()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_storage_entry(client):
|
def test_delete_storage_entry(client):
|
||||||
user1 = factories.UserFactory()
|
user1 = factories.UserFactory()
|
||||||
|
@ -186,6 +176,3 @@ def test_delete_storage_entry(client):
|
||||||
client.login(username=user2.username, password=user2.username)
|
client.login(username=user2.username, password=user2.username)
|
||||||
response = client.delete(reverse("user-storage-detail", args=[storage11.key]))
|
response = client.delete(reverse("user-storage-detail", args=[storage11.key]))
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
client.logout()
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
from taiga.base.api.permissions import (PermissionComponent,
|
||||||
|
AllowAny as TruePermissionComponent,
|
||||||
|
DenyAll as FalsePermissionComponent)
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_permission_component_composition():
|
||||||
|
assert (TruePermissionComponent() | TruePermissionComponent()).check_permissions(None, None, None)
|
||||||
|
assert (TruePermissionComponent() | FalsePermissionComponent()).check_permissions(None, None, None)
|
||||||
|
assert (FalsePermissionComponent() | TruePermissionComponent()).check_permissions(None, None, None)
|
||||||
|
assert not (FalsePermissionComponent() | FalsePermissionComponent()).check_permissions(None, None, None)
|
||||||
|
|
||||||
|
assert (TruePermissionComponent() & TruePermissionComponent()).check_permissions(None, None, None)
|
||||||
|
assert not (TruePermissionComponent() & FalsePermissionComponent()).check_permissions(None, None, None)
|
||||||
|
assert not (FalsePermissionComponent() & TruePermissionComponent()).check_permissions(None, None, None)
|
||||||
|
assert not (FalsePermissionComponent() & FalsePermissionComponent()).check_permissions(None, None, None)
|
||||||
|
|
||||||
|
assert (~FalsePermissionComponent()).check_permissions(None, None, None)
|
||||||
|
assert not (~TruePermissionComponent()).check_permissions(None, None, None)
|
|
@ -0,0 +1,11 @@
|
||||||
|
from taiga.permissions import service
|
||||||
|
from taiga.users.models import Role
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_role_has_perm():
|
||||||
|
role = Role()
|
||||||
|
role.permissions = ["test"]
|
||||||
|
assert service.role_has_perm(role, "test")
|
||||||
|
assert service.role_has_perm(role, "false") == False
|
|
@ -15,6 +15,7 @@
|
||||||
# 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/>.
|
||||||
import functools
|
import functools
|
||||||
|
import json
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
|
@ -86,3 +87,36 @@ class SettingsTestCase(object):
|
||||||
def teardown_class(cls):
|
def teardown_class(cls):
|
||||||
override_settings(cls.ORIGINAL_SETTINGS)
|
override_settings(cls.ORIGINAL_SETTINGS)
|
||||||
cls.OVERRIDE_SETTINGS.clear()
|
cls.OVERRIDE_SETTINGS.clear()
|
||||||
|
|
||||||
|
def _helper_test_http_method_responses(client, method, url, data, users, after_each_request=None):
|
||||||
|
results = []
|
||||||
|
for user in users:
|
||||||
|
if user is None:
|
||||||
|
client.logout()
|
||||||
|
else:
|
||||||
|
client.login(user)
|
||||||
|
if data:
|
||||||
|
response = getattr(client, method)(url, data, content_type="application/json")
|
||||||
|
else:
|
||||||
|
response = getattr(client, method)(url)
|
||||||
|
if response.status_code == 400:
|
||||||
|
print(response.content)
|
||||||
|
|
||||||
|
results.append(response)
|
||||||
|
|
||||||
|
if after_each_request is not None:
|
||||||
|
after_each_request()
|
||||||
|
return results
|
||||||
|
|
||||||
|
def helper_test_http_method(client, method, url, data, users, after_each_request=None):
|
||||||
|
responses = _helper_test_http_method_responses(client, method, url, data, users, after_each_request)
|
||||||
|
return list(map(lambda r: r.status_code, responses))
|
||||||
|
|
||||||
|
|
||||||
|
def helper_test_http_method_and_count(client, method, url, data, users, after_each_request=None):
|
||||||
|
responses = _helper_test_http_method_responses(client, method, url, data, users, after_each_request)
|
||||||
|
return list(map(lambda r: (r.status_code, len(json.loads(r.content.decode('utf-8')))), responses))
|
||||||
|
|
||||||
|
def helper_test_http_method_and_keys(client, method, url, data, users, after_each_request=None):
|
||||||
|
responses = _helper_test_http_method_responses(client, method, url, data, users, after_each_request)
|
||||||
|
return list(map(lambda r: (r.status_code, set(json.loads(r.content.decode('utf-8')).keys())), responses))
|
||||||
|
|
Loading…
Reference in New Issue