Merge pull request #250 from taigaio/refactor/bameda-style
Refactor day: @bameda styleremotes/origin/enhancement/email-actions
commit
6553b4d8dc
|
@ -20,13 +20,13 @@ from enum import Enum
|
||||||
from django.utils.translation import ugettext_lazy as _
|
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 import status
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from taiga.base.api import viewsets
|
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 import response
|
||||||
|
|
||||||
from taiga.users.services import get_and_validate_user
|
from taiga.users.services import get_and_validate_user
|
||||||
|
|
||||||
from .serializers import PublicRegisterSerializer
|
from .serializers import PublicRegisterSerializer
|
||||||
|
@ -108,7 +108,7 @@ class AuthViewSet(viewsets.ViewSet):
|
||||||
raise exc.BadRequest(e.detail)
|
raise exc.BadRequest(e.detail)
|
||||||
|
|
||||||
data = make_auth_response_data(user)
|
data = make_auth_response_data(user)
|
||||||
return Response(data, status=status.HTTP_201_CREATED)
|
return response.Created(data)
|
||||||
|
|
||||||
def _private_register(self, request):
|
def _private_register(self, request):
|
||||||
register_type = parse_register_type(request.DATA)
|
register_type = parse_register_type(request.DATA)
|
||||||
|
@ -121,7 +121,7 @@ class AuthViewSet(viewsets.ViewSet):
|
||||||
user = private_register_for_new_user(**data)
|
user = private_register_for_new_user(**data)
|
||||||
|
|
||||||
data = make_auth_response_data(user)
|
data = make_auth_response_data(user)
|
||||||
return Response(data, status=status.HTTP_201_CREATED)
|
return response.Created(data)
|
||||||
|
|
||||||
@list_route(methods=["POST"])
|
@list_route(methods=["POST"])
|
||||||
def register(self, request, **kwargs):
|
def register(self, request, **kwargs):
|
||||||
|
@ -134,7 +134,6 @@ class AuthViewSet(viewsets.ViewSet):
|
||||||
return self._private_register(request)
|
return self._private_register(request)
|
||||||
raise exc.BadRequest(_("invalid register type"))
|
raise exc.BadRequest(_("invalid register type"))
|
||||||
|
|
||||||
|
|
||||||
# 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)
|
self.check_permissions(request, 'create', None)
|
||||||
|
|
|
@ -29,12 +29,10 @@ from django.db import transaction as tx
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework import status
|
|
||||||
|
|
||||||
from djmail.template_mail import MagicMailBuilder, InlineCSSTemplateMail
|
from djmail.template_mail import MagicMailBuilder, InlineCSSTemplateMail
|
||||||
|
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
|
from taiga.base import response
|
||||||
from taiga.users.serializers import UserSerializer
|
from taiga.users.serializers import UserSerializer
|
||||||
from taiga.users.services import get_and_validate_user
|
from taiga.users.services import get_and_validate_user
|
||||||
from taiga.base.utils.slug import slugify_uniquely
|
from taiga.base.utils.slug import slugify_uniquely
|
||||||
|
@ -203,7 +201,7 @@ def normal_login_func(request):
|
||||||
|
|
||||||
user = get_and_validate_user(username=username, password=password)
|
user = get_and_validate_user(username=username, password=password)
|
||||||
data = make_auth_response_data(user)
|
data = make_auth_response_data(user)
|
||||||
return Response(data, status=status.HTTP_200_OK)
|
return response.Ok(data)
|
||||||
|
|
||||||
|
|
||||||
register_auth_plugin("normal", normal_login_func);
|
register_auth_plugin("normal", normal_login_func);
|
||||||
|
|
|
@ -23,9 +23,7 @@ from django.core.exceptions import ValidationError
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.db import transaction as tx
|
from django.db import transaction as tx
|
||||||
|
|
||||||
from rest_framework import status
|
from taiga.base import response
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.request import clone_request
|
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
|
|
||||||
from .utils import get_object_or_404
|
from .utils import get_object_or_404
|
||||||
|
@ -73,10 +71,9 @@ class CreateModelMixin(object):
|
||||||
self.object = serializer.save(force_insert=True)
|
self.object = serializer.save(force_insert=True)
|
||||||
self.post_save(self.object, created=True)
|
self.post_save(self.object, created=True)
|
||||||
headers = self.get_success_headers(serializer.data)
|
headers = self.get_success_headers(serializer.data)
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED,
|
return response.Created(serializer.data, headers=headers)
|
||||||
headers=headers)
|
|
||||||
|
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return response.BadRequest(serializer.errors)
|
||||||
|
|
||||||
def get_success_headers(self, data):
|
def get_success_headers(self, data):
|
||||||
try:
|
try:
|
||||||
|
@ -114,7 +111,7 @@ class ListModelMixin(object):
|
||||||
else:
|
else:
|
||||||
serializer = self.get_serializer(self.object_list, many=True)
|
serializer = self.get_serializer(self.object_list, many=True)
|
||||||
|
|
||||||
return Response(serializer.data)
|
return response.Ok(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
class RetrieveModelMixin(object):
|
class RetrieveModelMixin(object):
|
||||||
|
@ -130,7 +127,7 @@ class RetrieveModelMixin(object):
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
serializer = self.get_serializer(self.object)
|
serializer = self.get_serializer(self.object)
|
||||||
return Response(serializer.data)
|
return response.Ok(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
class UpdateModelMixin(object):
|
class UpdateModelMixin(object):
|
||||||
|
@ -149,7 +146,7 @@ class UpdateModelMixin(object):
|
||||||
files=request.FILES, partial=partial)
|
files=request.FILES, partial=partial)
|
||||||
|
|
||||||
if not serializer.is_valid():
|
if not serializer.is_valid():
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return response.BadRequest(serializer.errors)
|
||||||
|
|
||||||
# Hooks
|
# Hooks
|
||||||
try:
|
try:
|
||||||
|
@ -158,16 +155,16 @@ class UpdateModelMixin(object):
|
||||||
except ValidationError as err:
|
except ValidationError as err:
|
||||||
# full_clean on model instance may be called in pre_save,
|
# full_clean on model instance may be called in pre_save,
|
||||||
# so we have to handle eventual errors.
|
# so we have to handle eventual errors.
|
||||||
return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
|
return response.BadRequest(err.message_dict)
|
||||||
|
|
||||||
if self.object is None:
|
if self.object is None:
|
||||||
self.object = serializer.save(force_insert=True)
|
self.object = serializer.save(force_insert=True)
|
||||||
self.post_save(self.object, created=True)
|
self.post_save(self.object, created=True)
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return response.Created(serializer.data)
|
||||||
|
|
||||||
self.object = serializer.save(force_update=True)
|
self.object = serializer.save(force_update=True)
|
||||||
self.post_save(self.object, created=False)
|
self.post_save(self.object, created=False)
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return response.Ok(serializer.data)
|
||||||
|
|
||||||
def partial_update(self, request, *args, **kwargs):
|
def partial_update(self, request, *args, **kwargs):
|
||||||
kwargs['partial'] = True
|
kwargs['partial'] = True
|
||||||
|
@ -216,4 +213,4 @@ class DestroyModelMixin(object):
|
||||||
self.pre_conditions_on_delete(obj)
|
self.pre_conditions_on_delete(obj)
|
||||||
obj.delete()
|
obj.delete()
|
||||||
self.post_delete(obj)
|
self.post_delete(obj)
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return response.NoContent()
|
||||||
|
|
|
@ -27,10 +27,13 @@ from django.views.decorators.csrf import csrf_exempt
|
||||||
from rest_framework import status, exceptions
|
from rest_framework import status, exceptions
|
||||||
from rest_framework.compat import smart_text, HttpResponseBase, View
|
from rest_framework.compat import smart_text, HttpResponseBase, View
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
from rest_framework.utils import formatting
|
from rest_framework.utils import formatting
|
||||||
|
|
||||||
|
from taiga.base.response import Response
|
||||||
|
from taiga.base.response import Ok
|
||||||
|
from taiga.base.response import NotFound
|
||||||
|
from taiga.base.response import Forbidden
|
||||||
from taiga.base.utils.iterators import as_tuple
|
from taiga.base.utils.iterators import as_tuple
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -89,12 +92,10 @@ def exception_handler(exc):
|
||||||
headers=headers)
|
headers=headers)
|
||||||
|
|
||||||
elif isinstance(exc, Http404):
|
elif isinstance(exc, Http404):
|
||||||
return Response({'detail': 'Not found'},
|
return NotFound({'detail': 'Not found'})
|
||||||
status=status.HTTP_404_NOT_FOUND)
|
|
||||||
|
|
||||||
elif isinstance(exc, PermissionDenied):
|
elif isinstance(exc, PermissionDenied):
|
||||||
return Response({'detail': 'Permission denied'},
|
return Forbidden({'detail': 'Permission denied'})
|
||||||
status=status.HTTP_403_FORBIDDEN)
|
|
||||||
|
|
||||||
# Note: Unhandled exceptions will raise a 500 error.
|
# Note: Unhandled exceptions will raise a 500 error.
|
||||||
return None
|
return None
|
||||||
|
@ -425,7 +426,7 @@ class APIView(View):
|
||||||
We may as well implement this as Django will otherwise provide
|
We may as well implement this as Django will otherwise provide
|
||||||
a less useful default implementation.
|
a less useful default implementation.
|
||||||
"""
|
"""
|
||||||
return Response(self.metadata(request), status=status.HTTP_200_OK)
|
return Ok(self.metadata(request))
|
||||||
|
|
||||||
def metadata(self, request):
|
def metadata(self, request):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -14,17 +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 rest_framework import exceptions
|
from rest_framework import exceptions
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
|
||||||
|
|
||||||
from django.core.exceptions import PermissionDenied as DjangoPermissionDenied
|
from django.core.exceptions import PermissionDenied as DjangoPermissionDenied
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
|
|
||||||
from .utils.json import to_json
|
from taiga.base import response
|
||||||
|
from taiga.base.utils.json import to_json
|
||||||
|
|
||||||
|
|
||||||
class BaseException(exceptions.APIException):
|
class BaseException(exceptions.APIException):
|
||||||
|
@ -129,15 +128,13 @@ def exception_handler(exc):
|
||||||
headers["X-Throttle-Wait-Seconds"] = "%d" % exc.wait
|
headers["X-Throttle-Wait-Seconds"] = "%d" % exc.wait
|
||||||
|
|
||||||
detail = format_exception(exc)
|
detail = format_exception(exc)
|
||||||
return Response(detail, status=exc.status_code, headers=headers)
|
return response.Response(detail, status=exc.status_code, headers=headers)
|
||||||
|
|
||||||
elif isinstance(exc, Http404):
|
elif isinstance(exc, Http404):
|
||||||
return Response({'_error_message': str(exc)},
|
return response.NotFound({'_error_message': str(exc)})
|
||||||
status=status.HTTP_404_NOT_FOUND)
|
|
||||||
|
|
||||||
elif isinstance(exc, DjangoPermissionDenied):
|
elif isinstance(exc, DjangoPermissionDenied):
|
||||||
return Response({"_error_message": str(exc)},
|
return response.Forbidden({"_error_message": str(exc)})
|
||||||
status=status.HTTP_403_FORBIDDEN)
|
|
||||||
|
|
||||||
# Note: Unhandled exceptions will raise a 500 error.
|
# Note: Unhandled exceptions will raise a 500 error.
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -17,15 +17,13 @@ import operator
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db.models.sql.where import ExtraWhere, OR, AND
|
|
||||||
|
|
||||||
from rest_framework import filters
|
from rest_framework import filters
|
||||||
|
|
||||||
from taiga.base import tags
|
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
|
|
||||||
from taiga.projects.models import Membership
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -101,7 +99,8 @@ class PermissionBasedFilterBackend(FilterBackend):
|
||||||
try:
|
try:
|
||||||
project_id = int(request.QUERY_PARAMS["project"])
|
project_id = int(request.QUERY_PARAMS["project"])
|
||||||
except:
|
except:
|
||||||
logger.error("Filtering project diferent value than an integer: {}".format(request.QUERY_PARAMS["project"]))
|
logger.error("Filtering project diferent value than an integer: {}".format(
|
||||||
|
request.QUERY_PARAMS["project"]))
|
||||||
raise exc.BadRequest("'project' must be an integer value.")
|
raise exc.BadRequest("'project' must be an integer value.")
|
||||||
|
|
||||||
qs = queryset
|
qs = queryset
|
||||||
|
@ -109,7 +108,8 @@ class PermissionBasedFilterBackend(FilterBackend):
|
||||||
if request.user.is_authenticated() and request.user.is_superuser:
|
if request.user.is_authenticated() and request.user.is_superuser:
|
||||||
qs = qs
|
qs = qs
|
||||||
elif request.user.is_authenticated():
|
elif request.user.is_authenticated():
|
||||||
memberships_qs = Membership.objects.filter(user=request.user)
|
membership_model = apps.get_model('projects', 'Membership')
|
||||||
|
memberships_qs = membership_model.objects.filter(user=request.user)
|
||||||
if project_id:
|
if project_id:
|
||||||
memberships_qs = memberships_qs.filter(project_id=project_id)
|
memberships_qs = memberships_qs.filter(project_id=project_id)
|
||||||
memberships_qs = memberships_qs.filter(Q(role__permissions__contains=[self.permission]) |
|
memberships_qs = memberships_qs.filter(Q(role__permissions__contains=[self.permission]) |
|
||||||
|
@ -187,7 +187,8 @@ class CanViewProjectObjFilterBackend(FilterBackend):
|
||||||
try:
|
try:
|
||||||
project_id = int(request.QUERY_PARAMS["project"])
|
project_id = int(request.QUERY_PARAMS["project"])
|
||||||
except:
|
except:
|
||||||
logger.error("Filtering project diferent value than an integer: {}".format(request.QUERY_PARAMS["project"]))
|
logger.error("Filtering project diferent value than an integer: {}".format(
|
||||||
|
request.QUERY_PARAMS["project"]))
|
||||||
raise exc.BadRequest("'project' must be an integer value.")
|
raise exc.BadRequest("'project' must be an integer value.")
|
||||||
|
|
||||||
qs = queryset
|
qs = queryset
|
||||||
|
@ -195,7 +196,8 @@ class CanViewProjectObjFilterBackend(FilterBackend):
|
||||||
if request.user.is_authenticated() and request.user.is_superuser:
|
if request.user.is_authenticated() and request.user.is_superuser:
|
||||||
qs = qs
|
qs = qs
|
||||||
elif request.user.is_authenticated():
|
elif request.user.is_authenticated():
|
||||||
memberships_qs = Membership.objects.filter(user=request.user)
|
membership_model = apps.get_model("projects", "Membership")
|
||||||
|
memberships_qs = membership_model.objects.filter(user=request.user)
|
||||||
if project_id:
|
if project_id:
|
||||||
memberships_qs = memberships_qs.filter(project_id=project_id)
|
memberships_qs = memberships_qs.filter(project_id=project_id)
|
||||||
memberships_qs = memberships_qs.filter(Q(role__permissions__contains=['view_project']) |
|
memberships_qs = memberships_qs.filter(Q(role__permissions__contains=['view_project']) |
|
||||||
|
@ -203,7 +205,8 @@ class CanViewProjectObjFilterBackend(FilterBackend):
|
||||||
|
|
||||||
projects_list = [membership.project_id for membership in memberships_qs]
|
projects_list = [membership.project_id for membership in memberships_qs]
|
||||||
|
|
||||||
qs = qs.filter(Q(id__in=projects_list) | Q(public_permissions__contains=["view_project"]))
|
qs = qs.filter((Q(id__in=projects_list) |
|
||||||
|
Q(public_permissions__contains=["view_project"])))
|
||||||
else:
|
else:
|
||||||
qs = qs.filter(public_permissions__contains=["view_project"])
|
qs = qs.filter(public_permissions__contains=["view_project"])
|
||||||
|
|
||||||
|
@ -221,6 +224,25 @@ class IsProjectMemberFilterBackend(FilterBackend):
|
||||||
|
|
||||||
return super().filter_queryset(request, queryset.distinct(), view)
|
return super().filter_queryset(request, queryset.distinct(), view)
|
||||||
|
|
||||||
|
|
||||||
|
class MembersFilterBackend(filters.BaseFilterBackend):
|
||||||
|
def filter_queryset(self, request, queryset, view):
|
||||||
|
project_id = request.QUERY_PARAMS.get('project', None)
|
||||||
|
if project_id:
|
||||||
|
project_model = apps.get_model('projects', 'Project')
|
||||||
|
project = get_object_or_404(project_model, pk=project_id)
|
||||||
|
if (request.user.is_authenticated() and
|
||||||
|
project.memberships.filter(user=request.user).exists()):
|
||||||
|
return queryset.filter(memberships__project=project).distinct()
|
||||||
|
else:
|
||||||
|
raise exc.PermissionDenied(_("You don't have permisions to see this project users."))
|
||||||
|
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
class BaseIsProjectAdminFilterBackend(object):
|
class BaseIsProjectAdminFilterBackend(object):
|
||||||
def get_project_ids(self, request, view):
|
def get_project_ids(self, request, view):
|
||||||
project_id = None
|
project_id = None
|
||||||
|
@ -233,7 +255,8 @@ class BaseIsProjectAdminFilterBackend(object):
|
||||||
if not request.user.is_authenticated():
|
if not request.user.is_authenticated():
|
||||||
return []
|
return []
|
||||||
|
|
||||||
memberships_qs = Membership.objects.filter(user=request.user, is_owner=True)
|
membership_model = apps.get_model('projects', 'Membership')
|
||||||
|
memberships_qs = membership_model.objects.filter(user=request.user, is_owner=True)
|
||||||
if project_id:
|
if project_id:
|
||||||
memberships_qs = memberships_qs.filter(project_id=project_id)
|
memberships_qs = memberships_qs.filter(project_id=project_id)
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.urlresolvers import NoReverseMatch
|
from django.core.urlresolvers import NoReverseMatch
|
||||||
|
|
||||||
from rest_framework import views
|
from rest_framework import views
|
||||||
from rest_framework.response import Response
|
from taiga.base import response
|
||||||
from rest_framework.reverse import reverse
|
from rest_framework.reverse import reverse
|
||||||
from rest_framework.urlpatterns import format_suffix_patterns
|
from rest_framework.urlpatterns import format_suffix_patterns
|
||||||
|
|
||||||
|
@ -292,7 +292,7 @@ class DRFDefaultRouter(SimpleRouter):
|
||||||
except NoReverseMatch:
|
except NoReverseMatch:
|
||||||
# Support resources that are prefixed by a parametrized url
|
# Support resources that are prefixed by a parametrized url
|
||||||
ret[key] = request.build_absolute_uri() + key
|
ret[key] = request.build_absolute_uri() + key
|
||||||
return Response(ret)
|
return response.Response(ret)
|
||||||
|
|
||||||
return APIRoot.as_view()
|
return APIRoot.as_view()
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,6 @@ import json
|
||||||
import codecs
|
import codecs
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.decorators import throttle_classes
|
|
||||||
from rest_framework import status
|
|
||||||
|
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.db.transaction import atomic
|
from django.db.transaction import atomic
|
||||||
|
@ -30,10 +26,13 @@ from django.conf import settings
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
|
|
||||||
from taiga.base.api.mixins import CreateModelMixin
|
from rest_framework.decorators import throttle_classes
|
||||||
from taiga.base.api.viewsets import GenericViewSet
|
|
||||||
from taiga.base.decorators import detail_route, list_route
|
from taiga.base.decorators import detail_route, list_route
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
|
from taiga.base import response
|
||||||
|
from taiga.base.api.mixins import CreateModelMixin
|
||||||
|
from taiga.base.api.viewsets import GenericViewSet
|
||||||
from taiga.projects.models import Project, Membership
|
from taiga.projects.models import Project, Membership
|
||||||
from taiga.projects.issues.models import Issue
|
from taiga.projects.issues.models import Issue
|
||||||
from taiga.projects.serializers import ProjectSerializer
|
from taiga.projects.serializers import ProjectSerializer
|
||||||
|
@ -65,8 +64,9 @@ class ProjectExporterViewSet(mixins.ImportThrottlingPolicyMixin, GenericViewSet)
|
||||||
|
|
||||||
if settings.CELERY_ENABLED:
|
if settings.CELERY_ENABLED:
|
||||||
task = tasks.dump_project.delay(request.user, project)
|
task = tasks.dump_project.delay(request.user, project)
|
||||||
tasks.delete_project_dump.apply_async((project.pk, project.slug), countdown=settings.EXPORTS_TTL)
|
tasks.delete_project_dump.apply_async((project.pk, project.slug),
|
||||||
return Response({"export_id": task.id}, status=status.HTTP_202_ACCEPTED)
|
countdown=settings.EXPORTS_TTL)
|
||||||
|
return response.Accepted({"export_id": task.id})
|
||||||
|
|
||||||
path = "exports/{}/{}-{}.json".format(project.pk, project.slug, uuid.uuid4().hex)
|
path = "exports/{}/{}-{}.json".format(project.pk, project.slug, uuid.uuid4().hex)
|
||||||
content = ContentFile(ExportRenderer().render(service.project_to_dict(project),
|
content = ContentFile(ExportRenderer().render(service.project_to_dict(project),
|
||||||
|
@ -76,7 +76,7 @@ class ProjectExporterViewSet(mixins.ImportThrottlingPolicyMixin, GenericViewSet)
|
||||||
response_data = {
|
response_data = {
|
||||||
"url": default_storage.url(path)
|
"url": default_storage.url(path)
|
||||||
}
|
}
|
||||||
return Response(response_data, status=status.HTTP_200_OK)
|
return response.Ok(response_data)
|
||||||
|
|
||||||
|
|
||||||
class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixin, GenericViewSet):
|
class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixin, GenericViewSet):
|
||||||
|
@ -152,7 +152,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
|
||||||
response_data = project_serialized.data
|
response_data = project_serialized.data
|
||||||
response_data['id'] = project_serialized.object.id
|
response_data['id'] = project_serialized.object.id
|
||||||
headers = self.get_success_headers(response_data)
|
headers = self.get_success_headers(response_data)
|
||||||
return Response(response_data, status=status.HTTP_201_CREATED, headers=headers)
|
return response.Created(response_data, headers=headers)
|
||||||
|
|
||||||
@list_route(methods=["POST"])
|
@list_route(methods=["POST"])
|
||||||
@method_decorator(atomic)
|
@method_decorator(atomic)
|
||||||
|
@ -181,11 +181,11 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
|
||||||
|
|
||||||
if settings.CELERY_ENABLED:
|
if settings.CELERY_ENABLED:
|
||||||
task = tasks.load_project_dump.delay(request.user, dump)
|
task = tasks.load_project_dump.delay(request.user, dump)
|
||||||
return Response({"import_id": task.id}, status=status.HTTP_202_ACCEPTED)
|
return response.Accepted({"import_id": task.id})
|
||||||
|
|
||||||
project = dump_service.dict_to_project(dump, request.user.email)
|
project = dump_service.dict_to_project(dump, request.user.email)
|
||||||
response_data = ProjectSerializer(project).data
|
response_data = ProjectSerializer(project).data
|
||||||
return Response(response_data, status=status.HTTP_201_CREATED)
|
return response.Created(response_data)
|
||||||
|
|
||||||
|
|
||||||
@detail_route(methods=['post'])
|
@detail_route(methods=['post'])
|
||||||
|
@ -204,7 +204,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
|
||||||
raise exc.BadRequest(errors)
|
raise exc.BadRequest(errors)
|
||||||
|
|
||||||
headers = self.get_success_headers(issue.data)
|
headers = self.get_success_headers(issue.data)
|
||||||
return Response(issue.data, status=status.HTTP_201_CREATED, headers=headers)
|
return response.Created(issue.data, headers=headers)
|
||||||
|
|
||||||
@detail_route(methods=['post'])
|
@detail_route(methods=['post'])
|
||||||
@method_decorator(atomic)
|
@method_decorator(atomic)
|
||||||
|
@ -219,7 +219,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
|
||||||
raise exc.BadRequest(errors)
|
raise exc.BadRequest(errors)
|
||||||
|
|
||||||
headers = self.get_success_headers(task.data)
|
headers = self.get_success_headers(task.data)
|
||||||
return Response(task.data, status=status.HTTP_201_CREATED, headers=headers)
|
return response.Created(task.data, headers=headers)
|
||||||
|
|
||||||
@detail_route(methods=['post'])
|
@detail_route(methods=['post'])
|
||||||
@method_decorator(atomic)
|
@method_decorator(atomic)
|
||||||
|
@ -234,7 +234,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
|
||||||
raise exc.BadRequest(errors)
|
raise exc.BadRequest(errors)
|
||||||
|
|
||||||
headers = self.get_success_headers(us.data)
|
headers = self.get_success_headers(us.data)
|
||||||
return Response(us.data, status=status.HTTP_201_CREATED, headers=headers)
|
return response.Created(us.data, headers=headers)
|
||||||
|
|
||||||
@detail_route(methods=['post'])
|
@detail_route(methods=['post'])
|
||||||
@method_decorator(atomic)
|
@method_decorator(atomic)
|
||||||
|
@ -249,7 +249,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
|
||||||
raise exc.BadRequest(errors)
|
raise exc.BadRequest(errors)
|
||||||
|
|
||||||
headers = self.get_success_headers(milestone.data)
|
headers = self.get_success_headers(milestone.data)
|
||||||
return Response(milestone.data, status=status.HTTP_201_CREATED, headers=headers)
|
return response.Created(milestone.data, headers=headers)
|
||||||
|
|
||||||
@detail_route(methods=['post'])
|
@detail_route(methods=['post'])
|
||||||
@method_decorator(atomic)
|
@method_decorator(atomic)
|
||||||
|
@ -264,7 +264,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
|
||||||
raise exc.BadRequest(errors)
|
raise exc.BadRequest(errors)
|
||||||
|
|
||||||
headers = self.get_success_headers(wiki_page.data)
|
headers = self.get_success_headers(wiki_page.data)
|
||||||
return Response(wiki_page.data, status=status.HTTP_201_CREATED, headers=headers)
|
return response.Created(wiki_page.data, headers=headers)
|
||||||
|
|
||||||
@detail_route(methods=['post'])
|
@detail_route(methods=['post'])
|
||||||
@method_decorator(atomic)
|
@method_decorator(atomic)
|
||||||
|
@ -279,4 +279,4 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
|
||||||
raise exc.BadRequest(errors)
|
raise exc.BadRequest(errors)
|
||||||
|
|
||||||
headers = self.get_success_headers(wiki_link.data)
|
headers = self.get_success_headers(wiki_link.data)
|
||||||
return Response(wiki_link.data, status=status.HTTP_201_CREATED, headers=headers)
|
return response.Created(wiki_link.data, headers=headers)
|
||||||
|
|
|
@ -14,11 +14,11 @@
|
||||||
# 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 rest_framework.response import Response
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from taiga.base.api.viewsets import GenericViewSet
|
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
|
from taiga.base import response
|
||||||
|
from taiga.base.api.viewsets import GenericViewSet
|
||||||
from taiga.base.utils import json
|
from taiga.base.utils import json
|
||||||
from taiga.projects.models import Project
|
from taiga.projects.models import Project
|
||||||
|
|
||||||
|
@ -75,4 +75,4 @@ class BaseWebhookApiViewSet(GenericViewSet):
|
||||||
except ActionSyntaxException as e:
|
except ActionSyntaxException as e:
|
||||||
raise exc.BadRequest(e)
|
raise exc.BadRequest(e)
|
||||||
|
|
||||||
return Response({})
|
return response.NoContent()
|
||||||
|
|
|
@ -14,18 +14,14 @@
|
||||||
# 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 rest_framework.response import Response
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from taiga.base.api.viewsets import GenericViewSet
|
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
from taiga.base.utils import json
|
|
||||||
from taiga.projects.models import Project
|
from taiga.projects.models import Project
|
||||||
from taiga.hooks.api import BaseWebhookApiViewSet
|
from taiga.hooks.api import BaseWebhookApiViewSet
|
||||||
|
|
||||||
from . import event_hooks
|
from . import event_hooks
|
||||||
from ..exceptions import ActionSyntaxException
|
|
||||||
|
|
||||||
from urllib.parse import parse_qs
|
from urllib.parse import parse_qs
|
||||||
from ipware.ip import get_real_ip
|
from ipware.ip import get_real_ip
|
||||||
|
@ -61,7 +57,9 @@ class BitBucketViewSet(BaseWebhookApiViewSet):
|
||||||
if not project_secret:
|
if not project_secret:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
valid_origin_ips = project.modules_config.config.get("bitbucket", {}).get("valid_origin_ips", settings.BITBUCKET_VALID_ORIGIN_IPS)
|
bitbucket_config = project.modules_config.config.get("bitbucket", {})
|
||||||
|
valid_origin_ips = bitbucket_config.get("valid_origin_ips",
|
||||||
|
settings.BITBUCKET_VALID_ORIGIN_IPS)
|
||||||
origin_ip = get_real_ip(request)
|
origin_ip = get_real_ip(request)
|
||||||
if valid_origin_ips and (not origin_ip or not origin_ip in valid_origin_ips):
|
if valid_origin_ips and (not origin_ip or not origin_ip in valid_origin_ips):
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -14,13 +14,6 @@
|
||||||
# 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 rest_framework.response import Response
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from taiga.base.api.viewsets import GenericViewSet
|
|
||||||
from taiga.base import exceptions as exc
|
|
||||||
from taiga.base.utils import json
|
|
||||||
from taiga.projects.models import Project
|
|
||||||
from taiga.hooks.api import BaseWebhookApiViewSet
|
from taiga.hooks.api import BaseWebhookApiViewSet
|
||||||
|
|
||||||
from . import event_hooks
|
from . import event_hooks
|
||||||
|
@ -51,7 +44,8 @@ class GitHubViewSet(BaseWebhookApiViewSet):
|
||||||
if project.modules_config.config is None:
|
if project.modules_config.config is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
secret = bytes(project.modules_config.config.get("github", {}).get("secret", "").encode("utf-8"))
|
secret = project.modules_config.config.get("github", {}).get("secret", "")
|
||||||
|
secret = bytes(secret.encode("utf-8"))
|
||||||
mac = hmac.new(secret, msg=request.body,digestmod=hashlib.sha1)
|
mac = hmac.new(secret, msg=request.body,digestmod=hashlib.sha1)
|
||||||
return hmac.compare_digest(mac.hexdigest(), signature)
|
return hmac.compare_digest(mac.hexdigest(), signature)
|
||||||
|
|
||||||
|
|
|
@ -14,20 +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/>.
|
||||||
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from taiga.base.api.viewsets import GenericViewSet
|
from ipware.ip import get_real_ip
|
||||||
from taiga.base import exceptions as exc
|
|
||||||
from taiga.base.utils import json
|
from taiga.base.utils import json
|
||||||
|
|
||||||
from taiga.projects.models import Project
|
from taiga.projects.models import Project
|
||||||
from taiga.hooks.api import BaseWebhookApiViewSet
|
from taiga.hooks.api import BaseWebhookApiViewSet
|
||||||
|
|
||||||
from . import event_hooks
|
from . import event_hooks
|
||||||
|
|
||||||
from ipware.ip import get_real_ip
|
|
||||||
|
|
||||||
|
|
||||||
class GitLabViewSet(BaseWebhookApiViewSet):
|
class GitLabViewSet(BaseWebhookApiViewSet):
|
||||||
event_hook_classes = {
|
event_hook_classes = {
|
||||||
|
@ -51,7 +49,8 @@ class GitLabViewSet(BaseWebhookApiViewSet):
|
||||||
if not project_secret:
|
if not project_secret:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
valid_origin_ips = project.modules_config.config.get("gitlab", {}).get("valid_origin_ips", settings.GITLAB_VALID_ORIGIN_IPS)
|
gitlab_config = project.modules_config.config.get("gitlab", {})
|
||||||
|
valid_origin_ips = gitlab_config.get("valid_origin_ips", settings.GITLAB_VALID_ORIGIN_IPS)
|
||||||
origin_ip = get_real_ip(request)
|
origin_ip = get_real_ip(request)
|
||||||
if valid_origin_ips and (not origin_ip or origin_ip not in valid_origin_ips):
|
if valid_origin_ips and (not origin_ip or origin_ip not in valid_origin_ips):
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -20,7 +20,8 @@ from django.db.models import signals
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from taiga.base import filters, response
|
from taiga.base import filters
|
||||||
|
from taiga.base import response
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
from taiga.base.decorators import list_route
|
from taiga.base.decorators import list_route
|
||||||
from taiga.base.decorators import detail_route
|
from taiga.base.decorators import detail_route
|
||||||
|
@ -32,7 +33,6 @@ from taiga.base.utils.slug import slugify_uniquely
|
||||||
from taiga.projects.mixins.ordering import BulkUpdateOrderMixin
|
from taiga.projects.mixins.ordering import BulkUpdateOrderMixin
|
||||||
from taiga.projects.mixins.on_destroy import MoveOnDestroyMixin
|
from taiga.projects.mixins.on_destroy import MoveOnDestroyMixin
|
||||||
|
|
||||||
from taiga.users.models import Role
|
|
||||||
from taiga.projects.userstories.models import UserStory
|
from taiga.projects.userstories.models import UserStory
|
||||||
from taiga.projects.tasks.models import Task
|
from taiga.projects.tasks.models import Task
|
||||||
from taiga.projects.issues.models import Issue
|
from taiga.projects.issues.models import Issue
|
||||||
|
@ -74,7 +74,7 @@ class ProjectViewSet(ModelCrudViewSet):
|
||||||
modules_config = services.get_modules_config(project)
|
modules_config = services.get_modules_config(project)
|
||||||
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
return response.Ok(data=modules_config.config)
|
return response.Ok(modules_config.config)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
modules_config.config.update(request.DATA)
|
modules_config.config.update(request.DATA)
|
||||||
|
@ -85,31 +85,31 @@ class ProjectViewSet(ModelCrudViewSet):
|
||||||
def stats(self, request, pk=None):
|
def stats(self, request, pk=None):
|
||||||
project = self.get_object()
|
project = self.get_object()
|
||||||
self.check_permissions(request, "stats", project)
|
self.check_permissions(request, "stats", project)
|
||||||
return response.Ok(data=services.get_stats_for_project(project))
|
return response.Ok(services.get_stats_for_project(project))
|
||||||
|
|
||||||
@detail_route(methods=["GET"])
|
@detail_route(methods=["GET"])
|
||||||
def member_stats(self, request, pk=None):
|
def member_stats(self, request, pk=None):
|
||||||
project = self.get_object()
|
project = self.get_object()
|
||||||
self.check_permissions(request, "member_stats", project)
|
self.check_permissions(request, "member_stats", project)
|
||||||
return response.Ok(data=services.get_member_stats_for_project(project))
|
return response.Ok(services.get_member_stats_for_project(project))
|
||||||
|
|
||||||
@detail_route(methods=["GET"])
|
@detail_route(methods=["GET"])
|
||||||
def issues_stats(self, request, pk=None):
|
def issues_stats(self, request, pk=None):
|
||||||
project = self.get_object()
|
project = self.get_object()
|
||||||
self.check_permissions(request, "issues_stats", project)
|
self.check_permissions(request, "issues_stats", project)
|
||||||
return response.Ok(data=services.get_stats_for_project_issues(project))
|
return response.Ok(services.get_stats_for_project_issues(project))
|
||||||
|
|
||||||
@detail_route(methods=["GET"])
|
@detail_route(methods=["GET"])
|
||||||
def issue_filters_data(self, request, pk=None):
|
def issue_filters_data(self, request, pk=None):
|
||||||
project = self.get_object()
|
project = self.get_object()
|
||||||
self.check_permissions(request, "issues_filters_data", project)
|
self.check_permissions(request, "issues_filters_data", project)
|
||||||
return response.Ok(data=services.get_issues_filters_data(project))
|
return response.Ok(services.get_issues_filters_data(project))
|
||||||
|
|
||||||
@detail_route(methods=["GET"])
|
@detail_route(methods=["GET"])
|
||||||
def tags_colors(self, request, pk=None):
|
def tags_colors(self, request, pk=None):
|
||||||
project = self.get_object()
|
project = self.get_object()
|
||||||
self.check_permissions(request, "tags_colors", project)
|
self.check_permissions(request, "tags_colors", project)
|
||||||
return response.Ok(data=dict(project.tags_colors))
|
return response.Ok(dict(project.tags_colors))
|
||||||
|
|
||||||
@detail_route(methods=["POST"])
|
@detail_route(methods=["POST"])
|
||||||
def star(self, request, pk=None):
|
def star(self, request, pk=None):
|
||||||
|
@ -132,7 +132,7 @@ class ProjectViewSet(ModelCrudViewSet):
|
||||||
|
|
||||||
voters = votes_service.get_voters(project)
|
voters = votes_service.get_voters(project)
|
||||||
voters_data = votes_serializers.VoterSerializer(voters, many=True)
|
voters_data = votes_serializers.VoterSerializer(voters, many=True)
|
||||||
return response.Ok(data=voters_data.data)
|
return response.Ok(voters_data.data)
|
||||||
|
|
||||||
@detail_route(methods=["POST"])
|
@detail_route(methods=["POST"])
|
||||||
def create_template(self, request, **kwargs):
|
def create_template(self, request, **kwargs):
|
||||||
|
@ -325,7 +325,7 @@ class ProjectTemplateViewSet(ModelCrudViewSet):
|
||||||
|
|
||||||
|
|
||||||
######################################################
|
######################################################
|
||||||
## Members Invitations and Roles
|
## Members & Invitations
|
||||||
######################################################
|
######################################################
|
||||||
|
|
||||||
class MembershipViewSet(ModelCrudViewSet):
|
class MembershipViewSet(ModelCrudViewSet):
|
||||||
|
@ -359,7 +359,7 @@ class MembershipViewSet(ModelCrudViewSet):
|
||||||
return response.BadRequest(err.message_dict)
|
return response.BadRequest(err.message_dict)
|
||||||
|
|
||||||
members_serialized = self.serializer_class(members, many=True)
|
members_serialized = self.serializer_class(members, many=True)
|
||||||
return response.Ok(data=members_serialized.data)
|
return response.Ok(members_serialized.data)
|
||||||
|
|
||||||
@detail_route(methods=["POST"])
|
@detail_route(methods=["POST"])
|
||||||
def resend_invitation(self, request, **kwargs):
|
def resend_invitation(self, request, **kwargs):
|
||||||
|
@ -403,20 +403,3 @@ class InvitationViewSet(ModelListViewSet):
|
||||||
|
|
||||||
def list(self, *args, **kwargs):
|
def list(self, *args, **kwargs):
|
||||||
raise exc.PermissionDenied(_("You don't have permisions to see that."))
|
raise exc.PermissionDenied(_("You don't have permisions to see that."))
|
||||||
|
|
||||||
|
|
||||||
class RolesViewSet(ModelCrudViewSet):
|
|
||||||
model = Role
|
|
||||||
serializer_class = serializers.RoleSerializer
|
|
||||||
permission_classes = (permissions.RolesPermission, )
|
|
||||||
filter_backends = (filters.CanViewProjectFilterBackend,)
|
|
||||||
filter_fields = ('project',)
|
|
||||||
|
|
||||||
def pre_delete(self, obj):
|
|
||||||
move_to = self.request.QUERY_PARAMS.get('moveTo', None)
|
|
||||||
if move_to:
|
|
||||||
role_dest = get_object_or_404(self.model, project=obj.project, id=move_to)
|
|
||||||
qs = models.Membership.objects.filter(project_id=obj.project.pk, role=obj)
|
|
||||||
qs.update(role=role_dest)
|
|
||||||
|
|
||||||
super().pre_delete(obj)
|
|
||||||
|
|
|
@ -21,14 +21,15 @@ import mimetypes
|
||||||
mimetypes.init()
|
mimetypes.init()
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from taiga.base.api.utils import get_object_or_404
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django import http
|
from django import http
|
||||||
|
|
||||||
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.base.api import generics
|
||||||
|
from taiga.base.api import ModelCrudViewSet
|
||||||
|
from taiga.base.api.utils import get_object_or_404
|
||||||
|
|
||||||
from taiga.users.models import User
|
from taiga.users.models import User
|
||||||
|
|
||||||
from taiga.projects.notifications.mixins import WatchedResourceMixin
|
from taiga.projects.notifications.mixins import WatchedResourceMixin
|
||||||
|
|
|
@ -17,12 +17,10 @@
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from rest_framework.response import Response
|
from taiga.base import response
|
||||||
from rest_framework import status
|
|
||||||
|
|
||||||
from taiga.base.api.utils import get_object_or_404
|
|
||||||
from taiga.base.decorators import detail_route
|
from taiga.base.decorators import detail_route
|
||||||
from taiga.base.api import ReadOnlyListViewSet
|
from taiga.base.api import ReadOnlyListViewSet
|
||||||
|
from taiga.base.api.utils import get_object_or_404
|
||||||
|
|
||||||
from . import permissions
|
from . import permissions
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
@ -54,7 +52,7 @@ class HistoryViewSet(ReadOnlyListViewSet):
|
||||||
else:
|
else:
|
||||||
serializer = self.get_serializer(queryset, many=True)
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
|
|
||||||
return Response(serializer.data)
|
return response.Ok(serializer.data)
|
||||||
|
|
||||||
@detail_route(methods=['post'])
|
@detail_route(methods=['post'])
|
||||||
def delete_comment(self, request, pk):
|
def delete_comment(self, request, pk):
|
||||||
|
@ -65,15 +63,15 @@ class HistoryViewSet(ReadOnlyListViewSet):
|
||||||
self.check_permissions(request, 'delete_comment', comment)
|
self.check_permissions(request, 'delete_comment', comment)
|
||||||
|
|
||||||
if comment is None:
|
if comment is None:
|
||||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
return response.NotFound()
|
||||||
|
|
||||||
if comment.delete_comment_date or comment.delete_comment_user:
|
if comment.delete_comment_date or comment.delete_comment_user:
|
||||||
return Response({"error": "Comment already deleted"}, status=status.HTTP_400_BAD_REQUEST)
|
return response.BadRequest({"error": "Comment already deleted"})
|
||||||
|
|
||||||
comment.delete_comment_date = timezone.now()
|
comment.delete_comment_date = timezone.now()
|
||||||
comment.delete_comment_user = {"pk": request.user.pk, "name": request.user.get_full_name()}
|
comment.delete_comment_user = {"pk": request.user.pk, "name": request.user.get_full_name()}
|
||||||
comment.save()
|
comment.save()
|
||||||
return Response(status=status.HTTP_200_OK)
|
return response.Ok()
|
||||||
|
|
||||||
@detail_route(methods=['post'])
|
@detail_route(methods=['post'])
|
||||||
def undelete_comment(self, request, pk):
|
def undelete_comment(self, request, pk):
|
||||||
|
@ -84,20 +82,20 @@ class HistoryViewSet(ReadOnlyListViewSet):
|
||||||
self.check_permissions(request, 'undelete_comment', comment)
|
self.check_permissions(request, 'undelete_comment', comment)
|
||||||
|
|
||||||
if comment is None:
|
if comment is None:
|
||||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
return response.NotFound()
|
||||||
|
|
||||||
if not comment.delete_comment_date and not comment.delete_comment_user:
|
if not comment.delete_comment_date and not comment.delete_comment_user:
|
||||||
return Response({"error": "Comment not deleted"}, status=status.HTTP_400_BAD_REQUEST)
|
return response.BadRequest({"error": "Comment not deleted"})
|
||||||
|
|
||||||
comment.delete_comment_date = None
|
comment.delete_comment_date = None
|
||||||
comment.delete_comment_user = None
|
comment.delete_comment_user = None
|
||||||
comment.save()
|
comment.save()
|
||||||
return Response(status=status.HTTP_200_OK)
|
return response.Ok()
|
||||||
|
|
||||||
# Just for restframework! Because it raises
|
# Just for restframework! Because it raises
|
||||||
# 404 on main api root if this method not exists.
|
# 404 on main api root if this method not exists.
|
||||||
def list(self, request):
|
def list(self, request):
|
||||||
return Response({})
|
return response.NotFound()
|
||||||
|
|
||||||
def retrieve(self, request, pk):
|
def retrieve(self, request, pk):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
|
|
|
@ -18,14 +18,12 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
|
|
||||||
from rest_framework.response import Response
|
from taiga.base import filters
|
||||||
from rest_framework import status
|
|
||||||
|
|
||||||
from taiga.base.api.utils import get_object_or_404
|
|
||||||
from taiga.base import filters, response
|
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
|
from taiga.base import response
|
||||||
from taiga.base.decorators import detail_route, list_route
|
from taiga.base.decorators import detail_route, list_route
|
||||||
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
|
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
|
||||||
|
from taiga.base.api.utils import get_object_or_404
|
||||||
from taiga.base import tags
|
from taiga.base import tags
|
||||||
|
|
||||||
from taiga.users.models import User
|
from taiga.users.models import User
|
||||||
|
@ -139,19 +137,24 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
||||||
super().pre_conditions_on_save(obj)
|
super().pre_conditions_on_save(obj)
|
||||||
|
|
||||||
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 to set this milestone to this issue."))
|
raise exc.PermissionDenied(_("You don't have permissions to set this sprint "
|
||||||
|
"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 to set this status to 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 to set this severity to 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 to set this priority to 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 to set this type to this issue."))
|
raise exc.PermissionDenied(_("You don't have permissions to set this type "
|
||||||
|
"to this issue."))
|
||||||
|
|
||||||
@list_route(methods=["GET"])
|
@list_route(methods=["GET"])
|
||||||
def by_ref(self, request):
|
def by_ref(self, request):
|
||||||
|
@ -185,7 +188,7 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
||||||
self.check_permissions(request, 'upvote', issue)
|
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.Ok()
|
||||||
|
|
||||||
@detail_route(methods=['post'])
|
@detail_route(methods=['post'])
|
||||||
def downvote(self, request, pk=None):
|
def downvote(self, request, pk=None):
|
||||||
|
@ -194,7 +197,7 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
||||||
self.check_permissions(request, 'downvote', issue)
|
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.Ok()
|
||||||
|
|
||||||
|
|
||||||
class VotersViewSet(ModelListViewSet):
|
class VotersViewSet(ModelListViewSet):
|
||||||
|
@ -215,7 +218,7 @@ class VotersViewSet(ModelListViewSet):
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
serializer = self.get_serializer(self.object)
|
serializer = self.get_serializer(self.object)
|
||||||
return Response(serializer.data)
|
return response.Ok(serializer.data)
|
||||||
|
|
||||||
def list(self, request, *args, **kwargs):
|
def list(self, request, *args, **kwargs):
|
||||||
issue_id = kwargs.get("issue_id", None)
|
issue_id = kwargs.get("issue_id", None)
|
||||||
|
|
|
@ -14,16 +14,11 @@
|
||||||
# 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 django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from rest_framework.permissions import IsAuthenticated
|
|
||||||
from rest_framework.response import Response
|
|
||||||
|
|
||||||
from taiga.base.api.utils import get_object_or_404
|
|
||||||
from taiga.base import filters
|
from taiga.base import filters
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import response
|
||||||
from taiga.base.decorators import detail_route
|
from taiga.base.decorators import detail_route
|
||||||
from taiga.base.api import ModelCrudViewSet
|
from taiga.base.api import ModelCrudViewSet
|
||||||
|
from taiga.base.api.utils import get_object_or_404
|
||||||
|
|
||||||
from taiga.projects.notifications.mixins import WatchedResourceMixin
|
from taiga.projects.notifications.mixins import WatchedResourceMixin
|
||||||
from taiga.projects.history.mixins import HistoryResourceMixin
|
from taiga.projects.history.mixins import HistoryResourceMixin
|
||||||
|
@ -97,4 +92,4 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudView
|
||||||
current_date = current_date + datetime.timedelta(days=1)
|
current_date = current_date + datetime.timedelta(days=1)
|
||||||
optimal_points -= optimal_points_per_day
|
optimal_points -= optimal_points_per_day
|
||||||
|
|
||||||
return Response(milestone_stats)
|
return response.Ok(milestone_stats)
|
||||||
|
|
|
@ -14,20 +14,10 @@
|
||||||
# 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 django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from rest_framework.permissions import IsAuthenticated
|
|
||||||
from rest_framework.response import Response
|
|
||||||
|
|
||||||
from taiga.base.api.utils import get_object_or_404
|
|
||||||
from taiga.base import filters
|
|
||||||
from taiga.base import exceptions as exc
|
|
||||||
from taiga.base.decorators import detail_route
|
|
||||||
from taiga.base.api import ModelCrudViewSet
|
from taiga.base.api import ModelCrudViewSet
|
||||||
|
|
||||||
from taiga.projects.models import Project
|
|
||||||
|
|
||||||
from taiga.projects.notifications.choices import NotifyLevel
|
from taiga.projects.notifications.choices import NotifyLevel
|
||||||
|
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
|
@ -16,9 +16,13 @@
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from taiga.base.api.permissions import (TaigaResourcePermission, HasProjectPerm,
|
from taiga.base.api.permissions import TaigaResourcePermission
|
||||||
IsAuthenticated, IsProjectOwner,
|
from taiga.base.api.permissions import HasProjectPerm
|
||||||
AllowAny, IsSuperUser, PermissionComponent)
|
from taiga.base.api.permissions import IsAuthenticated
|
||||||
|
from taiga.base.api.permissions import IsProjectOwner
|
||||||
|
from taiga.base.api.permissions import AllowAny
|
||||||
|
from taiga.base.api.permissions import IsSuperUser
|
||||||
|
from taiga.base.api.permissions import PermissionComponent
|
||||||
|
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
from taiga.projects.models import Membership
|
from taiga.projects.models import Membership
|
||||||
|
@ -32,8 +36,8 @@ class CanLeaveProject(PermissionComponent):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not services.can_user_leave_project(request.user, obj):
|
if not services.can_user_leave_project(request.user, obj):
|
||||||
raise exc.PermissionDenied(_("You can't leave the project if there are no more owners"))
|
raise exc.PermissionDenied(_("You can't leave the project if there are no "
|
||||||
|
"more owners"))
|
||||||
return True
|
return True
|
||||||
except Membership.DoesNotExist:
|
except Membership.DoesNotExist:
|
||||||
return False
|
return False
|
||||||
|
@ -140,14 +144,6 @@ class IssueTypePermission(TaigaResourcePermission):
|
||||||
bulk_update_order_perms = IsProjectOwner()
|
bulk_update_order_perms = IsProjectOwner()
|
||||||
|
|
||||||
|
|
||||||
class RolesPermission(TaigaResourcePermission):
|
|
||||||
retrieve_perms = HasProjectPerm('view_project')
|
|
||||||
create_perms = IsProjectOwner()
|
|
||||||
update_perms = IsProjectOwner()
|
|
||||||
destroy_perms = IsProjectOwner()
|
|
||||||
list_perms = AllowAny()
|
|
||||||
|
|
||||||
|
|
||||||
# Project Templates
|
# Project Templates
|
||||||
|
|
||||||
class ProjectTemplatePermission(TaigaResourcePermission):
|
class ProjectTemplatePermission(TaigaResourcePermission):
|
||||||
|
|
|
@ -16,14 +16,13 @@
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
|
||||||
from rest_framework.response import Response
|
|
||||||
|
|
||||||
from taiga.base.api.utils import get_object_or_404
|
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
|
from taiga.base import response
|
||||||
from taiga.base.api import viewsets
|
from taiga.base.api import viewsets
|
||||||
from .serializers import ResolverSerializer
|
from taiga.base.api.utils import get_object_or_404
|
||||||
from taiga.permissions.service import user_has_perm
|
from taiga.permissions.service import user_has_perm
|
||||||
|
|
||||||
|
from .serializers import ResolverSerializer
|
||||||
from . import permissions
|
from . import permissions
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,19 +41,22 @@ class ResolverViewSet(viewsets.ViewSet):
|
||||||
|
|
||||||
self.check_permissions(request, "list", project)
|
self.check_permissions(request, "list", project)
|
||||||
|
|
||||||
result = {
|
result = {"project": project.pk}
|
||||||
"project": project.pk
|
|
||||||
}
|
|
||||||
|
|
||||||
if data["us"] and user_has_perm(request.user, "view_us", project):
|
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"] and user_has_perm(request.user, "view_tasks", project):
|
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"] and user_has_perm(request.user, "view_issues", project):
|
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"] and user_has_perm(request.user, "view_milestones", project):
|
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
|
||||||
if data["wikipage"] and user_has_perm(request.user, "view_wiki_pages", project):
|
if data["wikipage"] and user_has_perm(request.user, "view_wiki_pages", project):
|
||||||
result["wikipage"] = get_object_or_404(project.wiki_pages.all(), slug=data["wikipage"]).pk
|
result["wikipage"] = get_object_or_404(project.wiki_pages.all(),
|
||||||
|
slug=data["wikipage"]).pk
|
||||||
|
|
||||||
return Response(result)
|
return response.Ok(result)
|
||||||
|
|
|
@ -14,27 +14,32 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from os import path
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from taiga.base.serializers import JsonField, PgArrayField, ModelSerializer, TagsColorsField
|
from taiga.base.serializers import JsonField
|
||||||
from taiga.users.models import Role, User
|
from taiga.base.serializers import PgArrayField
|
||||||
|
from taiga.base.serializers import ModelSerializer
|
||||||
|
from taiga.base.serializers import TagsColorsField
|
||||||
from taiga.users.services import get_photo_or_gravatar_url
|
from taiga.users.services import get_photo_or_gravatar_url
|
||||||
from taiga.users.serializers import UserSerializer
|
from taiga.users.serializers import UserSerializer
|
||||||
|
from taiga.users.serializers import ProjectRoleSerializer
|
||||||
from taiga.users.validators import RoleExistsValidator
|
from taiga.users.validators import RoleExistsValidator
|
||||||
|
|
||||||
from taiga.permissions.service import get_user_project_permissions, is_project_owner
|
from taiga.permissions.service import get_user_project_permissions
|
||||||
|
from taiga.permissions.service import is_project_owner
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
from . import services
|
from . import services
|
||||||
from . validators import ProjectExistsValidator
|
from . validators import ProjectExistsValidator
|
||||||
|
|
||||||
|
|
||||||
# User Stories common serializers
|
######################################################
|
||||||
|
## Custom values for selectors
|
||||||
|
######################################################
|
||||||
|
|
||||||
class PointsSerializer(ModelSerializer):
|
class PointsSerializer(ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -69,10 +74,12 @@ class UserStoryStatusSerializer(ModelSerializer):
|
||||||
qs = None
|
qs = None
|
||||||
# If the user story status exists:
|
# If the user story status exists:
|
||||||
if self.object and attrs.get("name", None):
|
if self.object and attrs.get("name", None):
|
||||||
qs = models.UserStoryStatus.objects.filter(project=self.object.project, name=attrs[source])
|
qs = models.UserStoryStatus.objects.filter(project=self.object.project,
|
||||||
|
name=attrs[source])
|
||||||
|
|
||||||
if not self.object and attrs.get("project", None) and attrs.get("name", None):
|
if not self.object and attrs.get("project", None) and attrs.get("name", None):
|
||||||
qs = models.UserStoryStatus.objects.filter(project=attrs["project"], name=attrs[source])
|
qs = models.UserStoryStatus.objects.filter(project=attrs["project"],
|
||||||
|
name=attrs[source])
|
||||||
|
|
||||||
if qs and qs.exists():
|
if qs and qs.exists():
|
||||||
raise serializers.ValidationError("Name duplicated for the project")
|
raise serializers.ValidationError("Name duplicated for the project")
|
||||||
|
@ -80,8 +87,6 @@ class UserStoryStatusSerializer(ModelSerializer):
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
# Task common serializers
|
|
||||||
|
|
||||||
class TaskStatusSerializer(ModelSerializer):
|
class TaskStatusSerializer(ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.TaskStatus
|
model = models.TaskStatus
|
||||||
|
@ -103,7 +108,6 @@ class TaskStatusSerializer(ModelSerializer):
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
# Issues common serializers
|
|
||||||
|
|
||||||
class SeveritySerializer(ModelSerializer):
|
class SeveritySerializer(ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -142,13 +146,16 @@ class IssueTypeSerializer(ModelSerializer):
|
||||||
model = models.IssueType
|
model = models.IssueType
|
||||||
|
|
||||||
|
|
||||||
# Projects
|
######################################################
|
||||||
|
## Members
|
||||||
|
######################################################
|
||||||
|
|
||||||
class MembershipSerializer(ModelSerializer):
|
class MembershipSerializer(ModelSerializer):
|
||||||
role_name = serializers.CharField(source='role.name', required=False, read_only=True)
|
role_name = serializers.CharField(source='role.name', required=False, read_only=True)
|
||||||
full_name = serializers.CharField(source='user.get_full_name', required=False, read_only=True)
|
full_name = serializers.CharField(source='user.get_full_name', required=False, read_only=True)
|
||||||
user_email = serializers.EmailField(source='user.email', required=False, read_only=True)
|
user_email = serializers.EmailField(source='user.email', required=False, read_only=True)
|
||||||
is_user_active = serializers.BooleanField(source='user.is_active', required=False, read_only=True)
|
is_user_active = serializers.BooleanField(source='user.is_active', required=False,
|
||||||
|
read_only=True)
|
||||||
email = serializers.EmailField(required=True)
|
email = serializers.EmailField(required=True)
|
||||||
color = serializers.CharField(source='user.color', required=False, read_only=True)
|
color = serializers.CharField(source='user.color', required=False, read_only=True)
|
||||||
photo = serializers.SerializerMethodField("get_photo")
|
photo = serializers.SerializerMethodField("get_photo")
|
||||||
|
@ -210,7 +217,8 @@ class MembershipSerializer(ModelSerializer):
|
||||||
if project is None:
|
if project is None:
|
||||||
project = self.object.project
|
project = self.object.project
|
||||||
|
|
||||||
if self.object and not services.project_has_valid_owners(project, exclude_user=self.object.user):
|
if (self.object and
|
||||||
|
not services.project_has_valid_owners(project, exclude_user=self.object.user)):
|
||||||
raise serializers.ValidationError(_("At least one of the user must be an active admin"))
|
raise serializers.ValidationError(_("At least one of the user must be an active admin"))
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
@ -229,6 +237,21 @@ class ProjectMembershipSerializer(ModelSerializer):
|
||||||
return get_photo_or_gravatar_url(project.user)
|
return get_photo_or_gravatar_url(project.user)
|
||||||
|
|
||||||
|
|
||||||
|
class MemberBulkSerializer(RoleExistsValidator, serializers.Serializer):
|
||||||
|
email = serializers.EmailField()
|
||||||
|
role_id = serializers.IntegerField()
|
||||||
|
|
||||||
|
|
||||||
|
class MembersBulkSerializer(ProjectExistsValidator, serializers.Serializer):
|
||||||
|
project_id = serializers.IntegerField()
|
||||||
|
bulk_memberships = MemberBulkSerializer(many=True)
|
||||||
|
invitation_extra_text = serializers.CharField(required=False, max_length=255)
|
||||||
|
|
||||||
|
|
||||||
|
######################################################
|
||||||
|
## Projects
|
||||||
|
######################################################
|
||||||
|
|
||||||
class ProjectSerializer(ModelSerializer):
|
class ProjectSerializer(ModelSerializer):
|
||||||
tags = PgArrayField(required=False)
|
tags = PgArrayField(required=False)
|
||||||
anon_permissions = PgArrayField(required=False)
|
anon_permissions = PgArrayField(required=False)
|
||||||
|
@ -300,23 +323,20 @@ class ProjectDetailSerializer(ProjectSerializer):
|
||||||
return serializer.data
|
return serializer.data
|
||||||
|
|
||||||
|
|
||||||
class ProjectRoleSerializer(ModelSerializer):
|
######################################################
|
||||||
|
## Starred
|
||||||
|
######################################################
|
||||||
|
|
||||||
|
class StarredSerializer(ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Role
|
model = models.Project
|
||||||
fields = ('id', 'name', 'slug', 'order', 'computable')
|
fields = ['id', 'name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class RoleSerializer(ModelSerializer):
|
|
||||||
members_count = serializers.SerializerMethodField("get_members_count")
|
|
||||||
permissions = PgArrayField(required=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Role
|
|
||||||
fields = ('id', 'name', 'permissions', 'computable', 'project', 'order', 'members_count')
|
|
||||||
|
|
||||||
def get_members_count(self, obj):
|
|
||||||
return obj.memberships.count()
|
|
||||||
|
|
||||||
|
######################################################
|
||||||
|
## Project Templates
|
||||||
|
######################################################
|
||||||
|
|
||||||
class ProjectTemplateSerializer(ModelSerializer):
|
class ProjectTemplateSerializer(ModelSerializer):
|
||||||
default_options = JsonField(required=False, label=_("Default options"))
|
default_options = JsonField(required=False, label=_("Default options"))
|
||||||
|
@ -332,20 +352,3 @@ class ProjectTemplateSerializer(ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.ProjectTemplate
|
model = models.ProjectTemplate
|
||||||
read_only_fields = ("created_date", "modified_date")
|
read_only_fields = ("created_date", "modified_date")
|
||||||
|
|
||||||
|
|
||||||
class StarredSerializer(ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = models.Project
|
|
||||||
fields = ['id', 'name', 'slug']
|
|
||||||
|
|
||||||
|
|
||||||
class MemberBulkSerializer(RoleExistsValidator, serializers.Serializer):
|
|
||||||
email = serializers.EmailField()
|
|
||||||
role_id = serializers.IntegerField()
|
|
||||||
|
|
||||||
|
|
||||||
class MembersBulkSerializer(ProjectExistsValidator, serializers.Serializer):
|
|
||||||
project_id = serializers.IntegerField()
|
|
||||||
bulk_memberships = MemberBulkSerializer(many=True)
|
|
||||||
invitation_extra_text = serializers.CharField(required=False, max_length=255)
|
|
||||||
|
|
|
@ -16,19 +16,19 @@
|
||||||
|
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
from rest_framework.response import Response
|
from taiga.base import filters
|
||||||
from rest_framework import status
|
|
||||||
|
|
||||||
from taiga.base.api.utils import get_object_or_404
|
|
||||||
from taiga.base import filters, response
|
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
|
from taiga.base import response
|
||||||
from taiga.base.decorators import list_route
|
from taiga.base.decorators import list_route
|
||||||
from taiga.base.api import ModelCrudViewSet
|
from taiga.base.api import ModelCrudViewSet
|
||||||
|
from taiga.base.api.utils import get_object_or_404
|
||||||
|
|
||||||
from taiga.projects.notifications.mixins import WatchedResourceMixin
|
from taiga.projects.notifications.mixins import WatchedResourceMixin
|
||||||
from taiga.projects.history.mixins import HistoryResourceMixin
|
from taiga.projects.history.mixins import HistoryResourceMixin
|
||||||
|
|
|
@ -17,13 +17,12 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework import status
|
|
||||||
|
|
||||||
from taiga.base.api.utils import get_object_or_404
|
|
||||||
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 import response
|
||||||
from taiga.base.api import ModelCrudViewSet
|
from taiga.base.api import ModelCrudViewSet
|
||||||
|
from taiga.base.api.utils import get_object_or_404
|
||||||
from taiga.base.decorators import list_route
|
from taiga.base.decorators import list_route
|
||||||
from taiga.projects.models import Project
|
from taiga.projects.models import Project
|
||||||
from taiga.mdrender.service import render as mdrender
|
from taiga.mdrender.service import render as mdrender
|
||||||
|
@ -58,17 +57,17 @@ class WikiViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
||||||
project_id = request.DATA.get("project_id", None)
|
project_id = request.DATA.get("project_id", None)
|
||||||
|
|
||||||
if not content:
|
if not content:
|
||||||
raise exc.WrongArguments({"content": "No content parameter"})
|
raise exc.WrongArguments({"content": _("No content parameter")})
|
||||||
|
|
||||||
if not project_id:
|
if not project_id:
|
||||||
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)
|
self.check_permissions(request, "render", project)
|
||||||
|
|
||||||
data = mdrender(project, content)
|
data = mdrender(project, content)
|
||||||
return Response({"data": data})
|
return response.Ok({"data": data})
|
||||||
|
|
||||||
def pre_save(self, obj):
|
def pre_save(self, obj):
|
||||||
if not obj.owner:
|
if not obj.owner:
|
||||||
|
|
|
@ -18,15 +18,18 @@ from taiga.base import routers
|
||||||
|
|
||||||
router = routers.DefaultRouter(trailing_slash=False)
|
router = routers.DefaultRouter(trailing_slash=False)
|
||||||
|
|
||||||
# taiga.users
|
|
||||||
from taiga.users.api import UsersViewSet
|
# Users & Roles
|
||||||
from taiga.auth.api import AuthViewSet
|
from taiga.auth.api import AuthViewSet
|
||||||
|
from taiga.users.api import UsersViewSet
|
||||||
|
from taiga.users.api import RolesViewSet
|
||||||
|
|
||||||
router.register(r"users", UsersViewSet, base_name="users")
|
|
||||||
router.register(r"auth", AuthViewSet, base_name="auth")
|
router.register(r"auth", AuthViewSet, base_name="auth")
|
||||||
|
router.register(r"users", UsersViewSet, base_name="users")
|
||||||
|
router.register(r"roles", RolesViewSet, base_name="roles")
|
||||||
|
|
||||||
|
|
||||||
#taiga.userstorage
|
# User Storage
|
||||||
from taiga.userstorage.api import StorageEntriesViewSet
|
from taiga.userstorage.api import StorageEntriesViewSet
|
||||||
|
|
||||||
router.register(r"user-storage", StorageEntriesViewSet, base_name="user-storage")
|
router.register(r"user-storage", StorageEntriesViewSet, base_name="user-storage")
|
||||||
|
@ -51,8 +54,7 @@ router.register(r"importer", ProjectImporterViewSet, base_name="importer")
|
||||||
router.register(r"exporter", ProjectExporterViewSet, base_name="exporter")
|
router.register(r"exporter", ProjectExporterViewSet, base_name="exporter")
|
||||||
|
|
||||||
|
|
||||||
# Projects & Types
|
# Projects & Selectors
|
||||||
from taiga.projects.api import RolesViewSet
|
|
||||||
from taiga.projects.api import ProjectViewSet
|
from taiga.projects.api import ProjectViewSet
|
||||||
from taiga.projects.api import MembershipViewSet
|
from taiga.projects.api import MembershipViewSet
|
||||||
from taiga.projects.api import InvitationViewSet
|
from taiga.projects.api import InvitationViewSet
|
||||||
|
@ -65,8 +67,6 @@ from taiga.projects.api import PriorityViewSet
|
||||||
from taiga.projects.api import SeverityViewSet
|
from taiga.projects.api import SeverityViewSet
|
||||||
from taiga.projects.api import ProjectTemplateViewSet
|
from taiga.projects.api import ProjectTemplateViewSet
|
||||||
|
|
||||||
|
|
||||||
router.register(r"roles", RolesViewSet, base_name="roles")
|
|
||||||
router.register(r"projects", ProjectViewSet, base_name="projects")
|
router.register(r"projects", ProjectViewSet, base_name="projects")
|
||||||
router.register(r"project-templates", ProjectTemplateViewSet, base_name="project-templates")
|
router.register(r"project-templates", ProjectTemplateViewSet, base_name="project-templates")
|
||||||
router.register(r"memberships", MembershipViewSet, base_name="memberships")
|
router.register(r"memberships", MembershipViewSet, base_name="memberships")
|
||||||
|
@ -79,22 +79,27 @@ router.register(r"issue-types", IssueTypeViewSet, base_name="issue-types")
|
||||||
router.register(r"priorities", PriorityViewSet, base_name="priorities")
|
router.register(r"priorities", PriorityViewSet, base_name="priorities")
|
||||||
router.register(r"severities",SeverityViewSet , base_name="severities")
|
router.register(r"severities",SeverityViewSet , base_name="severities")
|
||||||
|
|
||||||
|
|
||||||
# Attachments
|
# Attachments
|
||||||
from taiga.projects.attachments.api import UserStoryAttachmentViewSet
|
from taiga.projects.attachments.api import UserStoryAttachmentViewSet
|
||||||
from taiga.projects.attachments.api import IssueAttachmentViewSet
|
from taiga.projects.attachments.api import IssueAttachmentViewSet
|
||||||
from taiga.projects.attachments.api import TaskAttachmentViewSet
|
from taiga.projects.attachments.api import TaskAttachmentViewSet
|
||||||
from taiga.projects.attachments.api import WikiAttachmentViewSet
|
from taiga.projects.attachments.api import WikiAttachmentViewSet
|
||||||
|
|
||||||
router.register(r"userstories/attachments", UserStoryAttachmentViewSet, base_name="userstory-attachments")
|
router.register(r"userstories/attachments", UserStoryAttachmentViewSet,
|
||||||
|
base_name="userstory-attachments")
|
||||||
router.register(r"tasks/attachments", TaskAttachmentViewSet, base_name="task-attachments")
|
router.register(r"tasks/attachments", TaskAttachmentViewSet, base_name="task-attachments")
|
||||||
router.register(r"issues/attachments", IssueAttachmentViewSet, base_name="issue-attachments")
|
router.register(r"issues/attachments", IssueAttachmentViewSet, base_name="issue-attachments")
|
||||||
router.register(r"wiki/attachments", WikiAttachmentViewSet, base_name="wiki-attachments")
|
router.register(r"wiki/attachments", WikiAttachmentViewSet, base_name="wiki-attachments")
|
||||||
|
|
||||||
|
|
||||||
# Webhooks
|
# Webhooks
|
||||||
from taiga.webhooks.api import WebhookViewSet, WebhookLogViewSet
|
from taiga.webhooks.api import WebhookViewSet, WebhookLogViewSet
|
||||||
|
|
||||||
router.register(r"webhooks", WebhookViewSet, base_name="webhooks")
|
router.register(r"webhooks", WebhookViewSet, base_name="webhooks")
|
||||||
router.register(r"webhooklogs", WebhookLogViewSet, base_name="webhooklogs")
|
router.register(r"webhooklogs", WebhookLogViewSet, base_name="webhooklogs")
|
||||||
|
|
||||||
|
|
||||||
# History & Components
|
# History & Components
|
||||||
from taiga.projects.history.api import UserStoryHistory
|
from taiga.projects.history.api import UserStoryHistory
|
||||||
from taiga.projects.history.api import TaskHistory
|
from taiga.projects.history.api import TaskHistory
|
||||||
|
@ -131,22 +136,30 @@ router.register(r"issues/(?P<issue_id>\d+)/voters", VotersViewSet, base_name="is
|
||||||
router.register(r"wiki", WikiViewSet, base_name="wiki")
|
router.register(r"wiki", WikiViewSet, base_name="wiki")
|
||||||
router.register(r"wiki-links", WikiLinkViewSet, base_name="wiki-links")
|
router.register(r"wiki-links", WikiLinkViewSet, base_name="wiki-links")
|
||||||
|
|
||||||
|
|
||||||
# Notify policies
|
# Notify policies
|
||||||
from taiga.projects.notifications.api import NotifyPolicyViewSet
|
from taiga.projects.notifications.api import NotifyPolicyViewSet
|
||||||
|
|
||||||
router.register(r"notify-policies", NotifyPolicyViewSet, base_name="notifications")
|
router.register(r"notify-policies", NotifyPolicyViewSet, base_name="notifications")
|
||||||
|
|
||||||
|
|
||||||
# GitHub webhooks
|
# GitHub webhooks
|
||||||
from taiga.hooks.github.api import GitHubViewSet
|
from taiga.hooks.github.api import GitHubViewSet
|
||||||
|
|
||||||
router.register(r"github-hook", GitHubViewSet, base_name="github-hook")
|
router.register(r"github-hook", GitHubViewSet, base_name="github-hook")
|
||||||
|
|
||||||
|
|
||||||
# Gitlab webhooks
|
# Gitlab webhooks
|
||||||
from taiga.hooks.gitlab.api import GitLabViewSet
|
from taiga.hooks.gitlab.api import GitLabViewSet
|
||||||
|
|
||||||
router.register(r"gitlab-hook", GitLabViewSet, base_name="gitlab-hook")
|
router.register(r"gitlab-hook", GitLabViewSet, base_name="gitlab-hook")
|
||||||
|
|
||||||
|
|
||||||
# Bitbucket webhooks
|
# Bitbucket webhooks
|
||||||
from taiga.hooks.bitbucket.api import BitBucketViewSet
|
from taiga.hooks.bitbucket.api import BitBucketViewSet
|
||||||
|
|
||||||
router.register(r"bitbucket-hook", BitBucketViewSet, base_name="bitbucket-hook")
|
router.register(r"bitbucket-hook", BitBucketViewSet, base_name="bitbucket-hook")
|
||||||
|
|
||||||
|
|
||||||
# feedback
|
# feedback
|
||||||
# - see taiga.feedback.routers and taiga.feedback.apps
|
# - see taiga.feedback.routers and taiga.feedback.apps
|
||||||
|
|
|
@ -16,10 +16,9 @@
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
|
||||||
from taiga.base import exceptions as excp
|
from taiga.base import response
|
||||||
from taiga.base.api.utils import get_object_or_404
|
from taiga.base.api.utils import get_object_or_404
|
||||||
from taiga.projects.userstories.serializers import UserStorySerializer
|
from taiga.projects.userstories.serializers import UserStorySerializer
|
||||||
from taiga.projects.tasks.serializers import TaskSerializer
|
from taiga.projects.tasks.serializers import TaskSerializer
|
||||||
|
@ -48,7 +47,7 @@ class SearchViewSet(viewsets.ViewSet):
|
||||||
result["wikipages"] = self._search_wiki_pages(project, text)
|
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.Ok(result)
|
||||||
|
|
||||||
def _get_project(self, project_id):
|
def _get_project(self, project_id):
|
||||||
project_model = apps.get_model("projects", "Project")
|
project_model = apps.get_model("projects", "Project")
|
||||||
|
|
|
@ -16,8 +16,7 @@
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from rest_framework.response import Response
|
from taiga.base import response
|
||||||
|
|
||||||
from taiga.base.api.utils import get_object_or_404
|
from taiga.base.api.utils import get_object_or_404
|
||||||
from taiga.base.api import GenericViewSet
|
from taiga.base.api import GenericViewSet
|
||||||
|
|
||||||
|
@ -52,12 +51,12 @@ class TimelineViewSet(GenericViewSet):
|
||||||
else:
|
else:
|
||||||
serializer = self.get_serializer(queryset, many=True)
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
|
|
||||||
return Response(serializer.data)
|
return response.Ok(serializer.data)
|
||||||
|
|
||||||
# Just for restframework! Because it raises
|
# Just for restframework! Because it raises
|
||||||
# 404 on main api root if this method not exists.
|
# 404 on main api root if this method not exists.
|
||||||
def list(self, request):
|
def list(self, request):
|
||||||
return Response({})
|
return response.NotFound()
|
||||||
|
|
||||||
def retrieve(self, request, pk):
|
def retrieve(self, request, pk):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
|
|
|
@ -23,23 +23,22 @@ from django.core.validators import validate_email
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from easy_thumbnails.source_generators import pil_image
|
|
||||||
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.filters import BaseFilterBackend
|
|
||||||
from rest_framework import status
|
|
||||||
|
|
||||||
from djmail.template_mail import MagicMailBuilder, InlineCSSTemplateMail
|
|
||||||
|
|
||||||
from taiga.auth.tokens import get_user_for_token
|
|
||||||
from taiga.base.decorators import list_route, detail_route
|
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
|
from taiga.base import filters
|
||||||
|
from taiga.base import response
|
||||||
|
from taiga.auth.tokens import get_user_for_token
|
||||||
|
from taiga.base.decorators import list_route
|
||||||
|
from taiga.base.decorators import detail_route
|
||||||
from taiga.base.api import ModelCrudViewSet
|
from taiga.base.api import ModelCrudViewSet
|
||||||
from taiga.base.api.utils import get_object_or_404
|
from taiga.base.api.utils import get_object_or_404
|
||||||
from taiga.base.utils.slug import slugify_uniquely
|
from taiga.base.filters import MembersFilterBackend
|
||||||
from taiga.projects.votes import services as votes_service
|
from taiga.projects.votes import services as votes_service
|
||||||
from taiga.projects.serializers import StarredSerializer
|
from taiga.projects.serializers import StarredSerializer
|
||||||
from taiga.permissions.service import is_project_owner
|
|
||||||
|
from easy_thumbnails.source_generators import pil_image
|
||||||
|
|
||||||
|
from djmail.template_mail import MagicMailBuilder
|
||||||
|
from djmail.template_mail import InlineCSSTemplateMail
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
@ -47,22 +46,9 @@ from . import permissions
|
||||||
from .signals import user_cancel_account as user_cancel_account_signal
|
from .signals import user_cancel_account as user_cancel_account_signal
|
||||||
|
|
||||||
|
|
||||||
class MembersFilterBackend(BaseFilterBackend):
|
######################################################
|
||||||
def filter_queryset(self, request, queryset, view):
|
## User
|
||||||
project_id = request.QUERY_PARAMS.get('project', None)
|
######################################################
|
||||||
if project_id:
|
|
||||||
Project = apps.get_model('projects', 'Project')
|
|
||||||
project = get_object_or_404(Project, pk=project_id)
|
|
||||||
if request.user.is_authenticated() and project.memberships.filter(user=request.user).exists():
|
|
||||||
return queryset.filter(memberships__project=project).distinct()
|
|
||||||
else:
|
|
||||||
raise exc.PermissionDenied(_("You don't have permisions to see this project users."))
|
|
||||||
|
|
||||||
if request.user.is_superuser:
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
class UsersViewSet(ModelCrudViewSet):
|
class UsersViewSet(ModelCrudViewSet):
|
||||||
permission_classes = (permissions.UserPermission,)
|
permission_classes = (permissions.UserPermission,)
|
||||||
|
@ -73,7 +59,9 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
raise exc.NotSupported()
|
raise exc.NotSupported()
|
||||||
|
|
||||||
def list(self, request, *args, **kwargs):
|
def list(self, request, *args, **kwargs):
|
||||||
self.object_list = MembersFilterBackend().filter_queryset(request, self.get_queryset(), self)
|
self.object_list = MembersFilterBackend().filter_queryset(request,
|
||||||
|
self.get_queryset(),
|
||||||
|
self)
|
||||||
|
|
||||||
page = self.paginate_queryset(self.object_list)
|
page = self.paginate_queryset(self.object_list)
|
||||||
if page is not None:
|
if page is not None:
|
||||||
|
@ -81,7 +69,7 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
else:
|
else:
|
||||||
serializer = self.get_serializer(self.object_list, many=True)
|
serializer = self.get_serializer(self.object_list, many=True)
|
||||||
|
|
||||||
return Response(serializer.data)
|
return response.Ok(serializer.data)
|
||||||
|
|
||||||
@list_route(methods=["POST"])
|
@list_route(methods=["POST"])
|
||||||
def password_recovery(self, request, pk=None):
|
def password_recovery(self, request, pk=None):
|
||||||
|
@ -106,7 +94,7 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
email = mbuilder.password_recovery(user.email, {"user": user})
|
email = mbuilder.password_recovery(user.email, {"user": user})
|
||||||
email.send()
|
email.send()
|
||||||
|
|
||||||
return Response({"detail": _("Mail sended successful!"),
|
return response.Ok({"detail": _("Mail sended successful!"),
|
||||||
"email": user.email})
|
"email": user.email})
|
||||||
|
|
||||||
@list_route(methods=["POST"])
|
@list_route(methods=["POST"])
|
||||||
|
@ -130,7 +118,7 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
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.NoContent()
|
||||||
|
|
||||||
@list_route(methods=["POST"])
|
@list_route(methods=["POST"])
|
||||||
def change_password(self, request, pk=None):
|
def change_password(self, request, pk=None):
|
||||||
|
@ -158,7 +146,7 @@ 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.NoContent()
|
||||||
|
|
||||||
@list_route(methods=["POST"])
|
@list_route(methods=["POST"])
|
||||||
def change_avatar(self, request):
|
def change_avatar(self, request):
|
||||||
|
@ -181,7 +169,7 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
request.user.save(update_fields=["photo"])
|
request.user.save(update_fields=["photo"])
|
||||||
user_data = serializers.UserSerializer(request.user).data
|
user_data = serializers.UserSerializer(request.user).data
|
||||||
|
|
||||||
return Response(user_data, status=status.HTTP_200_OK)
|
return response.Ok(user_data)
|
||||||
|
|
||||||
@list_route(methods=["POST"])
|
@list_route(methods=["POST"])
|
||||||
def remove_avatar(self, request):
|
def remove_avatar(self, request):
|
||||||
|
@ -192,7 +180,7 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
request.user.photo = None
|
request.user.photo = None
|
||||||
request.user.save(update_fields=["photo"])
|
request.user.save(update_fields=["photo"])
|
||||||
user_data = serializers.UserSerializer(request.user).data
|
user_data = serializers.UserSerializer(request.user).data
|
||||||
return Response(user_data, status=status.HTTP_200_OK)
|
return response.Ok(user_data)
|
||||||
|
|
||||||
@detail_route(methods=["GET"])
|
@detail_route(methods=["GET"])
|
||||||
def starred(self, request, pk=None):
|
def starred(self, request, pk=None):
|
||||||
|
@ -201,7 +189,7 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
|
|
||||||
stars = votes_service.get_voted(user.pk, model=apps.get_model('projects', 'Project'))
|
stars = votes_service.get_voted(user.pk, model=apps.get_model('projects', 'Project'))
|
||||||
stars_data = StarredSerializer(stars, many=True)
|
stars_data = StarredSerializer(stars, many=True)
|
||||||
return Response(stars_data.data)
|
return response.Ok(stars_data.data)
|
||||||
|
|
||||||
#TODO: commit_on_success
|
#TODO: commit_on_success
|
||||||
def partial_update(self, request, *args, **kwargs):
|
def partial_update(self, request, *args, **kwargs):
|
||||||
|
@ -249,12 +237,14 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
"""
|
"""
|
||||||
serializer = serializers.ChangeEmailSerializer(data=request.DATA, many=False)
|
serializer = serializers.ChangeEmailSerializer(data=request.DATA, many=False)
|
||||||
if not serializer.is_valid():
|
if not serializer.is_valid():
|
||||||
raise exc.WrongArguments(_("Invalid, are you sure the token is correct and you didn't use it before?"))
|
raise exc.WrongArguments(_("Invalid, are you sure the token is correct and you "
|
||||||
|
"didn't use it before?"))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user = models.User.objects.get(email_token=serializer.data["email_token"])
|
user = models.User.objects.get(email_token=serializer.data["email_token"])
|
||||||
except models.User.DoesNotExist:
|
except models.User.DoesNotExist:
|
||||||
raise exc.WrongArguments(_("Invalid, are you sure the token is correct and you didn't use it before?"))
|
raise exc.WrongArguments(_("Invalid, are you sure the token is correct and you "
|
||||||
|
"didn't use it before?"))
|
||||||
|
|
||||||
self.check_permissions(request, "change_email", user)
|
self.check_permissions(request, "change_email", user)
|
||||||
user.email = user.new_email
|
user.email = user.new_email
|
||||||
|
@ -262,7 +252,7 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
user.email_token = None
|
user.email_token = None
|
||||||
user.save(update_fields=["email", "new_email", "email_token"])
|
user.save(update_fields=["email", "new_email", "email_token"])
|
||||||
|
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return response.NoContent()
|
||||||
|
|
||||||
@list_route(methods=["GET"])
|
@list_route(methods=["GET"])
|
||||||
def me(self, request, pk=None):
|
def me(self, request, pk=None):
|
||||||
|
@ -271,7 +261,7 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
"""
|
"""
|
||||||
self.check_permissions(request, "me", None)
|
self.check_permissions(request, "me", None)
|
||||||
user_data = serializers.UserSerializer(request.user).data
|
user_data = serializers.UserSerializer(request.user).data
|
||||||
return Response(user_data, status=status.HTTP_200_OK)
|
return response.Ok(user_data)
|
||||||
|
|
||||||
@list_route(methods=["POST"])
|
@list_route(methods=["POST"])
|
||||||
def cancel(self, request, pk=None):
|
def cancel(self, request, pk=None):
|
||||||
|
@ -294,7 +284,7 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
raise exc.WrongArguments(_("Invalid, are you sure the token is correct?"))
|
raise exc.WrongArguments(_("Invalid, are you sure the token is correct?"))
|
||||||
|
|
||||||
user.cancel()
|
user.cancel()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return response.NoContent()
|
||||||
|
|
||||||
def destroy(self, request, pk=None):
|
def destroy(self, request, pk=None):
|
||||||
user = self.get_object()
|
user = self.get_object()
|
||||||
|
@ -303,4 +293,26 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
request_data = stream is not None and stream.GET or None
|
request_data = stream is not None and stream.GET or None
|
||||||
user_cancel_account_signal.send(sender=user.__class__, user=user, request_data=request_data)
|
user_cancel_account_signal.send(sender=user.__class__, user=user, request_data=request_data)
|
||||||
user.cancel()
|
user.cancel()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return response.NoContent()
|
||||||
|
|
||||||
|
|
||||||
|
######################################################
|
||||||
|
## Role
|
||||||
|
######################################################
|
||||||
|
|
||||||
|
class RolesViewSet(ModelCrudViewSet):
|
||||||
|
model = models.Role
|
||||||
|
serializer_class = serializers.RoleSerializer
|
||||||
|
permission_classes = (permissions.RolesPermission, )
|
||||||
|
filter_backends = (filters.CanViewProjectFilterBackend,)
|
||||||
|
filter_fields = ('project',)
|
||||||
|
|
||||||
|
def pre_delete(self, obj):
|
||||||
|
move_to = self.request.QUERY_PARAMS.get('moveTo', None)
|
||||||
|
if move_to:
|
||||||
|
membership_model = apps.get_model("projects", "Membership")
|
||||||
|
role_dest = get_object_or_404(self.model, project=obj.project, id=move_to)
|
||||||
|
qs = membership_model.objects.filter(project_id=obj.project.pk, role=obj)
|
||||||
|
qs.update(role=role_dest)
|
||||||
|
|
||||||
|
super().pre_delete(obj)
|
||||||
|
|
|
@ -174,6 +174,7 @@ class User(AbstractBaseUser, PermissionsMixin):
|
||||||
self.save()
|
self.save()
|
||||||
self.auth_data.all().delete()
|
self.auth_data.all().delete()
|
||||||
|
|
||||||
|
|
||||||
class Role(models.Model):
|
class Role(models.Model):
|
||||||
name = models.CharField(max_length=200, null=False, blank=False,
|
name = models.CharField(max_length=200, null=False, blank=False,
|
||||||
verbose_name=_("name"))
|
verbose_name=_("name"))
|
||||||
|
|
|
@ -14,9 +14,13 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from taiga.base.api.permissions import (TaigaResourcePermission, IsSuperUser,
|
from taiga.base.api.permissions import TaigaResourcePermission
|
||||||
AllowAny, PermissionComponent,
|
from taiga.base.api.permissions import IsSuperUser
|
||||||
IsAuthenticated)
|
from taiga.base.api.permissions import AllowAny
|
||||||
|
from taiga.base.api.permissions import IsAuthenticated
|
||||||
|
from taiga.base.api.permissions import HasProjectPerm
|
||||||
|
from taiga.base.api.permissions import IsProjectOwner
|
||||||
|
from taiga.base.api.permissions import PermissionComponent
|
||||||
|
|
||||||
|
|
||||||
class IsTheSameUser(PermissionComponent):
|
class IsTheSameUser(PermissionComponent):
|
||||||
|
@ -39,3 +43,11 @@ class UserPermission(TaigaResourcePermission):
|
||||||
remove_avatar_perms = IsAuthenticated()
|
remove_avatar_perms = IsAuthenticated()
|
||||||
starred_perms = AllowAny()
|
starred_perms = AllowAny()
|
||||||
change_email_perms = IsTheSameUser()
|
change_email_perms = IsTheSameUser()
|
||||||
|
|
||||||
|
|
||||||
|
class RolesPermission(TaigaResourcePermission):
|
||||||
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
|
create_perms = IsProjectOwner()
|
||||||
|
update_perms = IsProjectOwner()
|
||||||
|
destroy_perms = IsProjectOwner()
|
||||||
|
list_perms = AllowAny()
|
||||||
|
|
|
@ -16,16 +16,24 @@
|
||||||
|
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from taiga.base.serializers import Serializer
|
||||||
|
from taiga.base.serializers import ModelSerializer
|
||||||
|
from taiga.base.serializers import PgArrayField
|
||||||
|
|
||||||
from .models import User
|
from .models import User, Role
|
||||||
from .services import get_photo_or_gravatar_url, get_big_photo_or_gravatar_url
|
from .services import get_photo_or_gravatar_url, get_big_photo_or_gravatar_url
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
######################################################
|
||||||
|
## User
|
||||||
|
######################################################
|
||||||
|
|
||||||
|
class UserSerializer(ModelSerializer):
|
||||||
full_name_display = serializers.SerializerMethodField("get_full_name_display")
|
full_name_display = serializers.SerializerMethodField("get_full_name_display")
|
||||||
photo = serializers.SerializerMethodField("get_photo")
|
photo = serializers.SerializerMethodField("get_photo")
|
||||||
big_photo = serializers.SerializerMethodField("get_big_photo")
|
big_photo = serializers.SerializerMethodField("get_big_photo")
|
||||||
|
@ -39,16 +47,19 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
def validate_username(self, attrs, source):
|
def validate_username(self, attrs, source):
|
||||||
value = attrs[source]
|
value = attrs[source]
|
||||||
validator = validators.RegexValidator(re.compile('^[\w.-]+$'), "invalid username", "invalid")
|
validator = validators.RegexValidator(re.compile('^[\w.-]+$'), _("invalid username"),
|
||||||
|
_("invalid"))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
validator(value)
|
validator(value)
|
||||||
except ValidationError:
|
except ValidationError:
|
||||||
raise serializers.ValidationError("Required. 255 characters or fewer. Letters, numbers "
|
raise serializers.ValidationError(_("Required. 255 characters or fewer. Letters, "
|
||||||
"and /./-/_ characters'")
|
"numbers and /./-/_ characters'"))
|
||||||
|
|
||||||
if self.object and self.object.username != value and User.objects.filter(username=value).exists():
|
if (self.object and
|
||||||
raise serializers.ValidationError("Invalid username. Try with a different one.")
|
self.object.username != value and
|
||||||
|
User.objects.filter(username=value).exists()):
|
||||||
|
raise serializers.ValidationError(_("Invalid username. Try with a different one."))
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
@ -62,14 +73,36 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
return get_big_photo_or_gravatar_url(user)
|
return get_big_photo_or_gravatar_url(user)
|
||||||
|
|
||||||
|
|
||||||
class RecoverySerializer(serializers.Serializer):
|
class RecoverySerializer(Serializer):
|
||||||
token = serializers.CharField(max_length=200)
|
token = serializers.CharField(max_length=200)
|
||||||
password = serializers.CharField(min_length=6)
|
password = serializers.CharField(min_length=6)
|
||||||
|
|
||||||
|
|
||||||
class ChangeEmailSerializer(serializers.Serializer):
|
class ChangeEmailSerializer(Serializer):
|
||||||
email_token = serializers.CharField(max_length=200)
|
email_token = serializers.CharField(max_length=200)
|
||||||
|
|
||||||
|
|
||||||
class CancelAccountSerializer(serializers.Serializer):
|
class CancelAccountSerializer(Serializer):
|
||||||
cancel_token = serializers.CharField(max_length=200)
|
cancel_token = serializers.CharField(max_length=200)
|
||||||
|
|
||||||
|
|
||||||
|
######################################################
|
||||||
|
## Role
|
||||||
|
######################################################
|
||||||
|
|
||||||
|
class RoleSerializer(ModelSerializer):
|
||||||
|
members_count = serializers.SerializerMethodField("get_members_count")
|
||||||
|
permissions = PgArrayField(required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Role
|
||||||
|
fields = ('id', 'name', 'permissions', 'computable', 'project', 'order', 'members_count')
|
||||||
|
|
||||||
|
def get_members_count(self, obj):
|
||||||
|
return obj.memberships.count()
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectRoleSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Role
|
||||||
|
fields = ('id', 'name', 'slug', 'order', 'computable')
|
||||||
|
|
|
@ -16,10 +16,11 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from rest_framework.response import Response
|
|
||||||
|
|
||||||
from taiga.base import filters
|
from taiga.base import filters
|
||||||
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
|
from taiga.base import response
|
||||||
|
from taiga.base.api import ModelCrudViewSet
|
||||||
|
from taiga.base.api import ModelListViewSet
|
||||||
|
|
||||||
from taiga.base.api.utils import get_object_or_404
|
from taiga.base.api.utils import get_object_or_404
|
||||||
from taiga.base.decorators import detail_route
|
from taiga.base.decorators import detail_route
|
||||||
|
|
||||||
|
@ -44,7 +45,8 @@ class WebhookViewSet(ModelCrudViewSet):
|
||||||
webhooklog = tasks.test_webhook(webhook.id, webhook.url, webhook.key)
|
webhooklog = tasks.test_webhook(webhook.id, webhook.url, webhook.key)
|
||||||
log = serializers.WebhookLogSerializer(webhooklog)
|
log = serializers.WebhookLogSerializer(webhooklog)
|
||||||
|
|
||||||
return Response(log.data)
|
return response.Ok(log.data)
|
||||||
|
|
||||||
|
|
||||||
class WebhookLogViewSet(ModelListViewSet):
|
class WebhookLogViewSet(ModelListViewSet):
|
||||||
model = models.WebhookLog
|
model = models.WebhookLog
|
||||||
|
@ -60,7 +62,9 @@ class WebhookLogViewSet(ModelListViewSet):
|
||||||
|
|
||||||
webhook = webhooklog.webhook
|
webhook = webhooklog.webhook
|
||||||
|
|
||||||
webhooklog = tasks.resend_webhook(webhook.id, webhook.url, webhook.key, webhooklog.request_data)
|
webhooklog = tasks.resend_webhook(webhook.id, webhook.url, webhook.key,
|
||||||
|
webhooklog.request_data)
|
||||||
|
|
||||||
log = serializers.WebhookLogSerializer(webhooklog)
|
log = serializers.WebhookLogSerializer(webhooklog)
|
||||||
|
|
||||||
return Response(log.data)
|
return response.Ok(log.data)
|
||||||
|
|
|
@ -2,6 +2,7 @@ from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
from taiga.base.utils import json
|
from taiga.base.utils import json
|
||||||
from taiga.projects import serializers
|
from taiga.projects import serializers
|
||||||
|
from taiga.users.serializers import RoleSerializer
|
||||||
from taiga.permissions.permissions import MEMBERS_PERMISSIONS
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS
|
||||||
|
|
||||||
from tests import factories as f
|
from tests import factories as f
|
||||||
|
@ -140,19 +141,19 @@ def test_roles_update(client, data):
|
||||||
data.project_owner
|
data.project_owner
|
||||||
]
|
]
|
||||||
|
|
||||||
role_data = serializers.RoleSerializer(data.public_project.roles.all()[0]).data
|
role_data = RoleSerializer(data.public_project.roles.all()[0]).data
|
||||||
role_data["name"] = "test"
|
role_data["name"] = "test"
|
||||||
role_data = json.dumps(role_data)
|
role_data = json.dumps(role_data)
|
||||||
results = helper_test_http_method(client, 'put', public_url, role_data, users)
|
results = helper_test_http_method(client, 'put', public_url, role_data, users)
|
||||||
assert results == [401, 403, 403, 403, 200]
|
assert results == [401, 403, 403, 403, 200]
|
||||||
|
|
||||||
role_data = serializers.RoleSerializer(data.private_project1.roles.all()[0]).data
|
role_data = RoleSerializer(data.private_project1.roles.all()[0]).data
|
||||||
role_data["name"] = "test"
|
role_data["name"] = "test"
|
||||||
role_data = json.dumps(role_data)
|
role_data = json.dumps(role_data)
|
||||||
results = helper_test_http_method(client, 'put', private1_url, role_data, users)
|
results = helper_test_http_method(client, 'put', private1_url, role_data, users)
|
||||||
assert results == [401, 403, 403, 403, 200]
|
assert results == [401, 403, 403, 403, 200]
|
||||||
|
|
||||||
role_data = serializers.RoleSerializer(data.private_project2.roles.all()[0]).data
|
role_data = RoleSerializer(data.private_project2.roles.all()[0]).data
|
||||||
role_data["name"] = "test"
|
role_data["name"] = "test"
|
||||||
role_data = json.dumps(role_data)
|
role_data = json.dumps(role_data)
|
||||||
results = helper_test_http_method(client, 'put', private2_url, role_data, users)
|
results = helper_test_http_method(client, 'put', private2_url, role_data, users)
|
||||||
|
|
|
@ -55,7 +55,7 @@ def test_ok_signature(client):
|
||||||
urllib.parse.urlencode(data, True),
|
urllib.parse.urlencode(data, True),
|
||||||
content_type="application/x-www-form-urlencoded",
|
content_type="application/x-www-form-urlencoded",
|
||||||
REMOTE_ADDR=settings.BITBUCKET_VALID_ORIGIN_IPS[0])
|
REMOTE_ADDR=settings.BITBUCKET_VALID_ORIGIN_IPS[0])
|
||||||
assert response.status_code == 200
|
assert response.status_code == 204
|
||||||
|
|
||||||
def test_invalid_ip(client):
|
def test_invalid_ip(client):
|
||||||
project=f.ProjectFactory()
|
project=f.ProjectFactory()
|
||||||
|
@ -91,7 +91,7 @@ def test_not_ip_filter(client):
|
||||||
urllib.parse.urlencode(data, True),
|
urllib.parse.urlencode(data, True),
|
||||||
content_type="application/x-www-form-urlencoded",
|
content_type="application/x-www-form-urlencoded",
|
||||||
REMOTE_ADDR="111.111.111.112")
|
REMOTE_ADDR="111.111.111.112")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
def test_push_event_detected(client):
|
def test_push_event_detected(client):
|
||||||
|
@ -108,7 +108,7 @@ def test_push_event_detected(client):
|
||||||
|
|
||||||
assert process_event_mock.call_count == 1
|
assert process_event_mock.call_count == 1
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
def test_push_event_issue_processing(client):
|
def test_push_event_issue_processing(client):
|
||||||
|
|
|
@ -50,7 +50,7 @@ def test_ok_signature(client):
|
||||||
HTTP_X_HUB_SIGNATURE="sha1=3c8e83fdaa266f81c036ea0b71e98eb5e054581a",
|
HTTP_X_HUB_SIGNATURE="sha1=3c8e83fdaa266f81c036ea0b71e98eb5e054581a",
|
||||||
content_type="application/json")
|
content_type="application/json")
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
def test_push_event_detected(client):
|
def test_push_event_detected(client):
|
||||||
|
@ -70,7 +70,7 @@ def test_push_event_detected(client):
|
||||||
|
|
||||||
assert process_event_mock.call_count == 1
|
assert process_event_mock.call_count == 1
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
def test_push_event_issue_processing(client):
|
def test_push_event_issue_processing(client):
|
||||||
|
|
|
@ -55,7 +55,7 @@ def test_ok_signature(client):
|
||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
REMOTE_ADDR="111.111.111.111")
|
REMOTE_ADDR="111.111.111.111")
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_ip(client):
|
def test_invalid_ip(client):
|
||||||
|
@ -95,7 +95,7 @@ def test_not_ip_filter(client):
|
||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
REMOTE_ADDR="111.111.111.111")
|
REMOTE_ADDR="111.111.111.111")
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
def test_push_event_detected(client):
|
def test_push_event_detected(client):
|
||||||
|
@ -115,7 +115,7 @@ def test_push_event_detected(client):
|
||||||
|
|
||||||
assert process_event_mock.call_count == 1
|
assert process_event_mock.call_count == 1
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
def test_push_event_issue_processing(client):
|
def test_push_event_issue_processing(client):
|
||||||
|
@ -340,7 +340,6 @@ def test_issues_event_bad_issue(client):
|
||||||
assert len(mail.outbox) == 0
|
assert len(mail.outbox) == 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_api_get_project_modules(client):
|
def test_api_get_project_modules(client):
|
||||||
project = f.create_project()
|
project = f.create_project()
|
||||||
membership = f.MembershipFactory(project=project, user=project.owner, is_owner=True)
|
membership = f.MembershipFactory(project=project, user=project.owner, is_owner=True)
|
||||||
|
|
Loading…
Reference in New Issue