diff --git a/python/apps/taiga/src/taiga/base/db/exceptions.py b/python/apps/taiga/src/taiga/base/db/exceptions.py index 0d2580598..1bc36e2e6 100644 --- a/python/apps/taiga/src/taiga/base/db/exceptions.py +++ b/python/apps/taiga/src/taiga/base/db/exceptions.py @@ -6,6 +6,7 @@ # Copyright (c) 2023-present Kaleidos INC +from django.db.models.deletion import RestrictedError # noqa from django.db.utils import IntegrityError, ProgrammingError # noqa diff --git a/python/apps/taiga/src/taiga/commons/storage/repositories.py b/python/apps/taiga/src/taiga/commons/storage/repositories.py index 4227db2b8..244ae5a1f 100644 --- a/python/apps/taiga/src/taiga/commons/storage/repositories.py +++ b/python/apps/taiga/src/taiga/commons/storage/repositories.py @@ -9,6 +9,7 @@ from typing import TypedDict from uuid import UUID +from taiga.base.db.exceptions import RestrictedError from taiga.base.db.models import QuerySet from taiga.base.utils.datetime import aware_utcnow from taiga.base.utils.files import File @@ -68,9 +69,17 @@ async def list_storaged_objects(filters: StoragedObjectFilters = {}) -> list[Sto async def delete_storaged_object( storaged_object: StoragedObject, -) -> None: - await storaged_object.adelete() - storaged_object.file.delete(save=False) +) -> bool: + try: + await storaged_object.adelete() + storaged_object.file.delete(save=False) + return True + except RestrictedError: + # This happens when you try to delete a StoragedObject that is being used by someone + # (using ForeignKey with on_delete=PROTECT). + + # TODO: log this + return False def mark_storaged_object_as_deleted( diff --git a/python/apps/taiga/src/taiga/commons/storage/services.py b/python/apps/taiga/src/taiga/commons/storage/services.py index 915a6c4f5..75e744aee 100644 --- a/python/apps/taiga/src/taiga/commons/storage/services.py +++ b/python/apps/taiga/src/taiga/commons/storage/services.py @@ -12,8 +12,9 @@ async def clean_deleted_storaged_objects(before: datetime) -> int: storaged_objects = await storage_repositories.list_storaged_objects(filters={"deleted_before": before}) - + deleted = 0 for storaged_object in storaged_objects: - await storage_repositories.delete_storaged_object(storaged_object=storaged_object) + if storage_repositories.delete_storaged_object(storaged_object=storaged_object): + deleted += 1 - return len(storaged_objects) + return deleted diff --git a/python/apps/taiga/tests/conf_django.py b/python/apps/taiga/tests/conf_django.py index 462b47cb7..1fffa0d54 100644 --- a/python/apps/taiga/tests/conf_django.py +++ b/python/apps/taiga/tests/conf_django.py @@ -8,7 +8,8 @@ import os -from taiga.base.django.settings import * # noqa, pylint: disable=unused-wildcard-import +from taiga.base.django.settings import * # noqa +from taiga.base.django.settings import INSTALLED_APPS DEBUG = True @@ -16,9 +17,12 @@ STATIC_ROOT = "/tmp/taiga/static" -INSTALLED_APPS += [ # noqa: F405 - "tests.samples.occ", -] +INSTALLED_APPS = [ + # NOTE: We should add sample apps first because pytest-django uses an strange strategy to load models to the db and + # it causes integrity errors with foreign keys and many to many fields + "tests.samples.test_occ", + "tests.samples.test_storage", +] + INSTALLED_APPS # This is only for GitHubActions diff --git a/python/apps/taiga/tests/conftest.py b/python/apps/taiga/tests/conftest.py index cd0801e65..1a8e72fa3 100644 --- a/python/apps/taiga/tests/conftest.py +++ b/python/apps/taiga/tests/conftest.py @@ -55,9 +55,11 @@ def django_db_setup(django_db_setup, django_db_blocker): @pytest_asyncio.fixture(scope="session", autouse=True) async def connect_events_manage_on_startup(): - from taiga.events import connect_events_manager + from taiga.events import connect_events_manager, disconnect_events_manager await connect_events_manager() + yield + await disconnect_events_manager() # @@ -65,7 +67,6 @@ async def connect_events_manage_on_startup(): # def pytest_addoption(parser): parser.addoption("--slow_only", action="store_true", default=False, help="run slow tests only") - parser.addoption("--fast_only", action="store_true", default=False, help="exclude slow tests") diff --git a/python/apps/taiga/tests/integration/taiga/base/occ/test_repositories.py b/python/apps/taiga/tests/integration/taiga/base/occ/test_repositories.py index 07a542819..70eee8365 100644 --- a/python/apps/taiga/tests/integration/taiga/base/occ/test_repositories.py +++ b/python/apps/taiga/tests/integration/taiga/base/occ/test_repositories.py @@ -7,7 +7,7 @@ import pytest from taiga.base.occ import repositories -from tests.samples.occ.models import SampleOCCItem +from tests.samples.test_occ.models import SampleOCCItem pytestmark = pytest.mark.django_db(transaction=True) diff --git a/python/apps/taiga/tests/integration/taiga/commons/storage/test_repositories.py b/python/apps/taiga/tests/integration/taiga/commons/storage/test_repositories.py index 28d42d4e4..13b41c76c 100644 --- a/python/apps/taiga/tests/integration/taiga/commons/storage/test_repositories.py +++ b/python/apps/taiga/tests/integration/taiga/commons/storage/test_repositories.py @@ -12,6 +12,7 @@ from asgiref.sync import sync_to_async from taiga.base.utils.datetime import aware_utcnow from taiga.commons.storage import repositories +from tests.samples.test_storage.models import SampleAttachment from tests.utils import factories as f pytestmark = pytest.mark.django_db(transaction=True) @@ -87,6 +88,27 @@ async def test_delete_storaged_object(): assert not storage.exists(file_path) +async def test_delete_storaged_object_that_has_been_used(): + storaged_object = await f.create_storaged_object() + file_path = storaged_object.file.path + storage = storaged_object.file.storage + + await SampleAttachment.objects.acreate(storaged_object=storaged_object) + + assert len(await repositories.list_storaged_objects()) == 1 + assert storage.exists(file_path) + + assert not await repositories.delete_storaged_object(storaged_object=storaged_object) + + assert len(await repositories.list_storaged_objects()) == 1 + assert storage.exists(file_path) + + +########################################################## +# mark_storaged_object_as_deleted +########################################################## + + async def test_mark_storaged_object_as_deleted(): storaged_object = await f.create_storaged_object() diff --git a/python/apps/taiga/tests/samples/occ/__init__.py b/python/apps/taiga/tests/samples/test_occ/__init__.py similarity index 100% rename from python/apps/taiga/tests/samples/occ/__init__.py rename to python/apps/taiga/tests/samples/test_occ/__init__.py diff --git a/python/apps/taiga/tests/samples/occ/models.py b/python/apps/taiga/tests/samples/test_occ/models.py similarity index 100% rename from python/apps/taiga/tests/samples/occ/models.py rename to python/apps/taiga/tests/samples/test_occ/models.py diff --git a/python/apps/taiga/tests/samples/test_storage/__init__.py b/python/apps/taiga/tests/samples/test_storage/__init__.py new file mode 100644 index 000000000..87d9a5256 --- /dev/null +++ b/python/apps/taiga/tests/samples/test_storage/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# Copyright (c) 2023-present Kaleidos INC diff --git a/python/apps/taiga/tests/samples/test_storage/models.py b/python/apps/taiga/tests/samples/test_storage/models.py new file mode 100644 index 000000000..556bc17bd --- /dev/null +++ b/python/apps/taiga/tests/samples/test_storage/models.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# Copyright (c) 2023-present Kaleidos INC + +from taiga.base.db import models + + +class SampleAttachment(models.Model): + storaged_object = models.ForeignKey( + "storage.StoragedObject", + null=False, + blank=False, + on_delete=models.RESTRICT, + related_name="+", + )