diff --git a/taiga/projects/api.py b/taiga/projects/api.py index 17684fd3..b37250a5 100644 --- a/taiga/projects/api.py +++ b/taiga/projects/api.py @@ -20,6 +20,7 @@ from easy_thumbnails.source_generators import pil_image from dateutil.relativedelta import relativedelta from django.apps import apps +from django.conf import settings from django.db.models import signals, Prefetch from django.db.models import Value as V from django.db.models.functions import Coalesce @@ -442,9 +443,13 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin, self.pre_delete(obj) self.pre_conditions_on_delete(obj) - obj.delete_related_content() - obj.delete() - self.post_delete(obj) + + services.orphan_project(obj) + if settings.CELERY_ENABLED: + services.delete_project.delay(obj.id) + else: + services.delete_project(obj.id) + return response.NoContent() diff --git a/taiga/projects/migrations/0043_auto_20160530_1004.py b/taiga/projects/migrations/0043_auto_20160530_1004.py new file mode 100644 index 00000000..5ad8f1ad --- /dev/null +++ b/taiga/projects/migrations/0043_auto_20160530_1004.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-05-30 10:04 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0042_auto_20160525_0911'), + ] + + operations = [ + migrations.AlterField( + model_name='project', + name='owner', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='owned_projects', to=settings.AUTH_USER_MODEL, verbose_name='owner'), + ), + ] diff --git a/taiga/projects/models.py b/taiga/projects/models.py index 506c2736..4ff459e0 100644 --- a/taiga/projects/models.py +++ b/taiga/projects/models.py @@ -157,7 +157,7 @@ class Project(ProjectDefaults, TaggedMixin, models.Model): default=timezone.now) modified_date = models.DateTimeField(null=False, blank=False, verbose_name=_("modified date")) - owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False, + owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, related_name="owned_projects", verbose_name=_("owner")) members = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="projects", through="Membership", verbose_name=_("members"), diff --git a/taiga/projects/services/__init__.py b/taiga/projects/services/__init__.py index 23d6334c..d33d51e9 100644 --- a/taiga/projects/services/__init__.py +++ b/taiga/projects/services/__init__.py @@ -47,6 +47,8 @@ from .projects import check_if_project_privacity_can_be_changed from .projects import check_if_project_can_be_created_or_updated from .projects import check_if_project_can_be_transfered from .projects import check_if_project_is_out_of_owner_limits +from .projects import orphan_project +from .projects import delete_project from .stats import get_stats_for_project_issues from .stats import get_stats_for_project diff --git a/taiga/projects/services/projects.py b/taiga/projects/services/projects.py index 1ac6d2b7..43309851 100644 --- a/taiga/projects/services/projects.py +++ b/taiga/projects/services/projects.py @@ -15,8 +15,9 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from django.apps import apps from django.utils.translation import ugettext as _ - +from taiga.celery import app ERROR_MAX_PUBLIC_PROJECTS_MEMBERSHIPS = 'max_public_projects_memberships' ERROR_MAX_PRIVATE_PROJECTS_MEMBERSHIPS = 'max_private_projects_memberships' @@ -151,3 +152,21 @@ def check_if_project_is_out_of_owner_limits(project): return True return False + + +def orphan_project(project): + project.memberships.filter(user=project.owner).delete() + project.owner = None + project.save() + + +@app.task +def delete_project(project_id): + Project = apps.get_model("projects", "Project") + try: + project = Project.objects.get(id=project_id) + except Project.DoesNotExist: + return + + project.delete_related_content() + project.delete() diff --git a/tests/integration/test_projects.py b/tests/integration/test_projects.py index 28a0dbe3..fbc09722 100644 --- a/tests/integration/test_projects.py +++ b/tests/integration/test_projects.py @@ -19,6 +19,8 @@ from easy_thumbnails.files import generate_all_aliases, get_thumbnailer import os.path import pytest +from unittest import mock + pytestmark = pytest.mark.django_db class ExpiredSigner(signing.TimestampSigner): @@ -1814,3 +1816,36 @@ def test_public_project_when_project_has_unlimited_members(client): project.owner.max_memberships_public_projects = None assert check_if_project_is_out_of_owner_limits(project) == False + + +def test_delete_project_with_celery_enabled(client, settings): + settings.CELERY_ENABLED = True + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + role = f.RoleFactory.create(project=project, permissions=["view_project"]) + membership = f.MembershipFactory.create(project=project, user=user, role=role, is_admin=True) + url = reverse("projects-detail", args=(project.id,)) + client.login(user) + + #delete_project task should have been launched + with mock.patch('taiga.projects.services.delete_project') as delete_project_mock: + response = client.json.delete(url) + assert response.status_code == 204 + project = Project.objects.get(id=project.id) + assert project.owner == None + assert project.memberships.count() == 0 + delete_project_mock.delay.assert_called_once_with(project.id) + + +def test_delete_project_with_celery_disabled(client, settings): + settings.CELERY_ENABLED = False + + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + role = f.RoleFactory.create(project=project, permissions=["view_project"]) + membership = f.MembershipFactory.create(project=project, user=user, role=role, is_admin=True) + url = reverse("projects-detail", args=(project.id,)) + client.login(user) + response = client.json.delete(url) + assert response.status_code == 204 + assert Project.objects.filter(id=project.id).count() == 0