Adding project info on about slots when import/create/edit fails
parent
e4ba94f67f
commit
2457e8b705
|
@ -433,7 +433,9 @@ REST_FRAMEWORK = {
|
||||||
# Extra expose header related to Taiga APP (see taiga.base.middleware.cors=)
|
# Extra expose header related to Taiga APP (see taiga.base.middleware.cors=)
|
||||||
APP_EXTRA_EXPOSE_HEADERS = [
|
APP_EXTRA_EXPOSE_HEADERS = [
|
||||||
"taiga-info-total-opened-milestones",
|
"taiga-info-total-opened-milestones",
|
||||||
"taiga-info-total-closed-milestones"
|
"taiga-info-total-closed-milestones",
|
||||||
|
"taiga-info-project-memberships",
|
||||||
|
"taiga-info-project-is-private"
|
||||||
]
|
]
|
||||||
|
|
||||||
DEFAULT_PROJECT_TEMPLATE = "scrum"
|
DEFAULT_PROJECT_TEMPLATE = "scrum"
|
||||||
|
|
|
@ -202,10 +202,27 @@ class NotAuthenticated(NotAuthenticated):
|
||||||
|
|
||||||
|
|
||||||
class Blocked(APIException):
|
class Blocked(APIException):
|
||||||
|
"""
|
||||||
|
Exception used on blocked projects
|
||||||
|
"""
|
||||||
status_code = status.HTTP_451_BLOCKED
|
status_code = status.HTTP_451_BLOCKED
|
||||||
default_detail = _("Blocked element")
|
default_detail = _("Blocked element")
|
||||||
|
|
||||||
|
|
||||||
|
class NotEnoughSlotsForProject(BaseException):
|
||||||
|
"""
|
||||||
|
Exception used on import/edition/creation project errors where the user
|
||||||
|
hasn't slots enough
|
||||||
|
"""
|
||||||
|
default_detail = _("Not enough slots for project.")
|
||||||
|
|
||||||
|
def __init__(self, is_private, total_memberships, detail=None):
|
||||||
|
self.detail = detail or self.default_detail
|
||||||
|
self.project_data = {
|
||||||
|
"is_private": is_private,
|
||||||
|
"total_memberships": total_memberships
|
||||||
|
}
|
||||||
|
|
||||||
def format_exception(exc):
|
def format_exception(exc):
|
||||||
if isinstance(exc.detail, (dict, list, tuple,)):
|
if isinstance(exc.detail, (dict, list, tuple,)):
|
||||||
detail = exc.detail
|
detail = exc.detail
|
||||||
|
@ -237,6 +254,9 @@ def exception_handler(exc):
|
||||||
headers["WWW-Authenticate"] = exc.auth_header
|
headers["WWW-Authenticate"] = exc.auth_header
|
||||||
if getattr(exc, "wait", None):
|
if getattr(exc, "wait", None):
|
||||||
headers["X-Throttle-Wait-Seconds"] = "%d" % exc.wait
|
headers["X-Throttle-Wait-Seconds"] = "%d" % exc.wait
|
||||||
|
if getattr(exc, "project_data", None):
|
||||||
|
headers["Taiga-Info-Project-Memberships"] = exc.project_data["total_memberships"]
|
||||||
|
headers["Taiga-Info-Project-Is-Private"] = exc.project_data["is_private"]
|
||||||
|
|
||||||
detail = format_exception(exc)
|
detail = format_exception(exc)
|
||||||
return response.Response(detail, status=exc.status_code, headers=headers)
|
return response.Response(detail, status=exc.status_code, headers=headers)
|
||||||
|
|
|
@ -97,7 +97,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
|
||||||
project=Project(is_private=is_private, id=None)
|
project=Project(is_private=is_private, id=None)
|
||||||
)
|
)
|
||||||
if not enough_slots:
|
if not enough_slots:
|
||||||
raise exc.BadRequest(not_enough_slots_error)
|
raise exc.NotEnoughSlotsForProject(is_private, 1, not_enough_slots_error)
|
||||||
|
|
||||||
# Create Project
|
# Create Project
|
||||||
project_serialized = service.store_project(data)
|
project_serialized = service.store_project(data)
|
||||||
|
@ -122,7 +122,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
|
||||||
members=max(members, 1)
|
members=max(members, 1)
|
||||||
)
|
)
|
||||||
if not enough_slots:
|
if not enough_slots:
|
||||||
raise exc.BadRequest(not_enough_slots_error)
|
raise exc.NotEnoughSlotsForProject(is_private, max(members, 1), not_enough_slots_error)
|
||||||
service.store_memberships(project_serialized.object, data)
|
service.store_memberships(project_serialized.object, data)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -224,13 +224,6 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
|
||||||
raise exc.WrongArguments(_("Invalid dump format"))
|
raise exc.WrongArguments(_("Invalid dump format"))
|
||||||
|
|
||||||
user = request.user
|
user = request.user
|
||||||
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
|
||||||
user,
|
|
||||||
project=Project(is_private=is_private, id=None)
|
|
||||||
)
|
|
||||||
if not enough_slots:
|
|
||||||
raise exc.BadRequest(not_enough_slots_error)
|
|
||||||
|
|
||||||
slug = dump.get('slug', None)
|
slug = dump.get('slug', None)
|
||||||
if slug is not None and Project.objects.filter(slug=slug).exists():
|
if slug is not None and Project.objects.filter(slug=slug).exists():
|
||||||
del dump['slug']
|
del dump['slug']
|
||||||
|
@ -242,7 +235,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
|
||||||
members=max(members, 1)
|
members=max(members, 1)
|
||||||
)
|
)
|
||||||
if not enough_slots:
|
if not enough_slots:
|
||||||
raise exc.BadRequest(not_enough_slots_error)
|
raise exc.NotEnoughSlotsForProject(is_private, max(members, 1), not_enough_slots_error)
|
||||||
|
|
||||||
if settings.CELERY_ENABLED:
|
if settings.CELERY_ENABLED:
|
||||||
task = tasks.load_project_dump.delay(user, dump)
|
task = tasks.load_project_dump.delay(user, dump)
|
||||||
|
|
|
@ -378,7 +378,7 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin,
|
||||||
members=members
|
members=members
|
||||||
)
|
)
|
||||||
if not enough_slots:
|
if not enough_slots:
|
||||||
raise exc.BadRequest(not_enough_slots_error)
|
raise exc.NotEnoughSlotsForProject(project.is_private, members, not_enough_slots_error)
|
||||||
|
|
||||||
reason = request.DATA.get('reason', None)
|
reason = request.DATA.get('reason', None)
|
||||||
services.accept_project_transfer(project, request.user, token, reason)
|
services.accept_project_transfer(project, request.user, token, reason)
|
||||||
|
@ -414,8 +414,9 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin,
|
||||||
def pre_save(self, obj):
|
def pre_save(self, obj):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(user, project=obj)
|
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(user, project=obj)
|
||||||
|
members = max(obj.memberships.count(), 1)
|
||||||
if not enough_slots:
|
if not enough_slots:
|
||||||
raise exc.BadRequest(not_enough_slots_error)
|
raise exc.NotEnoughSlotsForProject(obj.is_private, members, not_enough_slots_error)
|
||||||
|
|
||||||
if not obj.id:
|
if not obj.id:
|
||||||
obj.owner = user
|
obj.owner = user
|
||||||
|
@ -624,13 +625,14 @@ class MembershipViewSet(BlockedByProjectMixin, ModelCrudViewSet):
|
||||||
# of handling explicit exception catchin here.
|
# of handling explicit exception catchin here.
|
||||||
|
|
||||||
if "bulk_memberships" in data and isinstance(data["bulk_memberships"], list):
|
if "bulk_memberships" in data and isinstance(data["bulk_memberships"], list):
|
||||||
|
members = len(data["bulk_memberships"])
|
||||||
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
||||||
request.user,
|
request.user,
|
||||||
project=project,
|
project=project,
|
||||||
members=len(data["bulk_memberships"])
|
members=members
|
||||||
)
|
)
|
||||||
if not enough_slots:
|
if not enough_slots:
|
||||||
raise exc.BadRequest(not_enough_slots_error)
|
raise exc.NotEnoughSlotsForProject(project.is_private, members, not_enough_slots_error)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
members = services.create_members_in_bulk(data["bulk_memberships"],
|
members = services.create_members_in_bulk(data["bulk_memberships"],
|
||||||
|
@ -660,13 +662,14 @@ class MembershipViewSet(BlockedByProjectMixin, ModelCrudViewSet):
|
||||||
|
|
||||||
def pre_save(self, obj):
|
def pre_save(self, obj):
|
||||||
if not obj.id:
|
if not obj.id:
|
||||||
|
members = 1
|
||||||
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
||||||
self.request.user,
|
self.request.user,
|
||||||
project=obj.project,
|
project=obj.project,
|
||||||
members=1
|
members=members
|
||||||
)
|
)
|
||||||
if not enough_slots:
|
if not enough_slots:
|
||||||
raise exc.BadRequest(not_enough_slots_error)
|
raise exc.NotEnoughSlotsForProject(obj.project.is_private, members, not_enough_slots_error)
|
||||||
|
|
||||||
if not obj.token:
|
if not obj.token:
|
||||||
obj.token = str(uuid.uuid1())
|
obj.token = str(uuid.uuid1())
|
||||||
|
|
|
@ -91,6 +91,8 @@ def test_valid_project_without_enough_public_projects_slots(client):
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert "can't have more public projects" in response.data["_error_message"]
|
assert "can't have more public projects" in response.data["_error_message"]
|
||||||
assert Project.objects.filter(slug="public-project-without-slots").count() == 0
|
assert Project.objects.filter(slug="public-project-without-slots").count() == 0
|
||||||
|
assert response["Taiga-Info-Project-Memberships"] == "1"
|
||||||
|
assert response["Taiga-Info-Project-Is-Private"] == "False"
|
||||||
|
|
||||||
|
|
||||||
def test_valid_project_without_enough_private_projects_slots(client):
|
def test_valid_project_without_enough_private_projects_slots(client):
|
||||||
|
@ -110,6 +112,8 @@ def test_valid_project_without_enough_private_projects_slots(client):
|
||||||
|
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert "can't have more private projects" in response.data["_error_message"]
|
assert "can't have more private projects" in response.data["_error_message"]
|
||||||
|
assert response["Taiga-Info-Project-Memberships"] == "1"
|
||||||
|
assert response["Taiga-Info-Project-Is-Private"] == "True"
|
||||||
assert Project.objects.filter(slug="private-project-without-slots").count() == 0
|
assert Project.objects.filter(slug="private-project-without-slots").count() == 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -1249,6 +1253,8 @@ def test_valid_dump_import_without_enough_public_projects_slots(client):
|
||||||
response = client.post(url, {'dump': data})
|
response = client.post(url, {'dump': data})
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert "can't have more public projects" in response.data["_error_message"]
|
assert "can't have more public projects" in response.data["_error_message"]
|
||||||
|
assert response["Taiga-Info-Project-Memberships"] == "1"
|
||||||
|
assert response["Taiga-Info-Project-Is-Private"] == "False"
|
||||||
assert Project.objects.filter(slug="public-project-without-slots").count() == 0
|
assert Project.objects.filter(slug="public-project-without-slots").count() == 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -1269,6 +1275,8 @@ def test_valid_dump_import_without_enough_private_projects_slots(client):
|
||||||
response = client.post(url, {'dump': data})
|
response = client.post(url, {'dump': data})
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert "can't have more private projects" in response.data["_error_message"]
|
assert "can't have more private projects" in response.data["_error_message"]
|
||||||
|
assert response["Taiga-Info-Project-Memberships"] == "1"
|
||||||
|
assert response["Taiga-Info-Project-Is-Private"] == "True"
|
||||||
assert Project.objects.filter(slug="private-project-without-slots").count() == 0
|
assert Project.objects.filter(slug="private-project-without-slots").count() == 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,8 @@ def test_create_private_project_without_enough_private_projects_slots(client):
|
||||||
|
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert "can't have more private projects" in response.data["_error_message"]
|
assert "can't have more private projects" in response.data["_error_message"]
|
||||||
|
assert response["Taiga-Info-Project-Memberships"] == "1"
|
||||||
|
assert response["Taiga-Info-Project-Is-Private"] == "True"
|
||||||
|
|
||||||
|
|
||||||
def test_create_public_project_without_enough_public_projects_slots(client):
|
def test_create_public_project_without_enough_public_projects_slots(client):
|
||||||
|
@ -86,6 +88,8 @@ def test_create_public_project_without_enough_public_projects_slots(client):
|
||||||
|
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert "can't have more public projects" in response.data["_error_message"]
|
assert "can't have more public projects" in response.data["_error_message"]
|
||||||
|
assert response["Taiga-Info-Project-Memberships"] == "1"
|
||||||
|
assert response["Taiga-Info-Project-Is-Private"] == "False"
|
||||||
|
|
||||||
|
|
||||||
def test_change_project_from_private_to_public_without_enough_public_projects_slots(client):
|
def test_change_project_from_private_to_public_without_enough_public_projects_slots(client):
|
||||||
|
@ -102,6 +106,8 @@ def test_change_project_from_private_to_public_without_enough_public_projects_sl
|
||||||
|
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert "can't have more public projects" in response.data["_error_message"]
|
assert "can't have more public projects" in response.data["_error_message"]
|
||||||
|
assert response["Taiga-Info-Project-Memberships"] == "1"
|
||||||
|
assert response["Taiga-Info-Project-Is-Private"] == "False"
|
||||||
|
|
||||||
|
|
||||||
def test_change_project_from_public_to_private_without_enough_private_projects_slots(client):
|
def test_change_project_from_public_to_private_without_enough_private_projects_slots(client):
|
||||||
|
@ -118,6 +124,8 @@ def test_change_project_from_public_to_private_without_enough_private_projects_s
|
||||||
|
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert "can't have more private projects" in response.data["_error_message"]
|
assert "can't have more private projects" in response.data["_error_message"]
|
||||||
|
assert response["Taiga-Info-Project-Memberships"] == "1"
|
||||||
|
assert response["Taiga-Info-Project-Is-Private"] == "True"
|
||||||
|
|
||||||
|
|
||||||
def test_create_private_project_with_enough_private_projects_slots(client):
|
def test_create_private_project_with_enough_private_projects_slots(client):
|
||||||
|
|
Loading…
Reference in New Issue