diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index f8a01492d..e7f8aebd3 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -9,6 +9,10 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Export runner UID/GID + run: | + echo "USER_UID=$(id -u)" >> $GITHUB_ENV + echo "USER_GID=$(id -g)" >> $GITHUB_ENV - name: Check formatting run: docker compose run web make check-formatting - name: Check translations diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 46ed3ce0e..04449fd8b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,8 +9,12 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Export runner UID/GID + run: | + echo "USER_UID=$(id -u)" >> $GITHUB_ENV + echo "USER_GID=$(id -g)" >> $GITHUB_ENV - name: Run Test - run: docker compose run web make test + run: docker compose run -v .:/app --rm web make test - name: Upload coverage uses: actions/upload-artifact@master with: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc14afa39..a3a91f4c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -111,7 +111,7 @@ The class name is the convention for the word in texts, followed by how to write ### Django Shell ```sh -docker compose exec web poetry run python manage.py shell_plus +docker compose exec web python manage.py shell_plus ``` ### LDAP @@ -129,7 +129,7 @@ docker compose up -d openldap Then, run the tests. ```sh -docker compose run --rm web poetry run pytest +docker compose run --rm web pytest ``` The `--rm` option will delete the temporary containers created to run the tests. Omit it if you want to keep the @@ -145,8 +145,8 @@ secret To generate the translation files, first use "makemessages" and specify the language you want to generate: ```sh -docker compose exec -w /app/tapir web poetry run python ../manage.py makemessages --no-wrap -l de -docker compose run --rm -w /app web poetry run python manage.py makemessages --no-wrap -l de -d djangojs +docker compose exec -w /app/tapir web python ../manage.py makemessages --no-wrap -l de +docker compose run --rm -w /app web python manage.py makemessages --no-wrap -l de -d djangojs ``` Update tapir/translations/locale/de/LC_MESSAGES/django.po with your translations. @@ -180,7 +180,7 @@ All changes must be done in the docker container. Since our development environm docker container, you must run djangos makemigrations on docker. You can do this with this command: ```sh -docker compose exec web poetry run python manage.py makemigrations +docker compose exec web python manage.py makemigrations ``` Please check the migration script. It might contain unwished changes. There seems to be a bug in ldpa migrations. @@ -190,7 +190,7 @@ Please check the migration script. It might contain unwished changes. There seem Last step is to update the database. this is done with this command: ```sh -docker compose exec web poetry run python manage.py migrate +docker compose exec web python manage.py migrate ``` Please check, if applications runs (again). diff --git a/Makefile b/Makefile index 9098886bd..0d5bd82b4 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,18 @@ lint: - poetry run black . + black . check-formatting: - poetry run black --check . + black --check . test: - poetry run pytest --cov-report xml:coverage.xml --cov=tapir --cov-config=pyproject.toml + pytest --cov-report xml:coverage.xml --cov=tapir --cov-config=pyproject.toml check-translations: + ls -la /app cp tapir/translations/locale/de/LC_MESSAGES/django.po tapir/translations/locale/de/LC_MESSAGES/django-old.po - cd tapir && poetry run python ../manage.py makemessages --no-wrap -l de + cd tapir && python ../manage.py makemessages --no-wrap -l de git diff --ignore-matching-lines=POT-Creation-Date --exit-code --no-index tapir/translations/locale/de/LC_MESSAGES/django.po tapir/translations/locale/de/LC_MESSAGES/django-old.po rm tapir/translations/locale/de/LC_MESSAGES/django-old.po check-migrations: - poetry run python manage.py makemigrations --check + python manage.py makemigrations --check diff --git a/django.Dockerfile b/django.Dockerfile index bd00b30eb..8854a6954 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -1,18 +1,84 @@ -FROM python:3.13 -ENV PYTHONUNBUFFERED=1 +# syntax=docker/dockerfile:1 +ARG USER_UID +ARG USER_GID + + +FROM python:3.13-slim AS build +ARG DEV=false +ENV POETRY_VERSION=2.2.1 \ + PYTHONUNBUFFERED=1 \ + # prevents python creating .pyc files + PYTHONDONTWRITEBYTECODE=1 \ + PIP_NO_CACHE_DIR=off \ + PIP_DISABLE_PIP_VERSION_CHECK=on \ + PIP_DEFAULT_TIMEOUT=100 \ + # make poetry install to this location + POETRY_HOME="/opt/poetry" \ + # make poetry create the virtual environment in the project's root + # it gets named `.venv` + POETRY_VIRTUALENVS_IN_PROJECT=true \ + # do not ask any interactive question + POETRY_NO_INTERACTION=1 \ + # this is where our requirements + virtual environment will live + PYSETUP_PATH="/opt/pysetup" \ + VENV_PATH="/opt/pysetup/.venv" + +ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH" + + +RUN apt-get update \ + && apt-get install --no-install-recommends -y \ + build-essential curl \ + # psycopg2 dependencies + libpq-dev \ + # Translations dependencies + gettext \ + # LDAP dependencies + libldap2-dev libsasl2-dev \ + # cleaning up unused files + && rm -rf /var/lib/apt/lists/* + +RUN curl -sS https://install.python-poetry.org | POETRY_HOME=$POETRY_HOME python3 - && \ + ln -s $POETRY_HOME/bin/poetry /usr/local/bin/poetry + +WORKDIR $PYSETUP_PATH + +COPY poetry.lock pyproject.toml ./ + +RUN if [ "$DEV" = "true" ]; then \ + poetry install --with dev --no-root; \ + else \ + poetry install --without dev --no-root; \ + fi + + +FROM python:3.13-slim AS runtime +ARG USERNAME=nonroot ARG ARG_VERSION +ARG USER_UID +ARG USER_GID ENV TAPIR_VERSION=${ARG_VERSION} +ENV VENV_PATH="/opt/pysetup/.venv" \ + PATH="/opt/pysetup/.venv/bin:$PATH" + +RUN apt-get update \ + && apt-get install --no-install-recommends -y libpq-dev gettext libldap2-dev libsasl2-dev \ + libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0 \ + make git \ + && rm -rf /var/lib/apt/lists/* + +RUN groupadd --gid $USER_GID $USERNAME && \ + useradd --uid $USER_UID --gid $USER_GID -m $USERNAME + WORKDIR /app -COPY . /app - -RUN apt-get update -y \ - && apt-get --no-install-recommends install -y \ - gettext \ - libldap2-dev \ - libsasl2-dev \ - postgresql-client \ - postgresql-client-common \ - && rm -rf /var/lib/apt/lists/* \ - && pip install poetry \ - && poetry install \ - && poetry run python manage.py compilemessages + +COPY --from=build /opt/pysetup/.venv /opt/pysetup/.venv +COPY --from=build /opt/pysetup/ ./ + +COPY tapir /app/tapir +COPY manage.py /app/manage.py +COPY Makefile /app/Makefile + +RUN python manage.py compilemessages + +USER $USERNAME diff --git a/docker-compose.yml b/docker-compose.yml index f2ea2e841..4a1672d91 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,14 +16,17 @@ services: web: build: context: . - args: - ARG_VERSION: "" dockerfile: ./django.Dockerfile - command: bash -c "poetry install && - poetry run python manage.py compilemessages --ignore \".venv\" && - poetry run python manage.py runserver_plus 0.0.0.0:80" + args: + DEV: true + ARG_VERSION: "" + USER_UID: ${USER_UID:-1000} + USER_GID: ${USER_GID:-1000} + command: bash -c "python manage.py runserver_plus 0.0.0.0:80" volumes: - - .:/app + - ./tapir:/app/tapir + - ./manage.py:/app/manage.py + - ./Makefile:/app/Makefile environment: VIRTUAL_HOST: localhost DEBUG: 1 @@ -69,8 +72,7 @@ services: celery: extends: service: web - command: bash -c "poetry install && - poetry run celery -A tapir worker -l info" + command: bash -c "celery -A tapir worker -l info" depends_on: - redis @@ -78,8 +80,7 @@ services: extends: service: web # --schedule to avoid polluting the app directory - command: bash -c "poetry install && - poetry run celery -A tapir beat -l info --schedule /tmp/celerybeat-schedule" + command: bash -c "celery -A tapir beat -l info --schedule /tmp/celerybeat-schedule" depends_on: - redis diff --git a/poetry.lock b/poetry.lock index 2189bd528..3dd6c26f9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -48,7 +48,7 @@ version = "3.0.1" description = "Annotate AST trees with source code positions" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] files = [ {file = "asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a"}, {file = "asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7"}, @@ -64,7 +64,7 @@ version = "25.4.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, @@ -747,7 +747,6 @@ files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\""} [[package]] name = "contourpy" @@ -1442,7 +1441,7 @@ version = "3.16.1" description = "Web APIs for Django, made easy." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main"] files = [ {file = "djangorestframework-3.16.1-py3-none-any.whl", hash = "sha256:33a59f47fb9c85ede792cbf88bde71893bcda0667bc573f784649521f1102cec"}, {file = "djangorestframework-3.16.1.tar.gz", hash = "sha256:166809528b1aced0a17dc66c24492af18049f2c9420dbd0be29422029cfc3ff7"}, @@ -1500,7 +1499,7 @@ version = "0.29.0" description = "Sane and flexible OpenAPI 3 schema generation for Django REST framework" optional = false python-versions = ">=3.7" -groups = ["dev"] +groups = ["main"] files = [ {file = "drf_spectacular-0.29.0-py3-none-any.whl", hash = "sha256:d1ee7c9535d89848affb4427347f7c4a22c5d22530b8842ef133d7b72e19b41a"}, {file = "drf_spectacular-0.29.0.tar.gz", hash = "sha256:0a069339ea390ce7f14a75e8b5af4a0860a46e833fd4af027411a3e94fc1a0cc"}, @@ -1539,7 +1538,7 @@ version = "2.2.1" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] files = [ {file = "executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017"}, {file = "executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4"}, @@ -1764,7 +1763,7 @@ version = "2.1.10" description = "Never use print() to debug again: inspect variables, expressions, and program execution with a single, simple function call." optional = false python-versions = "*" -groups = ["dev"] +groups = ["main"] files = [ {file = "icecream-2.1.10-py3-none-any.whl", hash = "sha256:6b0ae3e899de12954cd26d8611dcff86518ff19f40deef333427da2ccf4036b2"}, {file = "icecream-2.1.10.tar.gz", hash = "sha256:15900126ba7dbe1f83819583cbe5ff79a2943224600878d89307e4633b32e528"}, @@ -1812,7 +1811,7 @@ version = "0.5.1" description = "A port of Ruby on Rails inflector to Python" optional = false python-versions = ">=3.5" -groups = ["dev"] +groups = ["main"] files = [ {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"}, {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, @@ -1876,7 +1875,7 @@ version = "4.26.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.10" -groups = ["dev"] +groups = ["main"] files = [ {file = "jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce"}, {file = "jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326"}, @@ -1898,7 +1897,7 @@ version = "2025.9.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main"] files = [ {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, @@ -2930,7 +2929,7 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -3247,7 +3246,7 @@ version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -3350,7 +3349,7 @@ version = "0.37.0" description = "JSON Referencing + Python" optional = false python-versions = ">=3.10" -groups = ["dev"] +groups = ["main"] files = [ {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, @@ -3512,7 +3511,7 @@ version = "0.30.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.10" -groups = ["dev"] +groups = ["main"] files = [ {file = "rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288"}, {file = "rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00"}, @@ -3939,7 +3938,7 @@ version = "4.2.0" description = "Implementation of RFC 6570 URI Templates" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main"] files = [ {file = "uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686"}, {file = "uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e"}, @@ -4227,4 +4226,4 @@ test = ["pytest"] [metadata] lock-version = "2.1" python-versions = ">=3.13,<4.0" -content-hash = "5dfccddc8366d0e2b4bd921394eae630571fd3a471c3c2c763f74f2746cd468b" +content-hash = "e1e8f05fdff98644022422558c488e63655223ce2c0a76032416b99ea4d3c9db" diff --git a/pyproject.toml b/pyproject.toml index 08f75f039..66bcf6d2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,8 @@ dependencies = [ "django-cors-headers (>=4.6.0,<5.0.0)", "distinctipy[extras] (==1.3.4)", "slack-sdk (>=3.35.0,<4.0.0)", + "drf-spectacular (>=0.29.0,<0.30.0)", + "icecream (>=2.1.8,<3.0.0)", "holidays (>=0.93,<0.94)", ] @@ -51,12 +53,10 @@ pytest-django = "^4.12.0" pytest-sugar = "^1.1.1" selenium = "^4.41.0" factory-boy = "^3.3.3" -icecream = "^2.1.8" djlint = "^1.36.4" -pypdf = "^6.9.1" -django-upgrade = "^1.30.0" -drf-spectacular = "^0.29.0" -django-silk = "^5.5.0" +pypdf = "^6.1.1" +django-upgrade = "^1.29.0" +django-silk = "^5.4.3" [build-system] requires = ["poetry-core>=2.0.0,<3.0.0"] diff --git a/scripts/generate_api_schema.sh b/scripts/generate_api_schema.sh index 8c8c07dda..a96e2e820 100755 --- a/scripts/generate_api_schema.sh +++ b/scripts/generate_api_schema.sh @@ -1,3 +1,3 @@ #!/bin/sh -docker compose run --rm web poetry run python ./manage.py spectacular --file schema.yml \ No newline at end of file +docker compose run --rm web python ./manage.py spectacular --file schema.yml \ No newline at end of file diff --git a/scripts/update_translation_files.sh b/scripts/update_translation_files.sh index ac764c7b9..36cd3031c 100755 --- a/scripts/update_translation_files.sh +++ b/scripts/update_translation_files.sh @@ -1,8 +1,8 @@ #!/bin/sh # translations from Python -docker compose run --rm -w /app/tapir web poetry run python ../manage.py makemessages --no-wrap -l de +docker compose run --rm -w /app/tapir web python ../manage.py makemessages --no-wrap -l de # translations from Javascript docker compose run --rm vite npm run build -docker compose run --rm -w /app web poetry run python manage.py makemessages --no-wrap -l de -d djangojs \ No newline at end of file +docker compose run --rm -w /app web python manage.py makemessages --no-wrap -l de -d djangojs \ No newline at end of file diff --git a/tapir/accounts/management/commands/update_purchase_tracking_list.py b/tapir/accounts/management/commands/update_purchase_tracking_list.py index 5070b9a71..5f9d94164 100644 --- a/tapir/accounts/management/commands/update_purchase_tracking_list.py +++ b/tapir/accounts/management/commands/update_purchase_tracking_list.py @@ -1,4 +1,5 @@ import csv +import tempfile from django.core.management import BaseCommand @@ -17,15 +18,17 @@ class Command(BaseCommand): "Updates the file containing the list of users that allowed purchase tracking and synchronizes it with the " "BioOffice server." ) - FILE_NAME = "members-current.csv" def handle(self, *args, **options): - self.write_users_to_file() - send_file_to_storage_server(self.FILE_NAME, "u326634-sub4") + with tempfile.TemporaryFile( + mode="w", + ) as temp_file: + self.write_users_to_file(filename=temp_file.name) + send_file_to_storage_server(temp_file.name, "u326634-sub4") @classmethod - def write_users_to_file(cls): - with open(cls.FILE_NAME, "w", newline="") as csvfile: + def write_users_to_file(cls, filename): + with open(filename, "w", newline="") as csvfile: writer = csv.writer(csvfile, delimiter=";", quoting=csv.QUOTE_MINIMAL) writer.writerow( [ diff --git a/tapir/accounts/tests/test_purchase_tracking_setting.py b/tapir/accounts/tests/test_purchase_tracking_setting.py index 615808d7c..5cec68b7d 100644 --- a/tapir/accounts/tests/test_purchase_tracking_setting.py +++ b/tapir/accounts/tests/test_purchase_tracking_setting.py @@ -1,11 +1,9 @@ from pathlib import Path +from unittest.mock import patch from django.core.management import call_command from django.urls import reverse -from tapir.accounts.management.commands.update_purchase_tracking_list import ( - Command as UpdatePurchaseTrackingListCommand, -) from tapir.accounts.models import UpdateTapirUserLogEntry from tapir.accounts.tests.factories.factories import TapirUserFactory from tapir.utils.tests_utils import ( @@ -14,10 +12,9 @@ class TestPurchaseTrackingSetting(TapirFactoryTestBase): - EXPORT_FILE = Path(UpdatePurchaseTrackingListCommand.FILE_NAME) def test_normal_user_can_update_their_own_setting(self): - tapir_user = TapirUserFactory(allows_purchase_tracking=False) + tapir_user = TapirUserFactory.create(allows_purchase_tracking=False) self.login_as_user(tapir_user) response = self.client.get( @@ -38,7 +35,7 @@ def test_normal_user_can_update_their_own_setting(self): self.assertEqual(tapir_user, log_entry.actor) def test_other_user_cant_update_tracking_setting(self): - tapir_user = TapirUserFactory(allows_purchase_tracking=False) + tapir_user = TapirUserFactory.create(allows_purchase_tracking=False) self.login_as_member_office_user() response = self.client.get( @@ -52,7 +49,7 @@ def test_other_user_cant_update_tracking_setting(self): self.assertEqual(False, tapir_user.allows_purchase_tracking) def test_no_log_entry_created_if_setting_value_not_changed(self): - tapir_user = TapirUserFactory(allows_purchase_tracking=False) + tapir_user = TapirUserFactory.create(allows_purchase_tracking=False) self.login_as_user(tapir_user) self.client.get( @@ -63,28 +60,31 @@ def test_no_log_entry_created_if_setting_value_not_changed(self): self.assertEqual(0, UpdateTapirUserLogEntry.objects.count()) - def test_updatePurchaseTrackingList_userHasTrackingEnabled_userIsInExportFile(self): - tapir_user = TapirUserFactory(allows_purchase_tracking=True) - call_command("update_purchase_tracking_list") - - list_content = self.EXPORT_FILE.read_text() - self.assertIn(tapir_user.last_name, list_content) - self.EXPORT_FILE.unlink() - - def test_updatePurchaseTrackingList_userHasTrackingDisabled_userIsNotInExportFile( - self, + @patch("tempfile.TemporaryFile") + def test_updatePurchaseTrackingList_userswithTrackingEnabledOrDisabled_includedOrExcludedInExportFile( + self, mock_temp_file ): - tapir_user = TapirUserFactory(allows_purchase_tracking=False) + mock_temp_file_instance = mock_temp_file.return_value.__enter__.return_value + mock_temp_file_instance.name = "/tmp/test_temp_file.csv" + + tapir_user_enabled = TapirUserFactory.create(allows_purchase_tracking=True) + tapir_user_disabled = TapirUserFactory.create(allows_purchase_tracking=False) call_command("update_purchase_tracking_list") - list_content = self.EXPORT_FILE.read_text() - self.assertNotIn(tapir_user.last_name, list_content) - self.EXPORT_FILE.unlink() + list_content = Path(mock_temp_file_instance.name).read_text() + self.assertIn(tapir_user_enabled.last_name, list_content) + self.assertNotIn(tapir_user_disabled.last_name, list_content) + + @patch("tempfile.TemporaryFile") def test_updatePurchaseTrackingList_userHasTrackingEnabledButNoShareOwner_userIsNotInExportFile( - self, + self, mock_temp_file ): - tapir_user = TapirUserFactory(allows_purchase_tracking=True, share_owner=None) + mock_temp_file_instance = mock_temp_file.return_value.__enter__.return_value + mock_temp_file_instance.name = "/tmp/test_temp_file.csv" + + tapir_user = TapirUserFactory.create( + allows_purchase_tracking=True, share_owner=None + ) call_command("update_purchase_tracking_list") - list_content = self.EXPORT_FILE.read_text() + list_content = Path(mock_temp_file_instance.name).read_text() self.assertNotIn(tapir_user.last_name, list_content) - self.EXPORT_FILE.unlink() diff --git a/tapir/utils/shortcuts.py b/tapir/utils/shortcuts.py index e03ede274..568755ca3 100644 --- a/tapir/utils/shortcuts.py +++ b/tapir/utils/shortcuts.py @@ -91,7 +91,7 @@ def send_file_to_storage_server(filename: str, username: str): setup_ssh_for_biooffice_storage() os.system( - f"scp -o 'NumberOfPasswordPrompts=0' -o 'UserKnownHostsFile=~/.ssh/biooffice_known_hosts' -i ~/.ssh/biooffice_id_rsa -P 23 {filename} {username}@u326634.your-storagebox.de:./" + f"scp -o 'NumberOfPasswordPrompts=0' -o 'UserKnownHostsFile=~/.ssh/biooffice_known_hosts' -i ~/.ssh/biooffice_id_rsa -P 23 {filename} {username}@u326634.your-storagebox.de:./members-current.csv" )