Merge pull request #250 from taigaio/refactor/bameda-style

Refactor day: @bameda style
remotes/origin/enhancement/email-actions
Jesús Espino 2015-02-17 12:45:30 +01:00
commit 6553b4d8dc
35 changed files with 380 additions and 331 deletions

View File

@ -20,13 +20,13 @@ from enum import Enum
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from rest_framework.response import Response
from rest_framework import status
from rest_framework import serializers
from taiga.base.api import viewsets
from taiga.base.decorators import list_route
from taiga.base import exceptions as exc
from taiga.base import response
from taiga.users.services import get_and_validate_user
from .serializers import PublicRegisterSerializer
@ -108,7 +108,7 @@ class AuthViewSet(viewsets.ViewSet):
raise exc.BadRequest(e.detail)
data = make_auth_response_data(user)
return Response(data, status=status.HTTP_201_CREATED)
return response.Created(data)
def _private_register(self, request):
register_type = parse_register_type(request.DATA)
@ -121,7 +121,7 @@ class AuthViewSet(viewsets.ViewSet):
user = private_register_for_new_user(**data)
data = make_auth_response_data(user)
return Response(data, status=status.HTTP_201_CREATED)
return response.Created(data)
@list_route(methods=["POST"])
def register(self, request, **kwargs):
@ -134,7 +134,6 @@ class AuthViewSet(viewsets.ViewSet):
return self._private_register(request)
raise exc.BadRequest(_("invalid register type"))
# Login view: /api/v1/auth
def create(self, request, **kwargs):
self.check_permissions(request, 'create', None)

View File

@ -29,12 +29,10 @@ from django.db import transaction as tx
from django.db import IntegrityError
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 taiga.base import exceptions as exc
from taiga.base import response
from taiga.users.serializers import UserSerializer
from taiga.users.services import get_and_validate_user
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)
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);

View File

@ -23,9 +23,7 @@ from django.core.exceptions import ValidationError
from django.http import Http404
from django.db import transaction as tx
from rest_framework import status
from rest_framework.response import Response
from rest_framework.request import clone_request
from taiga.base import response
from rest_framework.settings import api_settings
from .utils import get_object_or_404
@ -73,10 +71,9 @@ class CreateModelMixin(object):
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return response.Created(serializer.data, headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return response.BadRequest(serializer.errors)
def get_success_headers(self, data):
try:
@ -114,7 +111,7 @@ class ListModelMixin(object):
else:
serializer = self.get_serializer(self.object_list, many=True)
return Response(serializer.data)
return response.Ok(serializer.data)
class RetrieveModelMixin(object):
@ -130,7 +127,7 @@ class RetrieveModelMixin(object):
raise Http404
serializer = self.get_serializer(self.object)
return Response(serializer.data)
return response.Ok(serializer.data)
class UpdateModelMixin(object):
@ -149,7 +146,7 @@ class UpdateModelMixin(object):
files=request.FILES, partial=partial)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return response.BadRequest(serializer.errors)
# Hooks
try:
@ -158,16 +155,16 @@ class UpdateModelMixin(object):
except ValidationError as err:
# full_clean on model instance may be called in pre_save,
# so we have to handle eventual errors.
return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
return response.BadRequest(err.message_dict)
if self.object is None:
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return response.Created(serializer.data)
self.object = serializer.save(force_update=True)
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):
kwargs['partial'] = True
@ -216,4 +213,4 @@ class DestroyModelMixin(object):
self.pre_conditions_on_delete(obj)
obj.delete()
self.post_delete(obj)
return Response(status=status.HTTP_204_NO_CONTENT)
return response.NoContent()

View File

@ -27,10 +27,13 @@ from django.views.decorators.csrf import csrf_exempt
from rest_framework import status, exceptions
from rest_framework.compat import smart_text, HttpResponseBase, View
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.utils import formatting
from taiga.base.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 django.conf import settings
@ -89,12 +92,10 @@ def exception_handler(exc):
headers=headers)
elif isinstance(exc, Http404):
return Response({'detail': 'Not found'},
status=status.HTTP_404_NOT_FOUND)
return NotFound({'detail': 'Not found'})
elif isinstance(exc, PermissionDenied):
return Response({'detail': 'Permission denied'},
status=status.HTTP_403_FORBIDDEN)
return Forbidden({'detail': 'Permission denied'})
# Note: Unhandled exceptions will raise a 500 error.
return None
@ -425,7 +426,7 @@ class APIView(View):
We may as well implement this as Django will otherwise provide
a less useful default implementation.
"""
return Response(self.metadata(request), status=status.HTTP_200_OK)
return Ok(self.metadata(request))
def metadata(self, request):
"""

View File

@ -14,17 +14,16 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from rest_framework import exceptions
from rest_framework import status
from rest_framework.response import Response
from django.core.exceptions import PermissionDenied as DjangoPermissionDenied
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
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):
@ -129,15 +128,13 @@ def exception_handler(exc):
headers["X-Throttle-Wait-Seconds"] = "%d" % exc.wait
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):
return Response({'_error_message': str(exc)},
status=status.HTTP_404_NOT_FOUND)
return response.NotFound({'_error_message': str(exc)})
elif isinstance(exc, DjangoPermissionDenied):
return Response({"_error_message": str(exc)},
status=status.HTTP_403_FORBIDDEN)
return response.Forbidden({"_error_message": str(exc)})
# Note: Unhandled exceptions will raise a 500 error.
return None

View File

@ -17,15 +17,13 @@ import operator
from functools import reduce
import logging
from django.apps import apps
from django.db.models import Q
from django.db.models.sql.where import ExtraWhere, OR, AND
from rest_framework import filters
from taiga.base import tags
from taiga.base import exceptions as exc
from taiga.projects.models import Membership
logger = logging.getLogger(__name__)
@ -101,7 +99,8 @@ class PermissionBasedFilterBackend(FilterBackend):
try:
project_id = int(request.QUERY_PARAMS["project"])
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.")
qs = queryset
@ -109,7 +108,8 @@ class PermissionBasedFilterBackend(FilterBackend):
if request.user.is_authenticated() and request.user.is_superuser:
qs = qs
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:
memberships_qs = memberships_qs.filter(project_id=project_id)
memberships_qs = memberships_qs.filter(Q(role__permissions__contains=[self.permission]) |
@ -187,7 +187,8 @@ class CanViewProjectObjFilterBackend(FilterBackend):
try:
project_id = int(request.QUERY_PARAMS["project"])
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.")
qs = queryset
@ -195,7 +196,8 @@ class CanViewProjectObjFilterBackend(FilterBackend):
if request.user.is_authenticated() and request.user.is_superuser:
qs = qs
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:
memberships_qs = memberships_qs.filter(project_id=project_id)
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]
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:
qs = qs.filter(public_permissions__contains=["view_project"])
@ -221,6 +224,25 @@ class IsProjectMemberFilterBackend(FilterBackend):
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):
def get_project_ids(self, request, view):
project_id = None
@ -233,7 +255,8 @@ class BaseIsProjectAdminFilterBackend(object):
if not request.user.is_authenticated():
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:
memberships_qs = memberships_qs.filter(project_id=project_id)

View File

@ -23,7 +23,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import NoReverseMatch
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.urlpatterns import format_suffix_patterns
@ -292,7 +292,7 @@ class DRFDefaultRouter(SimpleRouter):
except NoReverseMatch:
# Support resources that are prefixed by a parametrized url
ret[key] = request.build_absolute_uri() + key
return Response(ret)
return response.Response(ret)
return APIRoot.as_view()

View File

@ -18,10 +18,6 @@ import json
import codecs
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.translation import ugettext_lazy as _
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.base import ContentFile
from taiga.base.api.mixins import CreateModelMixin
from taiga.base.api.viewsets import GenericViewSet
from rest_framework.decorators import throttle_classes
from taiga.base.decorators import detail_route, list_route
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.issues.models import Issue
from taiga.projects.serializers import ProjectSerializer
@ -65,8 +64,9 @@ class ProjectExporterViewSet(mixins.ImportThrottlingPolicyMixin, GenericViewSet)
if settings.CELERY_ENABLED:
task = tasks.dump_project.delay(request.user, project)
tasks.delete_project_dump.apply_async((project.pk, project.slug), countdown=settings.EXPORTS_TTL)
return Response({"export_id": task.id}, status=status.HTTP_202_ACCEPTED)
tasks.delete_project_dump.apply_async((project.pk, project.slug),
countdown=settings.EXPORTS_TTL)
return response.Accepted({"export_id": task.id})
path = "exports/{}/{}-{}.json".format(project.pk, project.slug, uuid.uuid4().hex)
content = ContentFile(ExportRenderer().render(service.project_to_dict(project),
@ -76,7 +76,7 @@ class ProjectExporterViewSet(mixins.ImportThrottlingPolicyMixin, GenericViewSet)
response_data = {
"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):
@ -152,7 +152,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
response_data = project_serialized.data
response_data['id'] = project_serialized.object.id
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"])
@method_decorator(atomic)
@ -181,11 +181,11 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
if settings.CELERY_ENABLED:
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)
response_data = ProjectSerializer(project).data
return Response(response_data, status=status.HTTP_201_CREATED)
return response.Created(response_data)
@detail_route(methods=['post'])
@ -204,7 +204,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
raise exc.BadRequest(errors)
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'])
@method_decorator(atomic)
@ -219,7 +219,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
raise exc.BadRequest(errors)
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'])
@method_decorator(atomic)
@ -234,7 +234,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
raise exc.BadRequest(errors)
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'])
@method_decorator(atomic)
@ -249,7 +249,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
raise exc.BadRequest(errors)
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'])
@method_decorator(atomic)
@ -264,7 +264,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
raise exc.BadRequest(errors)
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'])
@method_decorator(atomic)
@ -279,4 +279,4 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
raise exc.BadRequest(errors)
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)

View File

@ -14,11 +14,11 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from 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 import response
from taiga.base.api.viewsets import GenericViewSet
from taiga.base.utils import json
from taiga.projects.models import Project
@ -75,4 +75,4 @@ class BaseWebhookApiViewSet(GenericViewSet):
except ActionSyntaxException as e:
raise exc.BadRequest(e)
return Response({})
return response.NoContent()

View File

@ -14,18 +14,14 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from rest_framework.response import Response
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
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 . import event_hooks
from ..exceptions import ActionSyntaxException
from urllib.parse import parse_qs
from ipware.ip import get_real_ip
@ -61,7 +57,9 @@ class BitBucketViewSet(BaseWebhookApiViewSet):
if not project_secret:
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)
if valid_origin_ips and (not origin_ip or not origin_ip in valid_origin_ips):
return False

View File

@ -14,13 +14,6 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from 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 . import event_hooks
@ -51,7 +44,8 @@ class GitHubViewSet(BaseWebhookApiViewSet):
if project.modules_config.config is None:
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)
return hmac.compare_digest(mac.hexdigest(), signature)

View File

@ -14,20 +14,18 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from rest_framework.response import Response
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from taiga.base.api.viewsets import GenericViewSet
from taiga.base import exceptions as exc
from ipware.ip import get_real_ip
from taiga.base.utils import json
from taiga.projects.models import Project
from taiga.hooks.api import BaseWebhookApiViewSet
from . import event_hooks
from ipware.ip import get_real_ip
class GitLabViewSet(BaseWebhookApiViewSet):
event_hook_classes = {
@ -51,7 +49,8 @@ class GitLabViewSet(BaseWebhookApiViewSet):
if not project_secret:
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)
if valid_origin_ips and (not origin_ip or origin_ip not in valid_origin_ips):
return False

View File

@ -20,7 +20,8 @@ from django.db.models import signals
from django.core.exceptions import ValidationError
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.decorators import list_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.on_destroy import MoveOnDestroyMixin
from taiga.users.models import Role
from taiga.projects.userstories.models import UserStory
from taiga.projects.tasks.models import Task
from taiga.projects.issues.models import Issue
@ -74,7 +74,7 @@ class ProjectViewSet(ModelCrudViewSet):
modules_config = services.get_modules_config(project)
if request.method == "GET":
return response.Ok(data=modules_config.config)
return response.Ok(modules_config.config)
else:
modules_config.config.update(request.DATA)
@ -85,31 +85,31 @@ class ProjectViewSet(ModelCrudViewSet):
def stats(self, request, pk=None):
project = self.get_object()
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"])
def member_stats(self, request, pk=None):
project = self.get_object()
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"])
def issues_stats(self, request, pk=None):
project = self.get_object()
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"])
def issue_filters_data(self, request, pk=None):
project = self.get_object()
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"])
def tags_colors(self, request, pk=None):
project = self.get_object()
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"])
def star(self, request, pk=None):
@ -132,7 +132,7 @@ class ProjectViewSet(ModelCrudViewSet):
voters = votes_service.get_voters(project)
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"])
def create_template(self, request, **kwargs):
@ -325,7 +325,7 @@ class ProjectTemplateViewSet(ModelCrudViewSet):
######################################################
## Members Invitations and Roles
## Members & Invitations
######################################################
class MembershipViewSet(ModelCrudViewSet):
@ -359,7 +359,7 @@ class MembershipViewSet(ModelCrudViewSet):
return response.BadRequest(err.message_dict)
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"])
def resend_invitation(self, request, **kwargs):
@ -403,20 +403,3 @@ class InvitationViewSet(ModelListViewSet):
def list(self, *args, **kwargs):
raise exc.PermissionDenied(_("You don't have permisions to see that."))
class RolesViewSet(ModelCrudViewSet):
model = Role
serializer_class = serializers.RoleSerializer
permission_classes = (permissions.RolesPermission, )
filter_backends = (filters.CanViewProjectFilterBackend,)
filter_fields = ('project',)
def pre_delete(self, obj):
move_to = self.request.QUERY_PARAMS.get('moveTo', None)
if move_to:
role_dest = get_object_or_404(self.model, project=obj.project, id=move_to)
qs = models.Membership.objects.filter(project_id=obj.project.pk, role=obj)
qs.update(role=role_dest)
super().pre_delete(obj)

View File

@ -21,14 +21,15 @@ import mimetypes
mimetypes.init()
from django.contrib.contenttypes.models import ContentType
from taiga.base.api.utils import get_object_or_404
from django.conf import settings
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 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.projects.notifications.mixins import WatchedResourceMixin

View File

@ -17,12 +17,10 @@
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
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 response
from taiga.base.decorators import detail_route
from taiga.base.api import ReadOnlyListViewSet
from taiga.base.api.utils import get_object_or_404
from . import permissions
from . import serializers
@ -54,7 +52,7 @@ class HistoryViewSet(ReadOnlyListViewSet):
else:
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
return response.Ok(serializer.data)
@detail_route(methods=['post'])
def delete_comment(self, request, pk):
@ -65,15 +63,15 @@ class HistoryViewSet(ReadOnlyListViewSet):
self.check_permissions(request, 'delete_comment', comment)
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:
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_user = {"pk": request.user.pk, "name": request.user.get_full_name()}
comment.save()
return Response(status=status.HTTP_200_OK)
return response.Ok()
@detail_route(methods=['post'])
def undelete_comment(self, request, pk):
@ -84,20 +82,20 @@ class HistoryViewSet(ReadOnlyListViewSet):
self.check_permissions(request, 'undelete_comment', comment)
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:
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_user = None
comment.save()
return Response(status=status.HTTP_200_OK)
return response.Ok()
# Just for restframework! Because it raises
# 404 on main api root if this method not exists.
def list(self, request):
return Response({})
return response.NotFound()
def retrieve(self, request, pk):
obj = self.get_object()

View File

@ -18,14 +18,12 @@ from django.utils.translation import ugettext_lazy as _
from django.db.models import Q
from django.http import Http404
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, response
from taiga.base import filters
from taiga.base import exceptions as exc
from taiga.base import response
from taiga.base.decorators import detail_route, list_route
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
from taiga.base.api.utils import get_object_or_404
from taiga.base import tags
from taiga.users.models import User
@ -139,19 +137,24 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
super().pre_conditions_on_save(obj)
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:
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:
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:
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:
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"])
def by_ref(self, request):
@ -185,7 +188,7 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
self.check_permissions(request, 'upvote', issue)
votes_service.add_vote(issue, user=request.user)
return Response(status=status.HTTP_200_OK)
return response.Ok()
@detail_route(methods=['post'])
def downvote(self, request, pk=None):
@ -194,7 +197,7 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
self.check_permissions(request, 'downvote', issue)
votes_service.remove_vote(issue, user=request.user)
return Response(status=status.HTTP_200_OK)
return response.Ok()
class VotersViewSet(ModelListViewSet):
@ -215,7 +218,7 @@ class VotersViewSet(ModelListViewSet):
raise Http404
serializer = self.get_serializer(self.object)
return Response(serializer.data)
return response.Ok(serializer.data)
def list(self, request, *args, **kwargs):
issue_id = kwargs.get("issue_id", None)

View File

@ -14,16 +14,11 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.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 exceptions as exc
from taiga.base import response
from taiga.base.decorators import detail_route
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.history.mixins import HistoryResourceMixin
@ -97,4 +92,4 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudView
current_date = current_date + datetime.timedelta(days=1)
optimal_points -= optimal_points_per_day
return Response(milestone_stats)
return response.Ok(milestone_stats)

View File

@ -14,20 +14,10 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.utils.translation import ugettext_lazy as _
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.projects.models import Project
from taiga.projects.notifications.choices import NotifyLevel
from . import serializers

View File

@ -16,9 +16,13 @@
from django.utils.translation import ugettext_lazy as _
from taiga.base.api.permissions import (TaigaResourcePermission, HasProjectPerm,
IsAuthenticated, IsProjectOwner,
AllowAny, IsSuperUser, PermissionComponent)
from taiga.base.api.permissions import TaigaResourcePermission
from taiga.base.api.permissions import HasProjectPerm
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.projects.models import Membership
@ -32,8 +36,8 @@ class CanLeaveProject(PermissionComponent):
try:
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
except Membership.DoesNotExist:
return False
@ -140,14 +144,6 @@ class IssueTypePermission(TaigaResourcePermission):
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
class ProjectTemplatePermission(TaigaResourcePermission):

View File

@ -16,14 +16,13 @@
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 response
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 .serializers import ResolverSerializer
from . import permissions
@ -42,19 +41,22 @@ class ResolverViewSet(viewsets.ViewSet):
self.check_permissions(request, "list", project)
result = {
"project": project.pk
}
result = {"project": project.pk}
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):
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):
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):
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):
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)

View File

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

View File

@ -16,19 +16,19 @@
from contextlib import suppress
from rest_framework import status
from django.apps import apps
from django.db import transaction
from django.utils.translation import ugettext as _
from django.core.exceptions import ObjectDoesNotExist
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, response
from taiga.base import filters
from taiga.base import exceptions as exc
from taiga.base import response
from taiga.base.decorators import list_route
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.history.mixins import HistoryResourceMixin

View File

@ -17,13 +17,12 @@
from django.utils.translation import ugettext_lazy as _
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 exceptions as exc
from taiga.base import response
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.projects.models import Project
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)
if not content:
raise exc.WrongArguments({"content": "No content parameter"})
raise exc.WrongArguments({"content": _("No content parameter")})
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)
self.check_permissions(request, "render", project)
data = mdrender(project, content)
return Response({"data": data})
return response.Ok({"data": data})
def pre_save(self, obj):
if not obj.owner:

View File

@ -18,15 +18,18 @@ from taiga.base import routers
router = routers.DefaultRouter(trailing_slash=False)
# taiga.users
from taiga.users.api import UsersViewSet
# Users & Roles
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"users", UsersViewSet, base_name="users")
router.register(r"roles", RolesViewSet, base_name="roles")
#taiga.userstorage
# User Storage
from taiga.userstorage.api import StorageEntriesViewSet
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")
# Projects & Types
from taiga.projects.api import RolesViewSet
# Projects & Selectors
from taiga.projects.api import ProjectViewSet
from taiga.projects.api import MembershipViewSet
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 ProjectTemplateViewSet
router.register(r"roles", RolesViewSet, base_name="roles")
router.register(r"projects", ProjectViewSet, base_name="projects")
router.register(r"project-templates", ProjectTemplateViewSet, base_name="project-templates")
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"severities",SeverityViewSet , base_name="severities")
# Attachments
from taiga.projects.attachments.api import UserStoryAttachmentViewSet
from taiga.projects.attachments.api import IssueAttachmentViewSet
from taiga.projects.attachments.api import TaskAttachmentViewSet
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"issues/attachments", IssueAttachmentViewSet, base_name="issue-attachments")
router.register(r"wiki/attachments", WikiAttachmentViewSet, base_name="wiki-attachments")
# Webhooks
from taiga.webhooks.api import WebhookViewSet, WebhookLogViewSet
router.register(r"webhooks", WebhookViewSet, base_name="webhooks")
router.register(r"webhooklogs", WebhookLogViewSet, base_name="webhooklogs")
# History & Components
from taiga.projects.history.api import UserStoryHistory
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-links", WikiLinkViewSet, base_name="wiki-links")
# Notify policies
from taiga.projects.notifications.api import NotifyPolicyViewSet
router.register(r"notify-policies", NotifyPolicyViewSet, base_name="notifications")
# GitHub webhooks
from taiga.hooks.github.api import GitHubViewSet
router.register(r"github-hook", GitHubViewSet, base_name="github-hook")
# Gitlab webhooks
from taiga.hooks.gitlab.api import GitLabViewSet
router.register(r"gitlab-hook", GitLabViewSet, base_name="gitlab-hook")
# Bitbucket webhooks
from taiga.hooks.bitbucket.api import BitBucketViewSet
router.register(r"bitbucket-hook", BitBucketViewSet, base_name="bitbucket-hook")
# feedback
# - see taiga.feedback.routers and taiga.feedback.apps

View File

@ -16,10 +16,9 @@
from django.apps import apps
from rest_framework.response import Response
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.projects.userstories.serializers import UserStorySerializer
from taiga.projects.tasks.serializers import TaskSerializer
@ -48,7 +47,7 @@ class SearchViewSet(viewsets.ViewSet):
result["wikipages"] = self._search_wiki_pages(project, text)
result["count"] = sum(map(lambda x: len(x), result.values()))
return Response(result)
return response.Ok(result)
def _get_project(self, project_id):
project_model = apps.get_model("projects", "Project")

View File

@ -16,8 +16,7 @@
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 import GenericViewSet
@ -52,12 +51,12 @@ class TimelineViewSet(GenericViewSet):
else:
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
return response.Ok(serializer.data)
# Just for restframework! Because it raises
# 404 on main api root if this method not exists.
def list(self, request):
return Response({})
return response.NotFound()
def retrieve(self, request, pk):
obj = self.get_object()

View File

@ -23,23 +23,22 @@ from django.core.validators import validate_email
from django.core.exceptions import ValidationError
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 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.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.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 serializers
@ -47,22 +46,9 @@ from . import permissions
from .signals import user_cancel_account as user_cancel_account_signal
class MembersFilterBackend(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
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 []
######################################################
## User
######################################################
class UsersViewSet(ModelCrudViewSet):
permission_classes = (permissions.UserPermission,)
@ -73,7 +59,9 @@ class UsersViewSet(ModelCrudViewSet):
raise exc.NotSupported()
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)
if page is not None:
@ -81,7 +69,7 @@ class UsersViewSet(ModelCrudViewSet):
else:
serializer = self.get_serializer(self.object_list, many=True)
return Response(serializer.data)
return response.Ok(serializer.data)
@list_route(methods=["POST"])
def password_recovery(self, request, pk=None):
@ -106,7 +94,7 @@ class UsersViewSet(ModelCrudViewSet):
email = mbuilder.password_recovery(user.email, {"user": user})
email.send()
return Response({"detail": _("Mail sended successful!"),
return response.Ok({"detail": _("Mail sended successful!"),
"email": user.email})
@list_route(methods=["POST"])
@ -130,7 +118,7 @@ class UsersViewSet(ModelCrudViewSet):
user.token = None
user.save(update_fields=["password", "token"])
return Response(status=status.HTTP_204_NO_CONTENT)
return response.NoContent()
@list_route(methods=["POST"])
def change_password(self, request, pk=None):
@ -158,7 +146,7 @@ class UsersViewSet(ModelCrudViewSet):
request.user.set_password(password)
request.user.save(update_fields=["password"])
return Response(status=status.HTTP_204_NO_CONTENT)
return response.NoContent()
@list_route(methods=["POST"])
def change_avatar(self, request):
@ -181,7 +169,7 @@ class UsersViewSet(ModelCrudViewSet):
request.user.save(update_fields=["photo"])
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"])
def remove_avatar(self, request):
@ -192,7 +180,7 @@ class UsersViewSet(ModelCrudViewSet):
request.user.photo = None
request.user.save(update_fields=["photo"])
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"])
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_data = StarredSerializer(stars, many=True)
return Response(stars_data.data)
return response.Ok(stars_data.data)
#TODO: commit_on_success
def partial_update(self, request, *args, **kwargs):
@ -249,12 +237,14 @@ class UsersViewSet(ModelCrudViewSet):
"""
serializer = serializers.ChangeEmailSerializer(data=request.DATA, many=False)
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:
user = models.User.objects.get(email_token=serializer.data["email_token"])
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)
user.email = user.new_email
@ -262,7 +252,7 @@ class UsersViewSet(ModelCrudViewSet):
user.email_token = None
user.save(update_fields=["email", "new_email", "email_token"])
return Response(status=status.HTTP_204_NO_CONTENT)
return response.NoContent()
@list_route(methods=["GET"])
def me(self, request, pk=None):
@ -271,7 +261,7 @@ class UsersViewSet(ModelCrudViewSet):
"""
self.check_permissions(request, "me", None)
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"])
def cancel(self, request, pk=None):
@ -294,7 +284,7 @@ class UsersViewSet(ModelCrudViewSet):
raise exc.WrongArguments(_("Invalid, are you sure the token is correct?"))
user.cancel()
return Response(status=status.HTTP_204_NO_CONTENT)
return response.NoContent()
def destroy(self, request, pk=None):
user = self.get_object()
@ -303,4 +293,26 @@ class UsersViewSet(ModelCrudViewSet):
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()
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)

View File

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

View File

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

View File

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

View File

@ -16,10 +16,11 @@
import json
from rest_framework.response import Response
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.decorators import detail_route
@ -44,7 +45,8 @@ class WebhookViewSet(ModelCrudViewSet):
webhooklog = tasks.test_webhook(webhook.id, webhook.url, webhook.key)
log = serializers.WebhookLogSerializer(webhooklog)
return Response(log.data)
return response.Ok(log.data)
class WebhookLogViewSet(ModelListViewSet):
model = models.WebhookLog
@ -60,7 +62,9 @@ class WebhookLogViewSet(ModelListViewSet):
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)
return Response(log.data)
return response.Ok(log.data)

View File

@ -2,6 +2,7 @@ from django.core.urlresolvers import reverse
from taiga.base.utils import json
from taiga.projects import serializers
from taiga.users.serializers import RoleSerializer
from taiga.permissions.permissions import MEMBERS_PERMISSIONS
from tests import factories as f
@ -140,19 +141,19 @@ def test_roles_update(client, data):
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 = json.dumps(role_data)
results = helper_test_http_method(client, 'put', public_url, role_data, users)
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 = json.dumps(role_data)
results = helper_test_http_method(client, 'put', private1_url, role_data, users)
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 = json.dumps(role_data)
results = helper_test_http_method(client, 'put', private2_url, role_data, users)

View File

@ -55,7 +55,7 @@ def test_ok_signature(client):
urllib.parse.urlencode(data, True),
content_type="application/x-www-form-urlencoded",
REMOTE_ADDR=settings.BITBUCKET_VALID_ORIGIN_IPS[0])
assert response.status_code == 200
assert response.status_code == 204
def test_invalid_ip(client):
project=f.ProjectFactory()
@ -91,7 +91,7 @@ def test_not_ip_filter(client):
urllib.parse.urlencode(data, True),
content_type="application/x-www-form-urlencoded",
REMOTE_ADDR="111.111.111.112")
assert response.status_code == 200
assert response.status_code == 204
def test_push_event_detected(client):
@ -108,7 +108,7 @@ def test_push_event_detected(client):
assert process_event_mock.call_count == 1
assert response.status_code == 200
assert response.status_code == 204
def test_push_event_issue_processing(client):

View File

@ -50,7 +50,7 @@ def test_ok_signature(client):
HTTP_X_HUB_SIGNATURE="sha1=3c8e83fdaa266f81c036ea0b71e98eb5e054581a",
content_type="application/json")
assert response.status_code == 200
assert response.status_code == 204
def test_push_event_detected(client):
@ -70,7 +70,7 @@ def test_push_event_detected(client):
assert process_event_mock.call_count == 1
assert response.status_code == 200
assert response.status_code == 204
def test_push_event_issue_processing(client):

View File

@ -55,7 +55,7 @@ def test_ok_signature(client):
content_type="application/json",
REMOTE_ADDR="111.111.111.111")
assert response.status_code == 200
assert response.status_code == 204
def test_invalid_ip(client):
@ -95,7 +95,7 @@ def test_not_ip_filter(client):
content_type="application/json",
REMOTE_ADDR="111.111.111.111")
assert response.status_code == 200
assert response.status_code == 204
def test_push_event_detected(client):
@ -115,7 +115,7 @@ def test_push_event_detected(client):
assert process_event_mock.call_count == 1
assert response.status_code == 200
assert response.status_code == 204
def test_push_event_issue_processing(client):
@ -340,7 +340,6 @@ def test_issues_event_bad_issue(client):
assert len(mail.outbox) == 0
def test_api_get_project_modules(client):
project = f.create_project()
membership = f.MembershipFactory(project=project, user=project.owner, is_owner=True)