diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 109de82..4933b73 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -117,6 +117,11 @@ jobs: type=ref,event=tag type=semver,pattern={{version}} type=semver,pattern={{raw}} + - name: DockerHub login + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - id: last_commit uses: ./.github/actions/last_commit - name: Build and push @@ -129,13 +134,13 @@ jobs: tags: unicef/hope-country-workspace:test-${{env.BRANCH}} platforms: linux/amd64 target: test - push: true - cache-from: type=registry,ref=unicef/hope-country-workspace:test-${{env.BRANCH}},ref=unicef/hope-country-workspace:${{env.BRANCH}} - cache-to: type=registry,ref=unicef/hope-country-workspace:${{env.BRANCH}}-cache,mode=max,image-manifest=true build-args: | GITHUB_SERVER_URL=${{ github.server_url }} GITHUB_REPOSITORY=${{ github.repository }} SOURCE_COMMIT=${{ steps.last_commit.outputs.last_commit_short_sha }} +# push: true +# cache-from: type=registry,ref=unicef/hope-country-workspace:test-${{env.BRANCH}},ref=unicef/hope-country-workspace:${{env.BRANCH}} +# cache-to: type=registry,ref=unicef/hope-country-workspace:${{env.BRANCH}}-cache,mode=max,image-manifest=true # BUILD_DATE=${{ env.BUILD_DATE }} # labels: "${{ steps.meta.outputs.labels }}\nchecksum=${{ inputs.code_checksum }}\ndistro=${{ inputs.target }}" diff --git a/pyproject.toml b/pyproject.toml index 72d9ee2..55cbf30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ dependencies = [ "django-adminactions>=2.3.0", "django-adminfilters>=2.5.0", "django-celery-beat>=2.6.0", + "django-celery-boost>=0.2.0", "django-celery-results>=2.5.1", "django-constance>=3.1.0", "django-csp", diff --git a/src/country_workspace/config/__init__.py b/src/country_workspace/config/__init__.py index bb46f07..a71007a 100644 --- a/src/country_workspace/config/__init__.py +++ b/src/country_workspace/config/__init__.py @@ -31,7 +31,7 @@ class Group(Enum): list, [], [], - True, + False, "list of emails that will automatically created as superusers", ), "ADMIN_EMAIL": (str, "", "admin", True, "Initial user created at first deploy"), diff --git a/src/country_workspace/config/settings.py b/src/country_workspace/config/settings.py index 918bb6b..b5a9020 100644 --- a/src/country_workspace/config/settings.py +++ b/src/country_workspace/config/settings.py @@ -38,6 +38,7 @@ "jsoneditor", "django_celery_beat", "django_celery_results", + "django_celery_boost", "hope_flex_fields", "hope_smart_import", "hope_smart_export", diff --git a/src/country_workspace/config/urls.py b/src/country_workspace/config/urls.py index 60f4613..8776eb0 100644 --- a/src/country_workspace/config/urls.py +++ b/src/country_workspace/config/urls.py @@ -16,3 +16,7 @@ path(r"__debug__/", include(debug_toolbar.urls)), path(r"", workspace.urls), ] + +admin.site.site_header = "Workspace Admin" +admin.site.site_title = "Workspace Admin Portal" +admin.site.index_title = "Welcome to HOPE Workspace" diff --git a/src/country_workspace/management/commands/upgrade.py b/src/country_workspace/management/commands/upgrade.py index d538c8d..74d09e0 100644 --- a/src/country_workspace/management/commands/upgrade.py +++ b/src/country_workspace/management/commands/upgrade.py @@ -166,6 +166,7 @@ def handle(self, *args: Any, **options: Any) -> None: # noqa ), name=settings.TENANT_HQ, ) + call_command("upgradescripts", ["apply"]) echo("Upgrade completed", style_func=self.style.SUCCESS) except ValidationError as e: self.halt(Exception("\n- ".join(["Wrong argument(s):", *e.messages]))) diff --git a/src/country_workspace/versioning/checks.py b/src/country_workspace/versioning/checks.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/country_workspace/versioning/management/commands/applyversion.py b/src/country_workspace/versioning/management/commands/applyversion.py deleted file mode 100644 index 6f0a2f7..0000000 --- a/src/country_workspace/versioning/management/commands/applyversion.py +++ /dev/null @@ -1,26 +0,0 @@ -from django.core.management.base import BaseCommand, no_translations - -from country_workspace.versioning.management.manager import Manager - - -class Command(BaseCommand): - help = "Creates new version for apps." - - def add_arguments(self, parser): - parser.add_argument("num", nargs="?", help="Specify the version label") - - @no_translations - def handle(self, num, **options): - m = Manager() - if not num: - num = m.max_version - print(f"Available update {m.max_version}") - print(f"Applied update {m.max_applied_version}") - if num == "zero": - m.zero() - else: - num = int(num) - if not num: - num = m.max_applied_version - if num >= m.max_applied_version: - m.forward(num) diff --git a/src/country_workspace/versioning/management/commands/makeversion.py b/src/country_workspace/versioning/management/commands/makeversion.py deleted file mode 100644 index f08573a..0000000 --- a/src/country_workspace/versioning/management/commands/makeversion.py +++ /dev/null @@ -1,48 +0,0 @@ -import re -from pathlib import Path - -from django.core.management.base import BaseCommand, no_translations -from django.utils.timezone import now - -import country_workspace - -VERSION_TEMPLATE = """# Generated by HCW %(version)s on %(timestamp)s - -class Version: - operations = [] -""" - -regex = re.compile(r"(\d+).*") - - -def get_version(filename): - if m := regex.match(filename): - return int(m.group(1)) - return None - - -ts = now().strftime("%Y_%m_%d_%H%M%S") - - -class Command(BaseCommand): - help = "Creates new version" - - def add_arguments(self, parser): - parser.add_argument( - "label", - nargs="?", - help="Specify the version label", - ) - - @no_translations - def handle(self, label, **options): - folder = Path(__file__).parent.parent.parent / "versions" - last_ver = 0 - for filename in folder.iterdir(): - if ver := get_version(filename.name): - last_ver = max(last_ver, ver) - new_ver = last_ver + 1 - dest_file = folder / "{:>04}_{}.py".format(new_ver, label or ts) - with dest_file.open("w") as f: - f.write(VERSION_TEMPLATE % {"timestamp": ts, "version": country_workspace.VERSION}) - print(f"Created version {dest_file.name}") diff --git a/src/country_workspace/versioning/management/commands/showversion.py b/src/country_workspace/versioning/management/commands/showversion.py deleted file mode 100644 index 3819934..0000000 --- a/src/country_workspace/versioning/management/commands/showversion.py +++ /dev/null @@ -1,39 +0,0 @@ -import re -from pathlib import Path - -from django.core.management.base import BaseCommand, no_translations - -from country_workspace.versioning.models import Version - -regex = re.compile(r"(\d+).*") - - -def get_version(filename): - if m := regex.match(filename): - return int(m.group(1)) - return None - - -class Command(BaseCommand): - help = "Creates new version for apps." - - def add_arguments(self, parser): - parser.add_argument( - "num", - nargs="?", - help="Specify the version label", - ) - - @no_translations - def handle(self, *app_labels, **options): - folder = Path(__file__).parent.parent.parent / "versions" - existing = {} - applied = list(Version.objects.order_by("name").values_list("name", flat=True)) - for filename in sorted(folder.iterdir()): - if ver := get_version(filename.name): - existing[ver] = filename.name - for filename in existing.values(): - if filename in applied: - print(f"[x] {filename}") - else: - print(f"[ ] {filename}") diff --git a/src/country_workspace/versioning/management/commands/upgradescripts.py b/src/country_workspace/versioning/management/commands/upgradescripts.py new file mode 100644 index 0000000..a3a7b44 --- /dev/null +++ b/src/country_workspace/versioning/management/commands/upgradescripts.py @@ -0,0 +1,80 @@ +from pathlib import Path + +from django.core.management.base import BaseCommand, CommandError, no_translations +from django.utils.timezone import now + +import country_workspace +from country_workspace.versioning.management.manager import Manager + +VERSION_TEMPLATE = """# Generated by HCW %(version)s on %(today)s +from packaging.version import Version + +_script_for_version = Version("%(version)s") + + +def forward(): + pass + + +def backward(): + pass + + +class Scripts: + operations = [(forward, backward)] +""" + + +class Command(BaseCommand): + help = "Creates new version for apps." + + def add_arguments(self, parser): + subparsers = parser.add_subparsers(dest="command") + subparsers.add_parser("list", help="Show applied/available scripts") + + parser_create = subparsers.add_parser("create", help="Create a new empty script") + parser_create.add_argument("--label", help="name of the new script", metavar="LABEL") + + parser_apply = subparsers.add_parser("apply", help="Apply scripts") + parser_apply.add_argument( + "num", + nargs="?", + help='Scripts will be applied to the one after that selected. Use the name "zero" to unapply all scripts.', + ) + parser_apply.add_argument( + "--fake", action="store_true", help="Mark script as run without actually running them." + ) + + @no_translations + def handle(self, command, label=None, num=None, fake=None, zero=False, **options): + m = Manager() + if command == "list": + for entry in m.existing: + stem = Path(entry).stem + x = "x" if m.is_processed(entry) else " " + self.stdout.write("[{x}] {name}".format(x=x, name=stem)) + elif command == "create": + new_ver = m.max_version + 1 + ts = now().strftime("%Y_%m_%d_%H%M%S") + today = now().strftime("%Y %m %d %H:%M:%S") + + dest_file = m.folder / "{:>04}_{}.py".format(new_ver, label or ts) + with dest_file.open("w") as f: + f.write(VERSION_TEMPLATE % {"timestamp": ts, "version": country_workspace.VERSION, "today": today}) + if options["verbosity"] > 0: + self.stdout.write(f"Created script {dest_file.name}") + + elif command == "apply": + if num == "zero": + m.zero(out=self.stdout) + else: + if not num: + num = m.max_version + else: + num = int(num) + if num >= m.max_applied_version: + m.forward(num, fake, self.stdout) + else: + m.backward(num, self.stdout) + else: + raise CommandError("Invalid command") diff --git a/src/country_workspace/versioning/management/manager.py b/src/country_workspace/versioning/management/manager.py index 6249e80..d51527e 100644 --- a/src/country_workspace/versioning/management/manager.py +++ b/src/country_workspace/versioning/management/manager.py @@ -1,5 +1,6 @@ import importlib.util import re +import sys from pathlib import Path from typing import Callable @@ -7,7 +8,6 @@ from country_workspace.versioning.models import Version regex = re.compile(r"(\d+).*") -default_folder = Path(__file__).parent.parent / "scripts" def get_version(filename): @@ -19,11 +19,11 @@ def get_version(filename): def get_funcs(filename: Path, direction: str = "forward"): if not filename.exists(): # pragma: no cover raise FileNotFoundError(filename) - spec = importlib.util.spec_from_file_location("version", filename.absolute()) + spec = importlib.util.spec_from_file_location(filename.stem, filename.absolute()) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) funcs = [] - for op in module.Version.operations: + for op in module.Scripts.operations: if isinstance(op, (list, tuple)): if direction == "forward": funcs.append(op[0]) @@ -39,8 +39,10 @@ def get_funcs(filename: Path, direction: str = "forward"): class Manager: - def __init__(self, folder: Path = default_folder): - self.folder = folder + default_folder = Path(__file__).parent.parent / "scripts" + + def __init__(self, folder: Path = ""): + self.folder = folder or self.default_folder self.existing = [] self.applied = list(Version.objects.order_by("name").values_list("name", flat=True)) self.max_version = 0 @@ -53,34 +55,44 @@ def __init__(self, folder: Path = default_folder): self.existing.append(filename) self.max_version = max(self.max_version, v) - def zero(self): - self.backward(0) + def is_processed(self, entry): + return Path(entry).name in self.applied + + def zero(self, out=sys.stdout): + self.backward(0, out=out) - def forward(self, to_num) -> list[tuple[Path, list[Callable[[None], None]]]]: - print("Upgrading...") + def forward( + self, to_num=None, fake: bool = False, out=sys.stdout + ) -> list[tuple[Path, list[Callable[[None], None]]]]: + out.write("Upgrading...\n") + if not to_num: + to_num = self.max_version processed = [] for entry in self.existing: if get_version(entry.stem) > to_num: break if entry.name not in self.applied: funcs = get_funcs(entry, direction="forward") - print(f" Applying {entry.stem}") - for func in funcs: - func() + if fake: + out.write(f" Applying {entry.stem} (fake)\n") + else: + out.write(f" Applying {entry.stem}\n") + for func in funcs: + func() Version.objects.create(name=entry.name, version=VERSION) processed.append((entry, funcs)) self.applied = list(Version.objects.order_by("name").values_list("name", flat=True)) return processed - def backward(self, to_num) -> list[tuple[Path, list[Callable[[None], None]]]]: - print("Downgrading...") + def backward(self, to_num, out=sys.stdout) -> list[tuple[Path, list[Callable[[None], None]]]]: + out.write("Downgrading...\n") processed = [] for entry in reversed(self.applied): if get_version(entry) <= to_num: break file_path = Path(self.folder) / entry funcs = get_funcs(file_path, direction="backward") - print(f" Discharging {file_path.stem}") + out.write(f" Discharging {file_path.stem}\n") for func in funcs: func() Version.objects.get(name=file_path.name).delete() diff --git a/src/country_workspace/versioning/scripts/0001_initial.py b/src/country_workspace/versioning/scripts/0001_initial.py index 7255fe6..453cdc2 100644 --- a/src/country_workspace/versioning/scripts/0001_initial.py +++ b/src/country_workspace/versioning/scripts/0001_initial.py @@ -17,7 +17,7 @@ def removes_hope_core_fieldset(): DataChecker.objects.filter(name=HOUSEHOLD_CHECKER_NAME).delete() -class Version: +class Scripts: operations = [ (create_hope_field_definitions, removes_hope_field_definitions), (create_hope_core_fieldset, removes_hope_core_fieldset), diff --git a/src/country_workspace/versioning/scripts/0002_synclog.py b/src/country_workspace/versioning/scripts/0002_synclog.py index 88eedf0..8b4ca56 100644 --- a/src/country_workspace/versioning/scripts/0002_synclog.py +++ b/src/country_workspace/versioning/scripts/0002_synclog.py @@ -1,5 +1,5 @@ from country_workspace.versioning.synclog import create_default_synclog, removes_default_synclog -class Version: +class Scripts: operations = [(create_default_synclog, removes_default_synclog)] diff --git a/tests/.coveragerc b/tests/.coveragerc index 9768563..455db04 100644 --- a/tests/.coveragerc +++ b/tests/.coveragerc @@ -7,6 +7,7 @@ omit = */migrations/*, src/country_workspace/management/commands/demo.py src/country_workspace/security/backends.py + src/country_workspace/versioning/scripts/* */wsgi.py, @@ -23,12 +24,14 @@ exclude_lines = raise AssertionError raise NotImplementedError except ImportError + raise CommandError # Don't complain if non-runnable code isn't run: #if 0: if __name__ == .__main__.: if TYPE_CHECKING: if settings.DEBUG: + #fail_under = 95 ignore_errors = True diff --git a/tests/test_commands.py b/tests/test_commands.py index 3618cd5..4a08bac 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -24,12 +24,19 @@ @pytest.fixture() def environment() -> dict[str, str]: return { - "CACHE_URL": "test", + "ADMIN_EMAIL": "test@example.com", + "ADMIN_PASSWORD": "test", + "ALLOWED_HOSTS": "test", + "CSRF_COOKIE_SECURE": "test", + "CSRF_TRUSTED_ORIGINS": "test", + "HOPE_API_TOKEN": "test", "CELERY_BROKER_URL": "", + "CACHE_URL": "", "DATABASE_URL": "", "SECRET_KEY": "", "MEDIA_ROOT": "/tmp/media", "STATIC_ROOT": "/tmp/static", + "DJANGO_SETTINGS_MODULE": "country_workspace.config.settings", "SECURE_SSL_REDIRECT": "1", "SESSION_COOKIE_SECURE": "1", } diff --git a/tests/versioning/conftest.py b/tests/versioning/conftest.py new file mode 100644 index 0000000..683f488 --- /dev/null +++ b/tests/versioning/conftest.py @@ -0,0 +1,50 @@ +from pathlib import Path + +import pytest + +from country_workspace import VERSION + + +@pytest.fixture() +def test_scripts_folder(): + return Path(__file__).parent / "scripts" + + +@pytest.fixture() +def scripts(test_scripts_folder): + return [ + (test_scripts_folder / "0001_test.py"), + (test_scripts_folder / "0002_test.py"), + (test_scripts_folder / "0003_test.py"), + ] + + +@pytest.fixture() +def version1(test_scripts_folder): + from testutils.factories import VersionFactory + + f = test_scripts_folder / "0001_test.py" + return VersionFactory(name=f.name, version=VERSION) + + +@pytest.fixture() +def version2(test_scripts_folder): + from testutils.factories import VersionFactory + + f = test_scripts_folder / "0002_test.py" + return VersionFactory(name=f.name, version=VERSION) + + +@pytest.fixture() +def version3(test_scripts_folder): + from testutils.factories import VersionFactory + + f = test_scripts_folder / "0003_test.py" + return VersionFactory(name=f.name, version=VERSION) + + +@pytest.fixture() +def manager(test_scripts_folder): + from country_workspace.versioning.management.manager import Manager + + return Manager(test_scripts_folder) diff --git a/tests/versioning/scripts/0001_test.py b/tests/versioning/scripts/0001_test.py index ed003ff..876f19a 100644 --- a/tests/versioning/scripts/0001_test.py +++ b/tests/versioning/scripts/0001_test.py @@ -6,7 +6,7 @@ def back(): pass -class Version: +class Scripts: operations = [ (forward, back), ] diff --git a/tests/versioning/scripts/0002_test.py b/tests/versioning/scripts/0002_test.py index ed003ff..876f19a 100644 --- a/tests/versioning/scripts/0002_test.py +++ b/tests/versioning/scripts/0002_test.py @@ -6,7 +6,7 @@ def back(): pass -class Version: +class Scripts: operations = [ (forward, back), ] diff --git a/tests/versioning/scripts/0003_test.py b/tests/versioning/scripts/0003_test.py index 8df2b4e..24c036f 100644 --- a/tests/versioning/scripts/0003_test.py +++ b/tests/versioning/scripts/0003_test.py @@ -6,7 +6,7 @@ def back(): pass -class Version: +class Scripts: operations = [ forward, ] diff --git a/tests/versioning/test_command.py b/tests/versioning/test_command.py new file mode 100644 index 0000000..3edb16d --- /dev/null +++ b/tests/versioning/test_command.py @@ -0,0 +1,68 @@ +from io import StringIO +from pathlib import Path +from unittest import mock + +from django.core.management import call_command + +import pytest + + +def test_command_list(version1, test_scripts_folder): + out = StringIO() + with mock.patch("country_workspace.versioning.management.manager.Manager.default_folder", test_scripts_folder): + call_command("upgradescripts", ["list"], stdout=out) + ret = str(out.getvalue()) + assert ret == "[x] 0001_test\n[ ] 0002_test\n[ ] 0003_test\n" + + +@pytest.mark.parametrize("verbosity", [0, 1]) +def test_command_apply_all(version1, test_scripts_folder, verbosity): + out = StringIO() + with mock.patch("country_workspace.versioning.management.manager.Manager.default_folder", test_scripts_folder): + call_command("upgradescripts", ["-v", verbosity, "apply"], stdout=out) + ret = str(out.getvalue()) + assert ret == "Upgrading...\n Applying 0002_test\n Applying 0003_test\n" + + +def test_command_apply(version1, test_scripts_folder): + out = StringIO() + with mock.patch("country_workspace.versioning.management.manager.Manager.default_folder", test_scripts_folder): + call_command("upgradescripts", ["apply", "2"], stdout=out) + ret = str(out.getvalue()) + assert ret == "Upgrading...\n Applying 0002_test\n" + + +def test_command_apply_fake(version1, test_scripts_folder): + out = StringIO() + with mock.patch("country_workspace.versioning.management.manager.Manager.default_folder", test_scripts_folder): + call_command("upgradescripts", ["apply", "2", "--fake"], stdout=out) + ret = str(out.getvalue()) + assert ret == "Upgrading...\n Applying 0002_test (fake)\n" + + +def test_command_backward(version1, version2, test_scripts_folder): + out = StringIO() + with mock.patch("country_workspace.versioning.management.manager.Manager.default_folder", test_scripts_folder): + call_command("upgradescripts", ["apply", "1"], stdout=out) + ret = str(out.getvalue()) + assert ret == "Downgrading...\n Discharging 0002_test\n" + + +def test_command_zero(version1, test_scripts_folder): + out = StringIO() + with mock.patch("country_workspace.versioning.management.manager.Manager.default_folder", test_scripts_folder): + call_command("upgradescripts", ["apply", "zero"], stdout=out) + ret = str(out.getvalue()) + assert ret == "Downgrading...\n Discharging 0001_test\n" + + +def test_command_create(version1, test_scripts_folder): + out = StringIO() + try: + with mock.patch("country_workspace.versioning.management.manager.Manager.default_folder", test_scripts_folder): + call_command("upgradescripts", ["create", "--label", "sample"], stdout=out) + ret = str(out.getvalue()) + assert ret == "Created script 0004_sample.py\n" + finally: + if (Path(test_scripts_folder) / "0004_sample.py").exists(): + (Path(test_scripts_folder) / "0004_sample.py").unlink() diff --git a/tests/versioning/test_scripts.py b/tests/versioning/test_scripts.py new file mode 100644 index 0000000..7403a9d --- /dev/null +++ b/tests/versioning/test_scripts.py @@ -0,0 +1,9 @@ +from country_workspace.versioning.management.manager import Manager + + +def test_forward_backward(): + m = Manager() + m.forward() + m.forward() + m.backward(1) + m.zero() diff --git a/tests/versioning/test_ver.py b/tests/versioning/test_ver.py index 2bda4b9..f262545 100644 --- a/tests/versioning/test_ver.py +++ b/tests/versioning/test_ver.py @@ -1,91 +1,92 @@ from pathlib import Path - -import pytest +from typing import TYPE_CHECKING from country_workspace import VERSION -from country_workspace.versioning.management.manager import Manager - -sample_folder = Path(__file__).parent / "scripts" -FILES = [ - (sample_folder / "0001_test.py"), - (sample_folder / "0002_test.py"), - (sample_folder / "0003_test.py"), -] - - -@pytest.fixture() -def version1(): - from testutils.factories import VersionFactory - - f = sample_folder / "0001_test.py" - return VersionFactory(name=f.name, version=VERSION) - - -@pytest.fixture() -def version2(): - from testutils.factories import VersionFactory - - f = sample_folder / "0002_test.py" - return VersionFactory(name=f.name, version=VERSION) - - -@pytest.fixture() -def version3(): - from testutils.factories import VersionFactory - - f = sample_folder / "0003_test.py" - return VersionFactory(name=f.name, version=VERSION) - - -@pytest.fixture() -def manager(): - return Manager(sample_folder) - -def test_manager_1(manager: Manager): +if TYPE_CHECKING: + from country_workspace.versioning.management.manager import Manager + +# sample_folder = Path(__file__).parent / "scripts" +# scripts = [ +# (sample_folder / "0001_test.py"), +# (sample_folder / "0002_test.py"), +# (sample_folder / "0003_test.py"), +# ] + +# +# @pytest.fixture() +# def version1(): +# from testutils.factories import VersionFactory +# +# f = sample_folder / "0001_test.py" +# return VersionFactory(name=f.name, version=VERSION) +# +# +# @pytest.fixture() +# def version2(): +# from testutils.factories import VersionFactory +# +# f = sample_folder / "0002_test.py" +# return VersionFactory(name=f.name, version=VERSION) +# +# +# @pytest.fixture() +# def version3(): +# from testutils.factories import VersionFactory +# +# f = sample_folder / "0003_test.py" +# return VersionFactory(name=f.name, version=VERSION) +# +# +# @pytest.fixture() +# def manager(): +# return Manager(sample_folder) + + +def test_manager_1(manager: "Manager", scripts: list[Path]) -> None: assert manager.max_version == 3 assert manager.max_applied_version == 0 assert manager.applied == [] - assert manager.existing == FILES + assert manager.existing == scripts -def test_manager_2(version1, manager: Manager): +def test_manager_2(version1, manager: "Manager", scripts: list[Path]): assert manager.max_version == 3 assert manager.max_applied_version == 1 assert manager.applied == [version1.name] - assert manager.existing == FILES + assert manager.existing == scripts -def test_manager_forward(manager: Manager): +def test_manager_forward(manager: "Manager", scripts: list[Path]): from country_workspace.versioning.models import Version assert manager.applied == [] manager.forward(1) - assert manager.applied == [FILES[0].name] + assert manager.applied == [scripts[0].name] - assert Version.objects.filter(name=FILES[0].name, version=VERSION).exists() - assert not Version.objects.filter(name=FILES[1].name, version=VERSION).exists() + assert Version.objects.filter(name=scripts[0].name, version=VERSION).exists() + assert not Version.objects.filter(name=scripts[1].name, version=VERSION).exists() manager.forward(2) - assert Version.objects.filter(name=FILES[0].name, version=VERSION).exists() - assert Version.objects.filter(name=FILES[1].name, version=VERSION).exists() + assert Version.objects.filter(name=scripts[0].name, version=VERSION).exists() + assert Version.objects.filter(name=scripts[1].name, version=VERSION).exists() manager.forward(manager.max_version) -def test_manager_backward(version1, version2, version3, manager: Manager): +def test_manager_backward(version1, version2, version3, manager: "Manager", scripts: list[Path]): from country_workspace.versioning.models import Version assert manager.applied == [version1.name, version2.name, version3.name] ret = manager.backward(2) - assert [r[0] for r in ret] == [FILES[2].name] + assert [r[0] for r in ret] == [scripts[2].name] - assert manager.applied == [FILES[0].name, FILES[1].name] - assert list(Version.objects.values_list("name", flat=True)) == [FILES[0].name, FILES[1].name] + assert manager.applied == [scripts[0].name, scripts[1].name] + assert list(Version.objects.values_list("name", flat=True)) == [scripts[0].name, scripts[1].name] -def test_manager_zero(version1, manager: Manager): +def test_manager_zero(version1, manager: "Manager", scripts: list[Path]): from country_workspace.versioning.models import Version manager.zero() assert manager.applied == [] - assert not Version.objects.filter(name=FILES[0].name, version=VERSION).exists() - assert not Version.objects.filter(name=FILES[1].name, version=VERSION).exists() + assert not Version.objects.filter(name=scripts[0].name, version=VERSION).exists() + assert not Version.objects.filter(name=scripts[1].name, version=VERSION).exists() diff --git a/uv.lock b/uv.lock index 46909e7..d32487d 100644 --- a/uv.lock +++ b/uv.lock @@ -490,6 +490,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/29/f8/f5a25472222b19258c3a53ce71c4efd171a12ab3c988bb3026dec0522a64/django_celery_beat-2.7.0-py3-none-any.whl", hash = "sha256:851c680d8fbf608ca5fecd5836622beea89fa017bc2b3f94a5b8c648c32d84b1", size = 94097 }, ] +[[package]] +name = "django-celery-boost" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "celery" }, + { name = "django-admin-extra-buttons" }, + { name = "django-concurrency" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/624ac88995441acd310bc1c268706e0cc8d851c8fe11364967304893c0cd/django_celery_boost-0.2.0.tar.gz", hash = "sha256:7399a46d4c361abbe96acb1f00f53bc96eaec4f1928f3f83fd0de85c0984ebda", size = 10977 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/38/4952bee610a9fae92a431898ac3cebd869e7e4a37add3a44fc441e27d2c7/django_celery_boost-0.2.0-py3-none-any.whl", hash = "sha256:a229a71b2a5bd1be1ba338005a64af81a3e1c159f5e553b8a211a47aa11ead1d", size = 7770 }, +] + [[package]] name = "django-celery-results" version = "2.5.1" @@ -503,6 +517,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/05/2e/aa82857354d5227922c2ae6e83e5214537d8f108184bfeea6704d0b045d8/django_celery_results-2.5.1-py3-none-any.whl", hash = "sha256:0da4cd5ecc049333e4524a23fcfc3460dfae91aa0a60f1fae4b6b2889c254e01", size = 36293 }, ] +[[package]] +name = "django-concurrency" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/a6/c3d75900363ec8b723eb0b062a3c5f38db736a71d57d7198e6da8c28b8f4/django_concurrency-2.6.0.tar.gz", hash = "sha256:f5c4133d6497da91a8c2640f443e216160a38398b6c8506fe551770691fe2a1d", size = 61512 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/84/ac13c62f90ceb83da90d89f46da9eef490aac7db4f47b9de8be3c6d6331d/django_concurrency-2.6.0-py3-none-any.whl", hash = "sha256:eed723272c5450f102c0e7d03fbb1ab1253bcf853073d7ba356591eab88ecc9b", size = 27162 }, +] + [[package]] name = "django-constance" version = "4.1.2" @@ -891,6 +914,7 @@ dependencies = [ { name = "django-adminactions" }, { name = "django-adminfilters" }, { name = "django-celery-beat" }, + { name = "django-celery-boost" }, { name = "django-celery-results" }, { name = "django-constance" }, { name = "django-csp" }, @@ -955,6 +979,7 @@ requires-dist = [ { name = "django-adminactions", specifier = ">=2.3.0" }, { name = "django-adminfilters", specifier = ">=2.5.0" }, { name = "django-celery-beat", specifier = ">=2.6.0" }, + { name = "django-celery-boost", specifier = ">=0.2.0" }, { name = "django-celery-results", specifier = ">=2.5.1" }, { name = "django-constance", specifier = ">=3.1.0" }, { name = "django-csp" },