diff --git a/taiga/export_import/api.py b/taiga/export_import/api.py index cdfc36f0..6f20cc60 100644 --- a/taiga/export_import/api.py +++ b/taiga/export_import/api.py @@ -36,6 +36,7 @@ from taiga.projects.models import Project, Membership from taiga.projects.issues.models import Issue from taiga.projects.tasks.models import Task from taiga.projects.serializers import ProjectSerializer +from taiga.users import services as users_service from . import mixins from . import serializers @@ -90,6 +91,10 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi data = request.DATA.copy() data['owner'] = data.get('owner', request.user.email) + is_private = data.get('is_private', False) + if not users_service.has_available_slot_for_project(self.request.user, is_private=is_private): + raise exc.BadRequest(_("The user can't have more projects of this type")) + # Create Project project_serialized = service.store_project(data) @@ -202,17 +207,22 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi try: dump = json.load(reader(dump)) + is_private = dump["is_private"] except Exception: raise exc.WrongArguments(_("Invalid dump format")) if Project.objects.filter(slug=dump['slug']).exists(): del dump['slug'] + user = request.user + if not users_service.has_available_slot_for_project(user, is_private=is_private): + raise exc.BadRequest(_("The user can't have more projects of this type")) + if settings.CELERY_ENABLED: - task = tasks.load_project_dump.delay(request.user, dump) + task = tasks.load_project_dump.delay(user, dump) 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) response_data = ProjectSerializer(project).data return response.Created(response_data) diff --git a/taiga/export_import/dump_service.py b/taiga/export_import/dump_service.py index 8029fa0f..0237145a 100644 --- a/taiga/export_import/dump_service.py +++ b/taiga/export_import/dump_service.py @@ -18,6 +18,7 @@ from django.utils.translation import ugettext as _ from taiga.projects.models import Membership +from taiga.users import services as users_service from . import serializers from . import service @@ -89,7 +90,9 @@ def store_tags_colors(project, data): def dict_to_project(data, owner=None): if owner: - data["owner"] = owner + data["owner"] = owner.email + if not users_service.has_available_slot_for_project(owner, is_private=data["is_private"]): + raise TaigaImportError(_("The user can't have more projects of this type")) project_serialized = service.store_project(data) diff --git a/taiga/export_import/management/commands/load_dump.py b/taiga/export_import/management/commands/load_dump.py index 1b44adbf..bea52417 100644 --- a/taiga/export_import/management/commands/load_dump.py +++ b/taiga/export_import/management/commands/load_dump.py @@ -25,7 +25,7 @@ from taiga.projects.models import Project from taiga.export_import.renderers import ExportRenderer from taiga.export_import.dump_service import dict_to_project, TaigaImportError from taiga.export_import.service import get_errors - +from taiga.users.models import User class Command(BaseCommand): args = ' ' @@ -58,7 +58,9 @@ class Command(BaseCommand): except Project.DoesNotExist: pass signals.post_delete.receivers = receivers_back - dict_to_project(data, args[1]) + + user = User.objects.get(email=args[1]) + dict_to_project(data, user) except TaigaImportError as e: print("ERROR:", end=" ") print(e.message) diff --git a/taiga/export_import/tasks.py b/taiga/export_import/tasks.py index c6389b8b..8044f35c 100644 --- a/taiga/export_import/tasks.py +++ b/taiga/export_import/tasks.py @@ -79,7 +79,7 @@ def delete_project_dump(project_id, project_slug, task_id): @app.task def load_project_dump(user, dump): try: - project = dict_to_project(dump, user.email) + project = dict_to_project(dump, user) except Exception: ctx = { "user": user, diff --git a/tests/integration/test_importer_api.py b/tests/integration/test_importer_api.py index 360550fb..996e45c6 100644 --- a/tests/integration/test_importer_api.py +++ b/tests/integration/test_importer_api.py @@ -23,6 +23,7 @@ from django.core.urlresolvers import reverse from django.core.files.base import ContentFile from taiga.base.utils import json +from taiga.export_import.dump_service import dict_to_project, TaigaImportError from taiga.projects.models import Project, Membership from taiga.projects.issues.models import Issue from taiga.projects.userstories.models import UserStory @@ -72,6 +73,85 @@ def test_valid_project_import_without_extra_data(client): assert response_data["watchers"] == [user.email, user_watching.email] +def test_valid_project_without_enough_public_projects_slots(client): + user = f.UserFactory.create(max_public_projects=0) + + url = reverse("importer-list") + data = { + "slug": "public-project-without-slots", + "name": "Imported project", + "description": "Imported project", + "roles": [{"name": "Role"}], + "is_private": False + } + + client.login(user) + response = client.json.post(url, json.dumps(data)) + + assert response.status_code == 400 + assert "can't have more projects" in response.data["_error_message"] + assert Project.objects.filter(slug="public-project-without-slots").count() == 0 + + +def test_valid_project_without_enough_private_projects_slots(client): + user = f.UserFactory.create(max_private_projects=0) + + url = reverse("importer-list") + data = { + "slug": "private-project-without-slots", + "name": "Imported project", + "description": "Imported project", + "roles": [{"name": "Role"}], + "is_private": True + } + + client.login(user) + response = client.json.post(url, json.dumps(data)) + + assert response.status_code == 400 + assert "can't have more projects" in response.data["_error_message"] + assert Project.objects.filter(slug="private-project-without-slots").count() == 0 + + +def test_valid_project_with_enough_public_projects_slots(client): + user = f.UserFactory.create(max_public_projects=1) + + url = reverse("importer-list") + data = { + "slug": "public-project-with-slots", + "name": "Imported project", + "description": "Imported project", + "roles": [{"name": "Role"}], + "is_private": False + } + + client.login(user) + response = client.json.post(url, json.dumps(data)) + + print(response.content) + assert response.status_code == 201 + assert Project.objects.filter(slug="public-project-with-slots").count() == 1 + + +def test_valid_project_with_enough_private_projects_slots(client): + user = f.UserFactory.create(max_private_projects=1) + + url = reverse("importer-list") + data = { + "slug": "private-project-with-slots", + "name": "Imported project", + "description": "Imported project", + "roles": [{"name": "Role"}], + "is_private": True + } + + client.login(user) + response = client.json.post(url, json.dumps(data)) + + assert response.status_code == 201 + assert Project.objects.filter(slug="private-project-with-slots").count() == 1 + + def test_valid_project_import_with_not_existing_memberships(client): user = f.UserFactory.create() client.login(user) @@ -930,6 +1010,22 @@ def test_milestone_import_duplicated_milestone(client): assert response_data["milestones"][0]["name"][0] == "Name duplicated for the project" +def test_dict_to_project_with_no_slots_available(client): + user = f.UserFactory.create(max_private_projects=0) + + data = { + "slug": "valid-project", + "name": "Valid project", + "description": "Valid project desc", + "is_private": True + } + + with pytest.raises(TaigaImportError) as excinfo: + project = dict_to_project(data, owner=user) + + assert "can't have more projects" in str(excinfo.value) + + def test_invalid_dump_import(client): user = f.UserFactory.create() client.login(user) @@ -986,7 +1082,8 @@ def test_valid_dump_import_with_celery_disabled(client, settings): data = ContentFile(bytes(json.dumps({ "slug": "valid-project", "name": "Valid project", - "description": "Valid project desc" + "description": "Valid project desc", + "is_private": True }), "utf-8")) data.name = "test" @@ -1008,7 +1105,8 @@ def test_valid_dump_import_with_celery_enabled(client, settings): data = ContentFile(bytes(json.dumps({ "slug": "valid-project", "name": "Valid project", - "description": "Valid project desc" + "description": "Valid project desc", + "is_private": True }), "utf-8")) data.name = "test" @@ -1028,7 +1126,8 @@ def test_dump_import_duplicated_project(client): data = ContentFile(bytes(json.dumps({ "slug": project.slug, "name": "Test import", - "description": "Valid project desc" + "description": "Valid project desc", + "is_private": True }), "utf-8")) data.name = "test" @@ -1051,7 +1150,8 @@ def test_dump_import_throttling(client, settings): data = ContentFile(bytes(json.dumps({ "slug": project.slug, "name": "Test import", - "description": "Valid project desc" + "description": "Valid project desc", + "is_private": True }), "utf-8")) data.name = "test" @@ -1059,3 +1159,43 @@ def test_dump_import_throttling(client, settings): assert response.status_code == 201 response = client.post(url, {'dump': data}) assert response.status_code == 429 + + +def test_valid_dump_import_without_enough_public_projects_slots(client): + user = f.UserFactory.create(max_public_projects=0) + client.login(user) + + url = reverse("importer-load-dump") + + data = ContentFile(bytes(json.dumps({ + "slug": "public-project-without-slots", + "name": "Valid project", + "description": "Valid project desc", + "is_private": False + }), "utf-8")) + data.name = "test" + + response = client.post(url, {'dump': data}) + assert response.status_code == 400 + assert "can't have more projects" in response.data["_error_message"] + assert Project.objects.filter(slug="public-project-without-slots").count() == 0 + + +def test_valid_dump_import_without_enough_private_projects_slots(client): + user = f.UserFactory.create(max_private_projects=0) + client.login(user) + + url = reverse("importer-load-dump") + + data = ContentFile(bytes(json.dumps({ + "slug": "private-project-without-slots", + "name": "Valid project", + "description": "Valid project desc", + "is_private": True + }), "utf-8")) + data.name = "test" + + response = client.post(url, {'dump': data}) + assert response.status_code == 400 + assert "can't have more projects" in response.data["_error_message"] + assert Project.objects.filter(slug="private-project-without-slots").count() == 0