From 5220e333fa304b90d5e61f47f586483fed4b1c81 Mon Sep 17 00:00:00 2001 From: Sal Tijerina Date: Wed, 2 Sep 2026 18:53:31 -0500 Subject: [PATCH 1/2] enable ruff checks; resolve ruff errors --- .github/workflows/main.yml | 8 +- .pre-commit-config.yaml | 6 +- server/conftest.py | 10 +- server/portal/apps/_custom/drp/models.py | 121 ++++++++++-------- server/portal/apps/_custom/drp/schema.py | 8 +- server/portal/apps/_custom/drp/urls.py | 1 + server/portal/apps/_custom/drp/views.py | 12 +- server/portal/apps/accounts/api/urls.py | 1 + .../portal/apps/accounts/api/views/systems.py | 14 +- server/portal/apps/accounts/integrations.py | 12 +- .../portal/apps/accounts/managers/abstract.py | 1 + .../portal/apps/accounts/managers/accounts.py | 2 + .../portal/apps/accounts/managers/ssh_keys.py | 16 ++- .../apps/accounts/managers/unit_test.py | 11 +- .../apps/accounts/migrations/0001_initial.py | 2 +- .../migrations/0002_hostkeys_keys_sshkeys.py | 2 +- server/portal/apps/accounts/models.py | 39 +++--- server/portal/apps/accounts/unit_test.py | 2 +- server/portal/apps/accounts/urls.py | 5 +- server/portal/apps/accounts/views.py | 14 +- server/portal/apps/auth/api/urls.py | 2 +- server/portal/apps/auth/api/views.py | 4 +- .../portal/apps/auth/api/views_unit_test.py | 6 +- server/portal/apps/auth/backends.py | 11 +- server/portal/apps/auth/middleware.py | 2 + .../apps/auth/migrations/0001_initial.py | 2 +- .../migrations/0002_auto_20220920_2239.py | 2 +- server/portal/apps/auth/models.py | 8 +- server/portal/apps/auth/models_unit_test.py | 4 +- server/portal/apps/auth/unit_test.py | 14 +- server/portal/apps/auth/urls.py | 1 + server/portal/apps/auth/views.py | 19 +-- .../handlers/googledrive_handlers.py | 5 +- .../googledrive_handlers_unit_test.py | 1 + .../apps/datafiles/handlers/tapis_handlers.py | 5 +- .../portal/apps/datafiles/models_unit_test.py | 3 +- server/portal/apps/datafiles/urls.py | 8 +- server/portal/apps/datafiles/utils.py | 16 ++- server/portal/apps/datafiles/views.py | 33 ++--- .../portal/apps/datafiles/views_unit_test.py | 12 +- server/portal/apps/forms/urls.py | 1 + server/portal/apps/forms/views.py | 3 +- .../googledrive_integration/integrations.py | 9 +- .../migrations/0001_initial.py | 4 +- .../apps/googledrive_integration/models.py | 17 ++- .../apps/googledrive_integration/tasks.py | 3 +- .../apps/googledrive_integration/unit_test.py | 9 +- .../apps/googledrive_integration/urls.py | 3 +- .../apps/googledrive_integration/views.py | 29 +++-- server/portal/apps/jupyter_mounts/api/urls.py | 1 + .../portal/apps/jupyter_mounts/api/views.py | 24 ++-- .../jupyter_mounts/api/views_unit_test.py | 3 +- server/portal/apps/licenses/admin.py | 1 + .../apps/licenses/migrations/0001_initial.py | 2 +- .../0002_alter_matlablicense_user.py | 2 +- server/portal/apps/licenses/models.py | 5 +- server/portal/apps/news/api/views.py | 4 +- server/portal/apps/news/views.py | 4 +- server/portal/apps/notifications/consumers.py | 2 +- .../apps/notifications/consumers_unit_test.py | 3 +- .../notifications/migrations/0001_initial.py | 1 + .../migrations/0002_auto_20200218_2115.py | 2 +- server/portal/apps/notifications/models.py | 15 ++- server/portal/apps/notifications/unit_test.py | 13 +- server/portal/apps/notifications/urls.py | 2 +- server/portal/apps/notifications/views.py | 6 +- server/portal/apps/onboarding/api/urls.py | 2 +- server/portal/apps/onboarding/api/views.py | 34 ++--- .../apps/onboarding/api/views_unit_test.py | 34 ++--- server/portal/apps/onboarding/conftest.py | 1 + server/portal/apps/onboarding/execute.py | 26 ++-- .../apps/onboarding/execute_unit_test.py | 17 +-- .../onboarding/migrations/0001_initial.py | 4 +- ...001_squashed_0002_alter_setupevent_data.py | 2 +- server/portal/apps/onboarding/models.py | 13 +- .../apps/onboarding/models_unit_test.py | 4 +- .../portal/apps/onboarding/steps/abstract.py | 10 +- .../onboarding/steps/abstract_unit_test.py | 5 +- server/portal/apps/onboarding/steps/access.py | 8 +- .../apps/onboarding/steps/access_unit_test.py | 12 +- .../apps/onboarding/steps/allocation.py | 15 ++- .../onboarding/steps/allocation_unit_test.py | 8 +- server/portal/apps/onboarding/steps/mfa.py | 16 ++- .../apps/onboarding/steps/mfa_unit_test.py | 3 +- .../onboarding/steps/project_membership.py | 23 ++-- .../steps/project_membership_unit_test.py | 12 +- .../apps/onboarding/steps/system_access.py | 8 +- .../steps/system_access_unit_test.py | 8 +- .../apps/onboarding/steps/system_access_v3.py | 8 +- .../apps/onboarding/steps/test_steps.py | 19 +-- server/portal/apps/portal_messages/admin.py | 1 + .../apps/portal_messages/intro_unit_test.py | 3 +- .../migrations/0001_initial.py | 4 +- ...02_custommessages_custommessagetemplate.py | 2 +- .../0006_migrate_intro_messages_cpu.py | 1 - server/portal/apps/portal_messages/models.py | 5 +- server/portal/apps/portal_messages/urls.py | 2 +- server/portal/apps/portal_messages/views.py | 9 +- server/portal/apps/projects/admin.py | 1 + server/portal/apps/projects/conftest.py | 1 + server/portal/apps/projects/exceptions.py | 4 +- .../management/commands/migrate-projects.py | 17 ++- .../commands/migrate-projects_unit_test.py | 6 +- .../management/commands/projects_id.py | 17 +-- .../commands/projects_id_unit_test.py | 3 +- server/portal/apps/projects/managers/base.py | 29 ++--- .../apps/projects/managers/unit_test.py | 4 +- .../apps/projects/migrations/0001_initial.py | 2 +- ...abstractprojectmetadata_co_pis_and_more.py | 2 +- ...a_squashed_0008_delete_projectsmetadata.py | 3 +- server/portal/apps/projects/models/base.py | 25 ++-- .../portal/apps/projects/models/metadata.py | 3 +- .../apps/projects/models/project_metadata.py | 10 +- .../portal/apps/projects/models/unit_test.py | 8 +- server/portal/apps/projects/models/utils.py | 5 +- .../projects/schema_models/base_metadata.py | 42 +++--- .../apps/projects/schema_models/schema.py | 2 +- server/portal/apps/projects/serializers.py | 6 +- server/portal/apps/projects/tasks.py | 12 +- server/portal/apps/projects/urls.py | 3 +- server/portal/apps/projects/utils.py | 6 +- server/portal/apps/projects/views.py | 69 +++++----- .../portal/apps/projects/views_unit_test.py | 20 +-- .../datacite_operations.py | 6 +- .../workspace_operations/graph_operations.py | 16 ++- .../project_meta_operations.py | 15 ++- .../project_publish_operations.py | 29 +++-- .../shared_workspace_migration.py | 17 ++- .../shared_workspace_operations.py | 15 ++- server/portal/apps/public_data/urls.py | 4 +- server/portal/apps/public_data/views.py | 10 +- server/portal/apps/publications/models.py | 4 +- server/portal/apps/publications/urls.py | 3 +- server/portal/apps/publications/views.py | 27 ++-- .../apps/request_access/api/unit_test.py | 3 +- server/portal/apps/request_access/api/urls.py | 1 + .../portal/apps/request_access/api/views.py | 8 +- server/portal/apps/request_access/urls.py | 1 + server/portal/apps/request_access/views.py | 8 +- .../management/commands/reindex-files.py | 10 +- .../search/management/commands/unit_test.py | 5 +- server/portal/apps/search/tasks.py | 14 +- server/portal/apps/signals/receivers.py | 16 ++- .../portal/apps/site_search/api/unit_test.py | 5 +- server/portal/apps/site_search/api/urls.py | 1 + server/portal/apps/site_search/api/views.py | 12 +- server/portal/apps/site_search/urls.py | 1 + server/portal/apps/site_search/views.py | 6 +- .../portal/apps/system_monitor/unit_test.py | 3 +- server/portal/apps/system_monitor/urls.py | 1 + server/portal/apps/system_monitor/views.py | 16 +-- server/portal/apps/tickets/api/unit_test.py | 15 ++- server/portal/apps/tickets/api/urls.py | 1 + server/portal/apps/tickets/api/views.py | 19 +-- server/portal/apps/tickets/rtUtil.py | 3 +- server/portal/apps/tickets/unit_test.py | 6 +- server/portal/apps/tickets/urls.py | 1 + server/portal/apps/tickets/utils.py | 23 ++-- server/portal/apps/tickets/views.py | 6 +- server/portal/apps/users/tasks.py | 4 +- server/portal/apps/users/unit_test.py | 29 +++-- server/portal/apps/users/urls.py | 13 +- server/portal/apps/users/utils.py | 34 ++--- server/portal/apps/users/views.py | 46 ++++--- server/portal/apps/webhooks/callback.py | 2 +- server/portal/apps/webhooks/fields.py | 1 - .../apps/webhooks/migrations/0001_initial.py | 4 +- ...d_0003_alter_externalcall_callback_data.py | 3 +- .../migrations/0002_auto_20221221_2114.py | 1 + .../0004_alter_externalcall_time.py | 2 +- server/portal/apps/webhooks/models.py | 2 +- server/portal/apps/webhooks/unit_test.py | 8 +- server/portal/apps/webhooks/urls.py | 2 +- server/portal/apps/webhooks/utils.py | 20 +-- .../portal/apps/webhooks/utils_unit_test.py | 8 +- server/portal/apps/webhooks/views.py | 37 +++--- .../portal/apps/webhooks/views_unit_test.py | 7 +- server/portal/apps/workbench/api/unit_test.py | 1 + server/portal/apps/workbench/api/urls.py | 1 + server/portal/apps/workbench/urls.py | 1 + server/portal/apps/workbench/views.py | 8 +- server/portal/apps/workspace/admin.py | 3 +- .../workspace/api/handlers/tapis_handlers.py | 1 - server/portal/apps/workspace/api/lookups.py | 5 +- server/portal/apps/workspace/api/unit_test.py | 7 +- server/portal/apps/workspace/api/urls.py | 2 +- server/portal/apps/workspace/api/utils.py | 7 +- .../apps/workspace/api/utils_unit_test.py | 9 +- server/portal/apps/workspace/api/views.py | 75 ++++++----- .../apps/workspace/api/views_unit_test.py | 12 +- .../management/commands/import-apps.py | 11 +- .../management/commands/import-jobs.py | 9 +- .../management/commands/unit_test.py | 6 +- .../apps/workspace/migrations/0001_initial.py | 3 +- ..._squashed_0006_alter_jobsubmission_data.py | 4 +- .../migrations/0002_auto_20200218_2115.py | 2 +- .../0003_apptraycategory_apptrayentry.py | 2 +- .../migrations/0004_jobsubmission_data.py | 1 + server/portal/apps/workspace/models.py | 8 +- .../portal/apps/workspace/models_unit_test.py | 2 +- server/portal/apps/workspace/unit_test.py | 11 +- server/portal/apps/workspace/urls.py | 1 + server/portal/apps/workspace/views.py | 6 +- server/portal/asgi.py | 5 +- server/portal/celery.py | 3 +- server/portal/exceptions/api.py | 2 +- server/portal/libs/agave/exceptions.py | 3 +- .../portal/libs/agave/models/applications.py | 14 +- .../portal/libs/agave/models/permissions.py | 20 +-- server/portal/libs/agave/operations.py | 43 +++---- .../portal/libs/agave/operations_unit_test.py | 10 +- server/portal/libs/agave/serializers.py | 3 +- server/portal/libs/agave/unit_test.py | 14 +- server/portal/libs/agave/utils.py | 14 +- server/portal/libs/agave/utils_unit_test.py | 3 +- server/portal/libs/elasticsearch/analyzers.py | 2 +- server/portal/libs/elasticsearch/docs/base.py | 12 +- .../libs/elasticsearch/docs/unit_test.py | 6 +- server/portal/libs/elasticsearch/indexes.py | 9 +- server/portal/libs/elasticsearch/unit_test.py | 13 +- server/portal/libs/elasticsearch/utils.py | 15 ++- server/portal/libs/exceptions.py | 2 +- server/portal/libs/files/file_processing.py | 11 +- server/portal/libs/googledrive/files.py | 8 +- server/portal/libs/googledrive/operations.py | 16 +-- .../libs/googledrive/operations_unit_test.py | 5 +- .../libs/transfer/operations_unit_test.py | 5 +- server/portal/middleware.py | 11 +- server/portal/settings/settings.py | 7 +- server/portal/settings/unit_test_settings.py | 1 - server/portal/urls.py | 12 +- server/portal/utils/decorators.py | 3 +- server/portal/utils/encryption.py | 7 +- server/portal/utils/exceptions.py | 4 +- server/portal/utils/fields.py | 1 - server/portal/utils/jwt_auth.py | 13 +- server/portal/utils/translations.py | 11 +- server/portal/utils/unit_test.py | 14 +- server/portal/views/base.py | 12 +- server/portal/views/unit_test.py | 11 +- server/portal/views/views.py | 11 +- server/portal/wsgi.py | 1 + 242 files changed, 1271 insertions(+), 1070 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 401b02367d..21dce0955e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -67,10 +67,10 @@ jobs: cd server poetry install --with dev - # - name: Run Ruff check - # run: | - # cd server - # poetry run ruff check --output-format=github . + - name: Run Ruff check + run: | + cd server + poetry run ruff check --output-format=github . - name: Run Ruff format check run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7128db8a57..c2dc7032bb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,8 +2,8 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.16.5 hooks: - # - id: ruff-check - # args: [--fix] - # files: ^server/ + - id: ruff-check + args: [--fix] + files: ^server/ - id: ruff-format files: ^server/ diff --git a/server/conftest.py b/server/conftest.py index 0e16506e89..c37247ada4 100644 --- a/server/conftest.py +++ b/server/conftest.py @@ -1,12 +1,14 @@ -import pytest -from mock import MagicMock import json import os import tempfile -from portal.apps.auth.models import TapisOAuthToken -from portal.apps.accounts.models import PortalProfile +from unittest.mock import MagicMock + +import pytest from django.conf import settings +from portal.apps.accounts.models import PortalProfile +from portal.apps.auth.models import TapisOAuthToken + @pytest.fixture def mock_tapis_client(mocker): diff --git a/server/portal/apps/_custom/drp/models.py b/server/portal/apps/_custom/drp/models.py index 172366a0fe..db6f9cc716 100644 --- a/server/portal/apps/_custom/drp/models.py +++ b/server/portal/apps/_custom/drp/models.py @@ -1,10 +1,11 @@ -from typing import Optional, Literal -from pydantic import ConfigDict, NonNegativeInt, NonNegativeFloat +from typing import Literal + +from pydantic import ConfigDict, NonNegativeFloat, NonNegativeInt from portal.apps.projects.schema_models.base_metadata import ( + BaseFileMetadata, BaseMetadataModel, BaseProjectMetadata, - BaseFileMetadata, FileObj, ) @@ -20,8 +21,8 @@ class DrpFileMetadata(BaseFileMetadata): extra="forbid", ) - is_advanced_image_file: Optional[bool] = False - image_type: Optional[ + is_advanced_image_file: bool | None = False + image_type: ( Literal[ "8_bit", "16_bit_signed", @@ -38,18 +39,19 @@ class DrpFileMetadata(BaseFileMetadata): "32_bit_abgr", "1_bit_bitmap", ] - ] = None - height: Optional[NonNegativeInt] = None - width: Optional[NonNegativeInt] = None - number_of_images: Optional[NonNegativeInt] = None - offset_to_first_image: Optional[int] = None - gap_between_images: Optional[int] = None - byte_order: Optional[Literal["big_endian", "little_endian"]] = None - use_binary_correction: Optional[bool] = None + | None + ) = None + height: NonNegativeInt | None = None + width: NonNegativeInt | None = None + number_of_images: NonNegativeInt | None = None + offset_to_first_image: int | None = None + gap_between_images: int | None = None + byte_order: Literal["big_endian", "little_endian"] | None = None + use_binary_correction: bool | None = None class DrpProjectMetadata(BaseProjectMetadata): - license: Optional[str] = "ODC-BY 1.0" + license: str | None = "ODC-BY 1.0" class DrpDatasetMetadata(BaseMetadataModel): @@ -60,8 +62,8 @@ class DrpDatasetMetadata(BaseMetadataModel): ) name: str - description: Optional[str] = None - uuid: Optional[str] = None + description: str | None = None + uuid: str | None = None data_type: Literal["sample", "origin_data", "digital_dataset", "analysis_data", "file"] file_objs: list[FileObj] = [] @@ -70,10 +72,18 @@ class DrpSampleMetadata(DrpDatasetMetadata): """Model for DRP Sample Metadata""" porous_media_type: Literal[ - "sandstone", "soil", "carbonate", "granite", "beads", "fibrous_media", "coal", "energy_storage", "other" + "sandstone", + "soil", + "carbonate", + "granite", + "beads", + "fibrous_media", + "coal", + "energy_storage", + "other", ] - porous_media_other_description: Optional[str] = None + porous_media_other_description: str | None = None source: Literal[ "natural", @@ -81,25 +91,25 @@ class DrpSampleMetadata(DrpDatasetMetadata): "artificial", "computer_generated", ] - collection_method: Optional[str] = None - onshore_offshore: Optional[Literal["onshore", "offshore"]] = None - depth: Optional[str] = None - total_vertical_depth: Optional[str] = None - water_depth: Optional[str] = None - geographic_origin: Optional[str] = None - procedure: Optional[str] = None - equipment: Optional[str] = None - algorithm_description: Optional[str] = None - grain_size_min: Optional[NonNegativeFloat] = None - grain_size_max: Optional[NonNegativeFloat] = None - grain_size_avg: Optional[NonNegativeFloat] = None - grain_size_units: Optional[Literal["nanometer", "micrometer", "millimeter", "other"]] = None - porosity: Optional[float] = None - geographical_location: Optional[str] = None - date_of_collection: Optional[str] = None - date_of_creation: Optional[str] = None - identifier: Optional[str] = None - location: Optional[str] = None # TODO_DRP: Remove in new model + collection_method: str | None = None + onshore_offshore: Literal["onshore", "offshore"] | None = None + depth: str | None = None + total_vertical_depth: str | None = None + water_depth: str | None = None + geographic_origin: str | None = None + procedure: str | None = None + equipment: str | None = None + algorithm_description: str | None = None + grain_size_min: NonNegativeFloat | None = None + grain_size_max: NonNegativeFloat | None = None + grain_size_avg: NonNegativeFloat | None = None + grain_size_units: Literal["nanometer", "micrometer", "millimeter", "other"] | None = None + porosity: float | None = None + geographical_location: str | None = None + date_of_collection: str | None = None + date_of_creation: str | None = None + identifier: str | None = None + location: str | None = None # TODO_DRP: Remove in new model class DrpOriginDatasetMetadata(DrpDatasetMetadata): @@ -107,18 +117,18 @@ class DrpOriginDatasetMetadata(DrpDatasetMetadata): is_segmented: Literal["yes", "no"] sample: str - imaging_center: Optional[str] = None - imaging_equipment_and_model: Optional[str] = None - image_format: Optional[str] = None - image_dimensions: Optional[str] = None - image_byte_order: Optional[str] = None - voxel_x: Optional[NonNegativeFloat] = None - voxel_y: Optional[NonNegativeFloat] = None - voxel_z: Optional[NonNegativeFloat] = None - voxel_units: Optional[Literal["nanometer", "micrometer", "millimeter", "other"]] = None - dimensionality: Optional[str] = None - digital_dataset: Optional[str] = None - external_uri: Optional[str] = None # TODO_DRP: Remove in new model + imaging_center: str | None = None + imaging_equipment_and_model: str | None = None + image_format: str | None = None + image_dimensions: str | None = None + image_byte_order: str | None = None + voxel_x: NonNegativeFloat | None = None + voxel_y: NonNegativeFloat | None = None + voxel_z: NonNegativeFloat | None = None + voxel_units: Literal["nanometer", "micrometer", "millimeter", "other"] | None = None + dimensionality: str | None = None + digital_dataset: str | None = None + external_uri: str | None = None # TODO_DRP: Remove in new model class DrpAnalysisDatasetMetadata(DrpDatasetMetadata): @@ -126,10 +136,15 @@ class DrpAnalysisDatasetMetadata(DrpDatasetMetadata): is_segmented: Literal["yes", "no"] dataset_type: Literal[ - "machine_learning", "simulation", "geometric_analysis", "experimental", "characterization", "other" + "machine_learning", + "simulation", + "geometric_analysis", + "experimental", + "characterization", + "other", ] - external_uri: Optional[str] = None + external_uri: str | None = None sample: str # base_origin_data: Optional[str] = None - digital_dataset: Optional[str] = None - digital_dataset_other_information: Optional[str] = None + digital_dataset: str | None = None + digital_dataset_other_information: str | None = None diff --git a/server/portal/apps/_custom/drp/schema.py b/server/portal/apps/_custom/drp/schema.py index 9c35377c8a..b4980e3944 100644 --- a/server/portal/apps/_custom/drp/schema.py +++ b/server/portal/apps/_custom/drp/schema.py @@ -1,13 +1,13 @@ """DRP schema extension.""" -from portal.apps.projects.schema_models import constants from portal.apps._custom.drp.models import ( - DrpProjectMetadata, - DrpSampleMetadata, - DrpOriginDatasetMetadata, DrpAnalysisDatasetMetadata, DrpFileMetadata, + DrpOriginDatasetMetadata, + DrpProjectMetadata, + DrpSampleMetadata, ) +from portal.apps.projects.schema_models import constants SCHEMA_MAPPING = { constants.PROJECT: DrpProjectMetadata, diff --git a/server/portal/apps/_custom/drp/urls.py b/server/portal/apps/_custom/drp/urls.py index 12a5d66559..d874a0182b 100644 --- a/server/portal/apps/_custom/drp/urls.py +++ b/server/portal/apps/_custom/drp/urls.py @@ -4,6 +4,7 @@ """ from django.urls import re_path + from portal.apps._custom.drp.views import DigitalRocksSampleView, GenerateImagesView app_name = "custom" diff --git a/server/portal/apps/_custom/drp/views.py b/server/portal/apps/_custom/drp/views.py index c932c11186..4f09092f90 100644 --- a/server/portal/apps/_custom/drp/views.py +++ b/server/portal/apps/_custom/drp/views.py @@ -1,15 +1,17 @@ import json -from portal.views.base import BaseApiView +import logging + +import networkx as nx from django.conf import settings from django.http import HttpRequest, JsonResponse -from portal.exceptions.api import ApiException + from portal.apps.projects.models.project_metadata import ProjectMetadata from portal.apps.projects.schema_models import constants -import networkx as nx -from portal.apps.projects.workspace_operations.project_meta_operations import patch_file_obj_entity from portal.apps.projects.tasks import process_file from portal.apps.projects.views import get_project_client -import logging +from portal.apps.projects.workspace_operations.project_meta_operations import patch_file_obj_entity +from portal.exceptions.api import ApiException +from portal.views.base import BaseApiView logger = logging.getLogger(__name__) diff --git a/server/portal/apps/accounts/api/urls.py b/server/portal/apps/accounts/api/urls.py index 7ca265b61c..40964edc1b 100644 --- a/server/portal/apps/accounts/api/urls.py +++ b/server/portal/apps/accounts/api/urls.py @@ -4,6 +4,7 @@ """ from django.urls import re_path + from portal.apps.accounts.api.views.systems import SystemKeysView app_name = "portal_accounts_api" diff --git a/server/portal/apps/accounts/api/views/systems.py b/server/portal/apps/accounts/api/views/systems.py index b50f4c8bf8..9d2657dad3 100644 --- a/server/portal/apps/accounts/api/views/systems.py +++ b/server/portal/apps/accounts/api/views/systems.py @@ -3,21 +3,23 @@ :synopsis: Account's systems views """ -import logging import json -from django.http import JsonResponse +import logging + from django.contrib.auth.decorators import login_required +from django.http import JsonResponse from django.utils.decorators import method_decorator -from portal.views.base import BaseApiView -from portal.apps.accounts.managers import accounts as AccountsManager from tapipy.errors import BaseTapyException + +from portal.apps.accounts.managers import accounts as AccountsManager +from portal.apps.datafiles.utils import evaluate_datafiles_storage_system from portal.apps.onboarding.steps.system_access_v3 import ( create_system_credentials_with_keys, - create_system_credentials_with_tms, create_system_credentials_with_password, + create_system_credentials_with_tms, ) from portal.utils.encryption import createKeyPair -from portal.apps.datafiles.utils import evaluate_datafiles_storage_system +from portal.views.base import BaseApiView logger = logging.getLogger(__name__) diff --git a/server/portal/apps/accounts/integrations.py b/server/portal/apps/accounts/integrations.py index 89a63efdd9..e5e05fde0e 100644 --- a/server/portal/apps/accounts/integrations.py +++ b/server/portal/apps/accounts/integrations.py @@ -1,7 +1,7 @@ -from importlib import import_module -from django.conf import settings import logging +from importlib import import_module +from django.conf import settings logger = logging.getLogger(__name__) INTEGRATION_APPS = [s["integration"] for s in settings.PORTAL_DATAFILES_STORAGE_SYSTEMS if "integration" in s] @@ -12,14 +12,10 @@ def get_integrations(request): for app in INTEGRATION_APPS: try: - mod = import_module("{}.integrations".format(app)) + mod = import_module(f"{app}.integrations") app_integrations += mod.provide_integrations(request) except Exception as exc: - logger.warning( - "Call to module.provide_integrations fail for module: {app_name}. {exc}".format( - app_name=app, exc=str(exc) - ) - ) + logger.warning(f"Call to module.provide_integrations fail for module: {app}. {str(exc)}") return app_integrations diff --git a/server/portal/apps/accounts/managers/abstract.py b/server/portal/apps/accounts/managers/abstract.py index afb9326b4c..c3aac6c96b 100644 --- a/server/portal/apps/accounts/managers/abstract.py +++ b/server/portal/apps/accounts/managers/abstract.py @@ -4,6 +4,7 @@ """ from abc import ABCMeta, abstractmethod + from six import add_metaclass diff --git a/server/portal/apps/accounts/managers/accounts.py b/server/portal/apps/accounts/managers/accounts.py index c1f39af6db..7e68a632e1 100644 --- a/server/portal/apps/accounts/managers/accounts.py +++ b/server/portal/apps/accounts/managers/accounts.py @@ -5,8 +5,10 @@ import logging from importlib import import_module + from django.conf import settings from paramiko.ssh_exception import AuthenticationException, ChannelException, SSHException + from portal.apps.accounts.managers.ssh_keys import KeyCannotBeAdded logger = logging.getLogger(__name__) diff --git a/server/portal/apps/accounts/managers/ssh_keys.py b/server/portal/apps/accounts/managers/ssh_keys.py index e2d11d373d..09ed103ed2 100644 --- a/server/portal/apps/accounts/managers/ssh_keys.py +++ b/server/portal/apps/accounts/managers/ssh_keys.py @@ -4,7 +4,9 @@ """ import logging + import paramiko + from portal.apps.accounts.managers.abstract import AbstractKeysManager # pylint: disable=invalid-name @@ -20,13 +22,13 @@ class KeyCannotBeAdded(Exception): """ def __init__(self, msg, output, error_output, *args, **kwargs): - super(KeyCannotBeAdded, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.msg = msg self.output = output self.error_output = error_output def __str__(self): - return "{msg}: {output} \n {error}".format(msg=self.msg, output=self.output, error=self.error_output) + return f"{self.msg}: {self.output} \n {self.error_output}" class KeysManager(AbstractKeysManager): @@ -84,7 +86,7 @@ def _get_pub_key_comment(self, system_id): :return str: comment """ - comment = "{username}@{system_id}".format(username=self.username, system_id=system_id) + comment = f"{self.username}@{system_id}" return comment def _get_add_pub_key_command(self, system_id, public_key): @@ -101,9 +103,9 @@ def _get_add_pub_key_command(self, system_id, public_key): 'if [ ! -f "~/.ssh/authorized_keys" ]; then ' "mkdir -p ~/.ssh/ && touch ~/.ssh/authorized_keys " "&& chmod 0600 ~/.ssh/authorized_keys; fi && " - 'grep -q -F "{string}" ~/.ssh/authorized_keys || ' - 'echo "{string}" >> ~/.ssh/authorized_keys' - ).format(string=string) + f'grep -q -F "{string}" ~/.ssh/authorized_keys || ' + f'echo "{string}" >> ~/.ssh/authorized_keys' + ) return command def add_public_key(self, system_id, hostname, public_key, port=22, transport=None): # pylint: disable=too-many-arguments, arguments-differ @@ -136,7 +138,7 @@ def add_public_key(self, system_id, hostname, public_key, port=22, transport=Non if status == -1: logger.info("No response from the server") elif status == 0: - logger.info("Public key added successfully to {}".format(hostname)) + logger.info(f"Public key added successfully to {hostname}") elif status > 0: error_lines = "" for line in stderr.readlines(): diff --git a/server/portal/apps/accounts/managers/unit_test.py b/server/portal/apps/accounts/managers/unit_test.py index 86c4e58b72..dcd7386b91 100644 --- a/server/portal/apps/accounts/managers/unit_test.py +++ b/server/portal/apps/accounts/managers/unit_test.py @@ -1,9 +1,10 @@ -from mock import MagicMock -from portal.apps.accounts.managers.accounts import add_pub_key_to_resource -from portal.apps.accounts.managers.ssh_keys import KeysManager -from portal.apps.accounts.managers.ssh_keys import KeyCannotBeAdded -from paramiko.ssh_exception import AuthenticationException, ChannelException, SSHException +from unittest.mock import MagicMock + import pytest +from paramiko.ssh_exception import AuthenticationException, ChannelException, SSHException + +from portal.apps.accounts.managers.accounts import add_pub_key_to_resource +from portal.apps.accounts.managers.ssh_keys import KeyCannotBeAdded, KeysManager @pytest.fixture diff --git a/server/portal/apps/accounts/migrations/0001_initial.py b/server/portal/apps/accounts/migrations/0001_initial.py index 4b0674da93..7ebc27a943 100644 --- a/server/portal/apps/accounts/migrations/0001_initial.py +++ b/server/portal/apps/accounts/migrations/0001_initial.py @@ -1,8 +1,8 @@ # Generated by Django 2.2.5 on 2019-10-05 00:11 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/server/portal/apps/accounts/migrations/0002_hostkeys_keys_sshkeys.py b/server/portal/apps/accounts/migrations/0002_hostkeys_keys_sshkeys.py index 4ae01d3240..9ab6874091 100644 --- a/server/portal/apps/accounts/migrations/0002_hostkeys_keys_sshkeys.py +++ b/server/portal/apps/accounts/migrations/0002_hostkeys_keys_sshkeys.py @@ -1,8 +1,8 @@ # Generated by Django 2.2.7 on 2019-11-05 19:50 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/server/portal/apps/accounts/models.py b/server/portal/apps/accounts/models.py index 50a6305265..1a5d76e0e4 100644 --- a/server/portal/apps/accounts/models.py +++ b/server/portal/apps/accounts/models.py @@ -4,13 +4,14 @@ """ import logging + from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.core.mail import send_mail from django.db import models from django.utils.translation import gettext_lazy as _ -from portal.utils import encryption as EncryptionUtil +from portal.utils import encryption as EncryptionUtil # pylint: disable=invalid-name logger = logging.getLogger(__name__) @@ -93,12 +94,12 @@ def save_keys( try: Keys.objects.get(ssh_keys__user=user, system=system_id) except ObjectDoesNotExist: - ssh_keys = super(SSHKeysManager, self).create(user=user) + ssh_keys = super().create(user=user) Keys.objects.create(ssh_keys=ssh_keys, system=system_id, private=priv_key, public=pub_key) return ssh_keys raise ValueError( - """A set of keys for system: '{system}' and username: '{username}' - already exists""".format(system=system_id, username=user.username) + f"""A set of keys for system: '{system_id}' and username: '{user.username}' + already exists""" ) def update_keys(self, user, system_id, priv_key, pub_key): @@ -123,15 +124,15 @@ def update_keys(self, user, system_id, priv_key, pub_key): keys = Keys.objects.get(ssh_keys__user=user, system=system_id) except ObjectDoesNotExist: try: - ssh_keys = super(SSHKeysManager, self).get_queryset().get(user=user) + ssh_keys = super().get_queryset().get(user=user) except ObjectDoesNotExist: - ssh_keys = super(SSHKeysManager, self).create(user=user) + ssh_keys = super().create(user=user) keys = Keys.objects.create(ssh_keys=ssh_keys, system=system_id) keys.public = pub_key keys.private = priv_key keys.save() - return super(SSHKeysManager, self).get_queryset().get(user=user) + return super().get_queryset().get(user=user) def save_hostname_keys( self, @@ -159,12 +160,12 @@ def save_hostname_keys( try: HostKeys.objects.get(ssh_keys__user=user, hostname=hostname) except ObjectDoesNotExist: - ssh_keys = super(SSHKeysManager, self).create(user=user) + ssh_keys = super().create(user=user) HostKeys.objects.create(ssh_keys=ssh_keys, hostname=hostname, private=priv_key, public=pub_key) return ssh_keys raise ValueError( - """A set of keys for hostname: '{hostname}' and username: '{username}' - already exists""".format(hostname=hostname, username=user.username) + f"""A set of keys for hostname: '{hostname}' and username: '{user.username}' + already exists""" ) def update_hostname_keys(self, user, hostname, priv_key, pub_key): @@ -189,15 +190,15 @@ def update_hostname_keys(self, user, hostname, priv_key, pub_key): keys = HostKeys.objects.get(ssh_keys__user=user, hostname=hostname) except ObjectDoesNotExist: try: - ssh_keys = super(SSHKeysManager, self).get_queryset().get(user=user) + ssh_keys = super().get_queryset().get(user=user) except ObjectDoesNotExist: - ssh_keys = super(SSHKeysManager, self).create(user=user) + ssh_keys = super().create(user=user) keys = HostKeys.objects.create(ssh_keys=ssh_keys, hostname=hostname) keys.public = pub_key keys.private = priv_key keys.save() - return super(SSHKeysManager, self).get_queryset().get(user=user) + return super().get_queryset().get(user=user) class SSHKeys(models.Model): @@ -290,7 +291,7 @@ class Keys(models.Model): public = models.TextField() def __init__(self, *args, **kwargs): - super(Keys, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._private = self.private def private_key(self): @@ -306,11 +307,11 @@ def save(self, *args, **kwargs): # pylint: disable=arguments-differ """ if self.private != self._private or self.pk is None: self.private = EncryptionUtil.encrypt(self.private) - super(Keys, self).save(*args, **kwargs) + super().save(*args, **kwargs) self._private = self.private def __str__(self): - return "{username}: {system}".format(username=self.ssh_keys.user.username, system=self.system) + return f"{self.ssh_keys.user.username}: {self.system}" class HostKeys(models.Model): @@ -330,7 +331,7 @@ class Meta: unique_together = (("hostname", "ssh_keys"),) def __init__(self, *args, **kwargs): - super(HostKeys, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._private = self.private def private_key(self): @@ -346,8 +347,8 @@ def save(self, *args, **kwargs): # pylint: disable=arguments-differ """ if self.private != self._private or self.pk is None: self.private = EncryptionUtil.encrypt(self.private) - super(HostKeys, self).save(*args, **kwargs) + super().save(*args, **kwargs) self._private = self.private def __str__(self): - return "{username}: {host}".format(username=self.ssh_keys.user.username, host=self.hostname) + return f"{self.ssh_keys.user.username}: {self.hostname}" diff --git a/server/portal/apps/accounts/unit_test.py b/server/portal/apps/accounts/unit_test.py index ca5a8e186d..05f36f5637 100644 --- a/server/portal/apps/accounts/unit_test.py +++ b/server/portal/apps/accounts/unit_test.py @@ -1,7 +1,7 @@ import pytest from django.conf import settings -from django.http import HttpResponseRedirect from django.contrib.auth import get_user +from django.http import HttpResponseRedirect def test_account_redirect(client): diff --git a/server/portal/apps/accounts/urls.py b/server/portal/apps/accounts/urls.py index 43648eaf4c..e397a6b557 100644 --- a/server/portal/apps/accounts/urls.py +++ b/server/portal/apps/accounts/urls.py @@ -4,10 +4,9 @@ """ from django.urls import re_path -from portal.apps.accounts.views import LogoutView -from portal.apps.accounts.views import accounts -from portal.apps.accounts import views +from portal.apps.accounts import views +from portal.apps.accounts.views import LogoutView, accounts app_name = "portal_accounts" urlpatterns = [ diff --git a/server/portal/apps/accounts/views.py b/server/portal/apps/accounts/views.py index 374bed0859..9f65600124 100644 --- a/server/portal/apps/accounts/views.py +++ b/server/portal/apps/accounts/views.py @@ -3,17 +3,17 @@ """ import logging -import requests -from django.forms.models import model_to_dict +import requests from django.conf import settings -from django.http import JsonResponse, HttpResponseRedirect +from django.contrib.auth import logout from django.contrib.auth.decorators import login_required from django.contrib.auth.views import LogoutView as DjangoLogoutView -from django.contrib.auth import logout from django.core.exceptions import ObjectDoesNotExist -from django.template.loader import render_to_string +from django.forms.models import model_to_dict +from django.http import HttpResponseRedirect, JsonResponse from django.shortcuts import redirect +from django.template.loader import render_to_string from pytas.http import TASClient from portal.apps.accounts import integrations @@ -49,7 +49,7 @@ def get_user_history(username): Get user history from tas """ auth = requests.auth.HTTPBasicAuth(settings.TAS_CLIENT_KEY, settings.TAS_CLIENT_SECRET) - r = requests.get("{0}/v1/users/{1}/history".format(settings.TAS_URL, username), auth=auth) + r = requests.get(f"{settings.TAS_URL}/v1/users/{username}/history", auth=auth) resp = r.json() if resp["status"] == "success": return resp["result"] @@ -76,7 +76,7 @@ def get_profile_data(request): demographics = model_to_dict(django_user.profile) except ObjectDoesNotExist as e: demographics = {} - logger.info("exception e:{} {}".format(type(e), e)) + logger.info(f"exception e:{type(e)} {e}") demographics.update(user_profile) context = { "demographics": demographics, diff --git a/server/portal/apps/auth/api/urls.py b/server/portal/apps/auth/api/urls.py index 8d534897f8..adc5ec8fa1 100644 --- a/server/portal/apps/auth/api/urls.py +++ b/server/portal/apps/auth/api/urls.py @@ -1,6 +1,6 @@ from django.urls import path -from portal.apps.auth.api.views import TapisToken +from portal.apps.auth.api.views import TapisToken app_name = "auth_api" urlpatterns = [ diff --git a/server/portal/apps/auth/api/views.py b/server/portal/apps/auth/api/views.py index 9e949e85da..aee5456a39 100644 --- a/server/portal/apps/auth/api/views.py +++ b/server/portal/apps/auth/api/views.py @@ -2,9 +2,11 @@ import logging from hashlib import sha256 -from django.http import JsonResponse + from django.contrib.auth.decorators import login_required +from django.http import JsonResponse from django.utils.decorators import method_decorator + from portal.views.base import BaseApiView logger = logging.getLogger(__name__) diff --git a/server/portal/apps/auth/api/views_unit_test.py b/server/portal/apps/auth/api/views_unit_test.py index c38673ed6d..21170e95c9 100644 --- a/server/portal/apps/auth/api/views_unit_test.py +++ b/server/portal/apps/auth/api/views_unit_test.py @@ -1,9 +1,11 @@ -import pytest import time from hashlib import sha256 -from portal.apps.auth.models import TapisOAuthToken + +import pytest from tapipy.tapis import Tapis +from portal.apps.auth.models import TapisOAuthToken + @pytest.fixture def authenticated_user_and_setup_complete(authenticated_user, mocker): diff --git a/server/portal/apps/auth/backends.py b/server/portal/apps/auth/backends.py index c6a7c59d6e..488faff935 100644 --- a/server/portal/apps/auth/backends.py +++ b/server/portal/apps/auth/backends.py @@ -1,14 +1,15 @@ """Auth backends""" import logging + import requests from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.backends import ModelBackend + from portal.apps.accounts.models import PortalProfile from portal.apps.users.utils import get_user_data - logger = logging.getLogger(__name__) @@ -19,7 +20,7 @@ def authenticate(self, *args, **kwargs): if "backend" in kwargs and kwargs["backend"] == "tapis": token = kwargs["token"] - logger.info('Attempting login via Tapis with token "%s"' % token[:8].ljust(len(token), "-")) + logger.info('Attempting login via Tapis with token "{}"'.format(token[:8].ljust(len(token), "-"))) response = requests.get( f"{settings.TAPIS_TENANT_BASEURL}/v3/oauth2/userinfo", headers={"X-Tapis-Token": token} @@ -50,10 +51,10 @@ def authenticate(self, *args, **kwargs): user, created = UserModel.objects.update_or_create(username=username, defaults=defaults) if created: - logger.info('Created local user record for "%s" from TAS Profile' % username) + logger.info(f'Created local user record for "{username}" from TAS Profile') PortalProfile.objects.update_or_create(user=user, defaults=profile_defaults) - logger.info('Login successful for user "%s"' % username) + logger.info(f'Login successful for user "{username}"') else: - logger.info("Tapis Authentication failed: %s" % json_result) + logger.info(f"Tapis Authentication failed: {json_result}") return user diff --git a/server/portal/apps/auth/middleware.py b/server/portal/apps/auth/middleware.py index 50dfee561f..d5a8c479e6 100644 --- a/server/portal/apps/auth/middleware.py +++ b/server/portal/apps/auth/middleware.py @@ -3,12 +3,14 @@ """ import logging + from django.contrib.auth import logout from django.core.exceptions import ObjectDoesNotExist from django.db import transaction from django.http import HttpResponseRedirect from django.urls import reverse from tapipy.errors import BaseTapyException + from portal.apps.auth.models import TapisOAuthToken logger = logging.getLogger(__name__) diff --git a/server/portal/apps/auth/migrations/0001_initial.py b/server/portal/apps/auth/migrations/0001_initial.py index cbcbd8c113..3bb66d9cc9 100644 --- a/server/portal/apps/auth/migrations/0001_initial.py +++ b/server/portal/apps/auth/migrations/0001_initial.py @@ -1,8 +1,8 @@ # Generated by Django 2.2.5 on 2019-09-12 07:04 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/server/portal/apps/auth/migrations/0002_auto_20220920_2239.py b/server/portal/apps/auth/migrations/0002_auto_20220920_2239.py index 069a5a5876..5414918d51 100644 --- a/server/portal/apps/auth/migrations/0002_auto_20220920_2239.py +++ b/server/portal/apps/auth/migrations/0002_auto_20220920_2239.py @@ -1,8 +1,8 @@ # Generated by Django 2.2.28 on 2022-09-20 22:39 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/server/portal/apps/auth/models.py b/server/portal/apps/auth/models.py index 7315bba90b..87d152651b 100644 --- a/server/portal/apps/auth/models.py +++ b/server/portal/apps/auth/models.py @@ -3,8 +3,9 @@ import logging import time from urllib.parse import urlparse -from django.db import models + from django.conf import settings +from django.db import models from tapipy.tapis import Tapis logger = logging.getLogger(__name__) @@ -108,4 +109,7 @@ def refresh_tokens(self): def __str__(self): access_token_masked = self.access_token[-5:] refresh_token_masked = self.refresh_token[-5:] - return f"access_token:{access_token_masked} refresh_token:{refresh_token_masked} expires_in:{self.expires_in} created:{self.created}" + return ( + f"access_token:{access_token_masked} refresh_token:{refresh_token_masked} " + f"expires_in:{self.expires_in} created:{self.created}" + ) diff --git a/server/portal/apps/auth/models_unit_test.py b/server/portal/apps/auth/models_unit_test.py index 6d3b81f2fd..2fe0eed6c2 100644 --- a/server/portal/apps/auth/models_unit_test.py +++ b/server/portal/apps/auth/models_unit_test.py @@ -1,6 +1,8 @@ -import pytest import time + +import pytest from django.conf import settings + from portal.apps.auth.models import TapisOAuthToken pytestmark = pytest.mark.django_db diff --git a/server/portal/apps/auth/unit_test.py b/server/portal/apps/auth/unit_test.py index 8b48f0483c..5b6a6e492c 100644 --- a/server/portal/apps/auth/unit_test.py +++ b/server/portal/apps/auth/unit_test.py @@ -1,10 +1,12 @@ -from django.test import TransactionTestCase, override_settings +from unittest.mock import MagicMock, patch + +import pytest from django.contrib.auth import get_user_model -from mock import patch, MagicMock -from portal.apps.auth.backends import TapisOAuthBackend +from django.test import TransactionTestCase, override_settings from requests import Response + +from portal.apps.auth.backends import TapisOAuthBackend from portal.apps.auth.views import launch_setup_checks -import pytest pytestmark = pytest.mark.django_db @@ -20,7 +22,7 @@ def test_launch_setup_checks(mocker, regular_user, settings): class TestTapisOAuthBackend(TransactionTestCase): def setUp(self): - super(TestTapisOAuthBackend, self).setUp() + super().setUp() self.backend = TapisOAuthBackend() self.mock_response = MagicMock(autospec=Response) self.mock_requests_patcher = patch("portal.apps.auth.backends.requests.get", return_value=self.mock_response) @@ -33,7 +35,7 @@ def setUp(self): self.mock_user_data = self.mock_user_data_patcher.start() def tearDown(self): - super(TestTapisOAuthBackend, self).tearDown() + super().tearDown() self.mock_requests_patcher.stop() self.mock_user_data_patcher.stop() diff --git a/server/portal/apps/auth/urls.py b/server/portal/apps/auth/urls.py index 9583a79b64..76b7232329 100644 --- a/server/portal/apps/auth/urls.py +++ b/server/portal/apps/auth/urls.py @@ -4,6 +4,7 @@ """ from django.urls import re_path + from portal.apps.auth import views app_name = "portal_auth" diff --git a/server/portal/apps/auth/views.py b/server/portal/apps/auth/views.py index aa02ca4e12..da3f65600d 100644 --- a/server/portal/apps/auth/views.py +++ b/server/portal/apps/auth/views.py @@ -3,24 +3,27 @@ """ import logging +import math +import secrets import time + import requests -import secrets -import math from django.conf import settings from django.contrib import messages from django.contrib.auth import authenticate, login -from django.urls import reverse -from django.http import HttpResponseRedirect, HttpResponseBadRequest, JsonResponse +from django.contrib.sessions.models import Session +from django.http import HttpResponseBadRequest, HttpResponseRedirect, JsonResponse from django.shortcuts import render +from django.urls import reverse from django.utils import timezone -from django.contrib.sessions.models import Session -from .models import TapisOAuthToken + from portal.apps.onboarding.execute import execute_setup_steps, new_user_setup_check from portal.apps.users.tasks import index_allocations from portal.apps.users.utils import check_user_groups from portal.utils import get_client_ip +from .models import TapisOAuthToken + logger = logging.getLogger(__name__) METRICS = logging.getLogger(f"metrics.{__name__}") @@ -134,7 +137,7 @@ def tapis_oauth_callback(request): state = request.GET.get("state") if request.session["auth_state"] != state: - msg = "OAuth Authorization State mismatch!? auth_state=%s does not match returned state=%s" % ( + msg = "OAuth Authorization State mismatch!? auth_state={} does not match returned state={}".format( request.session["auth_state"], state, ) @@ -196,7 +199,7 @@ def tapis_oauth_callback(request): else: if "error" in request.GET: error = request.GET["error"] - logger.warning("Authorization failed: %s" % error) + logger.warning(f"Authorization failed: {error}") return HttpResponseRedirect(reverse("portal_accounts:logout")) diff --git a/server/portal/apps/datafiles/handlers/googledrive_handlers.py b/server/portal/apps/datafiles/handlers/googledrive_handlers.py index bb4c3858ca..aa72420920 100644 --- a/server/portal/apps/datafiles/handlers/googledrive_handlers.py +++ b/server/portal/apps/datafiles/handlers/googledrive_handlers.py @@ -1,7 +1,8 @@ -from portal.libs.googledrive import operations -from django.core.exceptions import PermissionDenied import logging +from django.core.exceptions import PermissionDenied + +from portal.libs.googledrive import operations logger = logging.getLogger(__name__) diff --git a/server/portal/apps/datafiles/handlers/googledrive_handlers_unit_test.py b/server/portal/apps/datafiles/handlers/googledrive_handlers_unit_test.py index 8016bd7ea0..cede194920 100644 --- a/server/portal/apps/datafiles/handlers/googledrive_handlers_unit_test.py +++ b/server/portal/apps/datafiles/handlers/googledrive_handlers_unit_test.py @@ -1,5 +1,6 @@ import pytest from django.core.exceptions import PermissionDenied + from portal.apps.datafiles.handlers.googledrive_handlers import googledrive_get_handler, googledrive_put_handler diff --git a/server/portal/apps/datafiles/handlers/tapis_handlers.py b/server/portal/apps/datafiles/handlers/tapis_handlers.py index 4d2627cce5..5b8bb427ec 100644 --- a/server/portal/apps/datafiles/handlers/tapis_handlers.py +++ b/server/portal/apps/datafiles/handlers/tapis_handlers.py @@ -1,7 +1,8 @@ -from portal.libs.agave import operations -from django.core.exceptions import PermissionDenied import logging +from django.core.exceptions import PermissionDenied + +from portal.libs.agave import operations logger = logging.getLogger(__name__) diff --git a/server/portal/apps/datafiles/models_unit_test.py b/server/portal/apps/datafiles/models_unit_test.py index 9946cd56c2..5b4a164808 100644 --- a/server/portal/apps/datafiles/models_unit_test.py +++ b/server/portal/apps/datafiles/models_unit_test.py @@ -1,6 +1,7 @@ -from portal.apps.datafiles.models import Link import pytest +from portal.apps.datafiles.models import Link + @pytest.mark.django_db def test_link_uuid(): diff --git a/server/portal/apps/datafiles/urls.py b/server/portal/apps/datafiles/urls.py index 68b3c844c1..330a790cd7 100644 --- a/server/portal/apps/datafiles/urls.py +++ b/server/portal/apps/datafiles/urls.py @@ -1,14 +1,14 @@ from django.urls import path + from portal.apps.datafiles.views import ( - TapisFilesView, GoogleDriveFilesView, - TransferFilesView, LinkView, - SystemListingView, SystemDefinitionView, + SystemListingView, + TapisFilesView, + TransferFilesView, ) - app_name = "users" urlpatterns = [ path("systems/list/", SystemListingView.as_view()), diff --git a/server/portal/apps/datafiles/utils.py b/server/portal/apps/datafiles/utils.py index e29dec10cf..678eaf26ea 100644 --- a/server/portal/apps/datafiles/utils.py +++ b/server/portal/apps/datafiles/utils.py @@ -1,11 +1,13 @@ import logging +from typing import NotRequired, TypedDict + from django.conf import settings -from typing import TypedDict, NotRequired, Optional -from tapipy.errors import InternalServerError, BaseTapyException +from tapipy.errors import BaseTapyException, InternalServerError from tapipy.tapis import TapisResult + +from portal.apps.auth.models import TapisOAuthToken from portal.apps.notifications.models import Notification from portal.apps.users.utils import get_user_data -from portal.apps.auth.models import TapisOAuthToken logger = logging.getLogger(__name__) @@ -19,7 +21,7 @@ class PortalDataFilesSystem(TypedDict): api: str homeDir: str hostEval: NotRequired[str] - icon: NotRequired[Optional[str]] + icon: NotRequired[str | None] siteSearchPriority: NotRequired[int] resourceProvider: NotRequired[str] readOnly: NotRequired[bool] @@ -47,7 +49,8 @@ def evaluate_datafiles_storage_system( Args: tapis (TapisOAuthToken): Tapis OAuth token object system (PortalDataFilesSystem): Storage system definition - default_host_eval (str, optional): Default environment variable name to evaluate for homeDir if hostEval is not provided. + default_host_eval (str, optional): Default environment variable name to + evaluate for homeDir if hostEval is not provided. Returns: PortalDataFilesSystem: Evaluated storage system definition @@ -101,7 +104,8 @@ def evaluate_datafiles_storage_systems(tapis: TapisOAuthToken, systems: list, de Args: tapis (TapisOAuthToken): Tapis OAuth token object systems (list): List of storage system definitions - default_host_eval (str, optional): Default environment variable name to evaluate for homeDir if hostEval is not provided. + default_host_eval (str, optional): Default environment variable name to + evaluate for homeDir if hostEval is not provided. Returns: list: List of evaluated storage system definitions diff --git a/server/portal/apps/datafiles/views.py b/server/portal/apps/datafiles/views.py index 76109f16ed..184b83294b 100644 --- a/server/portal/apps/datafiles/views.py +++ b/server/portal/apps/datafiles/views.py @@ -1,28 +1,31 @@ import json import logging from hashlib import sha256 -from portal.apps.users.utils import get_allocations + +import dateutil.parser from django.conf import settings -from django.http import JsonResponse, HttpResponseForbidden +from django.contrib.auth.decorators import login_required +from django.core.exceptions import PermissionDenied +from django.http import HttpResponseForbidden, JsonResponse +from django.utils.decorators import method_decorator from requests.exceptions import HTTPError from tapipy.errors import InternalServerError, UnauthorizedError -from portal.views.base import BaseApiView -from portal.utils import check_group_membership, get_client_ip -from portal.libs.agave.utils import service_account -from portal.apps.datafiles.handlers.tapis_handlers import tapis_get_handler, tapis_put_handler, tapis_post_handler + from portal.apps.datafiles.handlers.googledrive_handlers import googledrive_get_handler, googledrive_put_handler -from portal.libs.transfer.operations import transfer, transfer_folder -from portal.libs.agave.serializers import BaseTapisResultSerializer -from portal.exceptions.api import ApiException +from portal.apps.datafiles.handlers.tapis_handlers import tapis_get_handler, tapis_post_handler, tapis_put_handler from portal.apps.datafiles.models import Link -from django.contrib.auth.decorators import login_required -from django.core.exceptions import PermissionDenied -from django.utils.decorators import method_decorator +from portal.apps.datafiles.utils import evaluate_datafiles_storage_systems, get_user_storage_systems +from portal.apps.users.utils import get_allocations from portal.apps.workspace.api.utils import push_keys_required_if_not_credentials_ensured -from .utils import notify, NOTIFY_ACTIONS -import dateutil.parser +from portal.exceptions.api import ApiException +from portal.libs.agave.serializers import BaseTapisResultSerializer +from portal.libs.agave.utils import service_account +from portal.libs.transfer.operations import transfer, transfer_folder +from portal.utils import check_group_membership, get_client_ip from portal.utils.decorators import retry -from portal.apps.datafiles.utils import evaluate_datafiles_storage_systems, get_user_storage_systems +from portal.views.base import BaseApiView + +from .utils import NOTIFY_ACTIONS, notify logger = logging.getLogger(__name__) METRICS = logging.getLogger(f"metrics.{__name__}") diff --git a/server/portal/apps/datafiles/views_unit_test.py b/server/portal/apps/datafiles/views_unit_test.py index f2603c414a..de2c65d5ca 100644 --- a/server/portal/apps/datafiles/views_unit_test.py +++ b/server/portal/apps/datafiles/views_unit_test.py @@ -1,11 +1,11 @@ import json import logging import os +from unittest.mock import MagicMock, patch import pytest -from django.contrib.auth.models import Group from django.conf import settings -from mock import MagicMock, patch +from django.contrib.auth.models import Group from tapipy.errors import InternalServerError, UnauthorizedError from tapipy.tapis import TapisResult @@ -365,16 +365,12 @@ def test_tapis_file_view_preview_supported_non_text_files( mock_tapis_client.files.listFiles.return_value = [TapisResult(**f) for f in agave_file_listing_mock] mock_tapis_client.files.createPostIt.return_value = TapisResult(redeemUrl=POSTIT_HREF, expiration=None) response = client.put( - "/api/datafiles/tapis/preview/private/frontera.home.username/test_text.{}/".format(EXTENSION), + f"/api/datafiles/tapis/preview/private/frontera.home.username/test_text.{EXTENSION}/", content_type="application/json", data={"href": "https//tapis.example/href"}, ) - href = ( - POSTIT_HREF - if TYPE != "ms-office" - else "https://view.officeapps.live.com/op/view.aspx?src={}".format(POSTIT_HREF) - ) + href = POSTIT_HREF if TYPE != "ms-office" else f"https://view.officeapps.live.com/op/view.aspx?src={POSTIT_HREF}" assert response.status_code == 200 assert response.json() == {"data": {"href": href, "fileType": TYPE, "content": None, "error": None}} diff --git a/server/portal/apps/forms/urls.py b/server/portal/apps/forms/urls.py index fec0a2b950..0caa626d96 100644 --- a/server/portal/apps/forms/urls.py +++ b/server/portal/apps/forms/urls.py @@ -4,6 +4,7 @@ """ from django.urls import re_path + from portal.apps.forms.views import FormsView app_name = "workbench" diff --git a/server/portal/apps/forms/views.py b/server/portal/apps/forms/views.py index 5c28fb3e37..9249bc7281 100644 --- a/server/portal/apps/forms/views.py +++ b/server/portal/apps/forms/views.py @@ -1,7 +1,8 @@ -from portal.views.base import BaseApiView from django.conf import settings from django.http import JsonResponse +from portal.views.base import BaseApiView + class FormsView(BaseApiView): def get(self, request): diff --git a/server/portal/apps/googledrive_integration/integrations.py b/server/portal/apps/googledrive_integration/integrations.py index 9e125170ba..db647dd20a 100644 --- a/server/portal/apps/googledrive_integration/integrations.py +++ b/server/portal/apps/googledrive_integration/integrations.py @@ -1,8 +1,9 @@ from django.conf import settings -from portal.apps.googledrive_integration.models import GoogleDriveUserToken from django.core.cache import cache from django.urls import reverse +from portal.apps.googledrive_integration.models import GoogleDriveUserToken + def provide_integrations(request): activated = False @@ -12,14 +13,14 @@ def provide_integrations(request): request.user.googledrive_user_token activated = True except GoogleDriveUserToken.DoesNotExist: - if cache.get("{0}_googledrive_error".format(request.session.session_key), False): - error = cache.get("{0}_googledrive_error".format(request.session.session_key)) + if cache.get(f"{request.session.session_key}_googledrive_error", False): + error = cache.get(f"{request.session.session_key}_googledrive_error") pass integration = ( { "label": "Google Drive", - "description": "Access files from your Google Drive account in {}.".format(settings.PORTAL_NAMESPACE), + "description": f"Access files from your Google Drive account in {settings.PORTAL_NAMESPACE}.", "activated": activated, "error": error, "disconnect": reverse("googledrive_integration:disconnect"), diff --git a/server/portal/apps/googledrive_integration/migrations/0001_initial.py b/server/portal/apps/googledrive_integration/migrations/0001_initial.py index 63817bea10..ef69f4d256 100644 --- a/server/portal/apps/googledrive_integration/migrations/0001_initial.py +++ b/server/portal/apps/googledrive_integration/migrations/0001_initial.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.10.5 on 2019-04-18 20:39 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion + import portal.apps.googledrive_integration.models diff --git a/server/portal/apps/googledrive_integration/models.py b/server/portal/apps/googledrive_integration/models.py index dbbe6927d3..590b3dd27a 100644 --- a/server/portal/apps/googledrive_integration/models.py +++ b/server/portal/apps/googledrive_integration/models.py @@ -1,15 +1,14 @@ -from google.auth.transport.requests import Request -from googleapiclient import discovery -from django.conf import settings -from django.db import models -import google.oauth2.credentials -import logging - import base64 +import logging import pickle -from django.utils import encoding +import google.oauth2.credentials import jsonpickle +from django.conf import settings +from django.db import models +from django.utils import encoding +from google.auth.transport.requests import Request +from googleapiclient import discovery logger = logging.getLogger(__name__) @@ -20,7 +19,7 @@ class CredentialsField(models.Field): def __init__(self, *args, **kwargs): if "null" not in kwargs: kwargs["null"] = True - super(CredentialsField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def get_internal_type(self): return "BinaryField" diff --git a/server/portal/apps/googledrive_integration/tasks.py b/server/portal/apps/googledrive_integration/tasks.py index 2f26d65238..f359401006 100644 --- a/server/portal/apps/googledrive_integration/tasks.py +++ b/server/portal/apps/googledrive_integration/tasks.py @@ -1,6 +1,7 @@ -from django.contrib.auth import get_user_model import logging +from django.contrib.auth import get_user_model + logger = logging.getLogger(__name__) diff --git a/server/portal/apps/googledrive_integration/unit_test.py b/server/portal/apps/googledrive_integration/unit_test.py index ff61dbdca2..38dbf6bd23 100644 --- a/server/portal/apps/googledrive_integration/unit_test.py +++ b/server/portal/apps/googledrive_integration/unit_test.py @@ -5,12 +5,13 @@ """ # from django.core.urlresolvers import reverse -from portal.apps.googledrive_integration.models import GoogleDriveUserToken -from mock import MagicMock -from google.oauth2.credentials import Credentials +import logging +from unittest.mock import MagicMock + import pytest +from google.oauth2.credentials import Credentials -import logging +from portal.apps.googledrive_integration.models import GoogleDriveUserToken logger = logging.getLogger("portal.apps.googledrive_integration.views") diff --git a/server/portal/apps/googledrive_integration/urls.py b/server/portal/apps/googledrive_integration/urls.py index 24c66e0400..2cf386d366 100644 --- a/server/portal/apps/googledrive_integration/urls.py +++ b/server/portal/apps/googledrive_integration/urls.py @@ -1,6 +1,7 @@ -from portal.apps.googledrive_integration import views from django.urls import path +from portal.apps.googledrive_integration import views + app_name = "googledrive_integration" urlpatterns = [ path("", views.IndexView.as_view(), name="privacy"), diff --git a/server/portal/apps/googledrive_integration/views.py b/server/portal/apps/googledrive_integration/views.py index 065edcf218..4b4611b8f4 100644 --- a/server/portal/apps/googledrive_integration/views.py +++ b/server/portal/apps/googledrive_integration/views.py @@ -1,15 +1,16 @@ +import logging + import google_auth_oauthlib.flow import requests from django.conf import settings from django.contrib.auth.decorators import login_required -from django.urls import reverse +from django.core.cache import cache from django.http import HttpResponseRedirect +from django.urls import reverse from django.views.decorators.csrf import csrf_exempt from django.views.generic.base import TemplateView -from portal.apps.googledrive_integration.models import GoogleDriveUserToken -from django.core.cache import cache -import logging +from portal.apps.googledrive_integration.models import GoogleDriveUserToken logger = logging.getLogger(__name__) @@ -22,7 +23,7 @@ class IndexView(TemplateView): template_name = "portal/apps/workbench/index.html" def get_context_data(self, **kwargs): - context = super(IndexView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context["setup_complete"] = ( False if self.request.user.is_anonymous else self.request.user.profile.setup_complete ) @@ -30,7 +31,7 @@ def get_context_data(self, **kwargs): return context def dispatch(self, request, *args, **kwargs): - return super(IndexView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_client_config(): @@ -73,12 +74,12 @@ def oauth2_callback(request): googledrive = request.session["googledrive"] else: logger.error("Could not retrieve googledrive from session") - cache.set("{0}_googledrive_error".format(request.session.session_key), error, error_timeout) + cache.set(f"{request.session.session_key}_googledrive_error", error, error_timeout) return HttpResponseRedirect("/accounts/profile") if not (state == googledrive["state"]): logger.error("Could not retrieve state from googledrive stored var") - cache.set("{0}_googledrive_error".format(request.session.session_key), error, error_timeout) + cache.set(f"{request.session.session_key}_googledrive_error", error, error_timeout) return HttpResponseRedirect("/accounts/profile") try: @@ -90,10 +91,10 @@ def oauth2_callback(request): ], state=state, ) - flow.redirect_uri = "https://{}{}".format(request.get_host(), redirect_uri) + flow.redirect_uri = f"https://{request.get_host()}{redirect_uri}" # Use the authorization server's response to fetch the OAuth 2.0 tokens. - authorization_response = "https://{}{}".format(request.get_host(), request.get_full_path()) + authorization_response = f"https://{request.get_host()}{request.get_full_path()}" flow.fetch_token(authorization_response=authorization_response) @@ -115,8 +116,8 @@ def oauth2_callback(request): GoogleDriveUserToken.objects.update_or_create(user=request.user, defaults={"credentials": credentials}) except Exception as e: - logger.exception("Unable to complete Google Drive integration setup: %s" % e) - cache.set("{0}_googledrive_error".format(request.session.session_key), error, error_timeout) + logger.exception(f"Unable to complete Google Drive integration setup: {e}") + cache.set(f"{request.session.session_key}_googledrive_error", error, error_timeout) return HttpResponseRedirect("/accounts/profile") @@ -142,7 +143,7 @@ def disconnect(request): else: logger.error("Disconnect Google Drive; google drive account revoke error.", extra={"user": request.user}) - logger.debug("status code:{}".format(status_code)) + logger.debug(f"status code:{status_code}") return HttpResponseRedirect("/accounts/profile") @@ -151,6 +152,6 @@ def disconnect(request): except Exception as e: logger.error("Disconnect Google Drive; GoogleDriveUserToken delete error.", extra={"user": request.user}) - logger.exception("google drive delete error: {}".format(e)) + logger.exception(f"google drive delete error: {e}") return HttpResponseRedirect("/accounts/profile") diff --git a/server/portal/apps/jupyter_mounts/api/urls.py b/server/portal/apps/jupyter_mounts/api/urls.py index c5a82d7c5d..cf6cbfc0d3 100644 --- a/server/portal/apps/jupyter_mounts/api/urls.py +++ b/server/portal/apps/jupyter_mounts/api/urls.py @@ -1,6 +1,7 @@ """Jupyter Mounts API Urls""" from django.urls import path + from portal.apps.jupyter_mounts.api import views app_name = "jupyter_mounts" diff --git a/server/portal/apps/jupyter_mounts/api/views.py b/server/portal/apps/jupyter_mounts/api/views.py index ca96d56ff5..d6dfd04dee 100644 --- a/server/portal/apps/jupyter_mounts/api/views.py +++ b/server/portal/apps/jupyter_mounts/api/views.py @@ -1,15 +1,15 @@ -from portal.utils.decorators import agave_jwt_login -from django.contrib.auth.decorators import login_required +import logging + from django.conf import settings -from django.utils.decorators import method_decorator +from django.contrib.auth.decorators import login_required from django.http import JsonResponse +from django.utils.decorators import method_decorator + from portal.apps.auth.models import TapisOAuthToken -from portal.views.base import BaseApiView -from portal.apps.projects.workspace_operations.shared_workspace_operations import list_projects, get_workspace_role from portal.apps.datafiles.utils import evaluate_datafiles_storage_systems - -import logging - +from portal.apps.projects.workspace_operations.shared_workspace_operations import get_workspace_role, list_projects +from portal.utils.decorators import agave_jwt_login +from portal.views.base import BaseApiView logger = logging.getLogger(__name__) @@ -41,7 +41,7 @@ def getDatafilesStorageSystems(self, tapis_oauth: TapisOAuthToken) -> list: } ) except Exception: - logger.exception("Could not retrieve system {}".format(system)) + logger.exception(f"Could not retrieve system {system}") return result def getLocalStorageSystems(self, tapis_oauth: TapisOAuthToken) -> list: @@ -63,7 +63,7 @@ def getLocalStorageSystems(self, tapis_oauth: TapisOAuthToken) -> list: } ) except Exception: - logger.exception("Could not retrieve system {}".format(system)) + logger.exception(f"Could not retrieve system {system}") return result def getProjectSystems(self, tapis_oauth: TapisOAuthToken) -> list: @@ -88,9 +88,7 @@ def getProjectSystems(self, tapis_oauth: TapisOAuthToken) -> list: result.append( { "path": project["path"], - "mountPath": "/{namespace}/My Projects/{name}".format( - namespace=settings.PORTAL_NAMESPACE, name=name - ), + "mountPath": f"/{settings.PORTAL_NAMESPACE}/My Projects/{name}", "pems": permissions, } ) diff --git a/server/portal/apps/jupyter_mounts/api/views_unit_test.py b/server/portal/apps/jupyter_mounts/api/views_unit_test.py index 4f73531c28..ba6d8126d3 100644 --- a/server/portal/apps/jupyter_mounts/api/views_unit_test.py +++ b/server/portal/apps/jupyter_mounts/api/views_unit_test.py @@ -1,6 +1,7 @@ -import pytest import json import os + +import pytest from django.conf import settings diff --git a/server/portal/apps/licenses/admin.py b/server/portal/apps/licenses/admin.py index b59fce4450..82c6495fdb 100644 --- a/server/portal/apps/licenses/admin.py +++ b/server/portal/apps/licenses/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from . import models diff --git a/server/portal/apps/licenses/migrations/0001_initial.py b/server/portal/apps/licenses/migrations/0001_initial.py index 662cc19ffc..c6aaa0ee8c 100644 --- a/server/portal/apps/licenses/migrations/0001_initial.py +++ b/server/portal/apps/licenses/migrations/0001_initial.py @@ -1,8 +1,8 @@ # Generated by Django 2.2.7 on 2019-11-05 19:50 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/server/portal/apps/licenses/migrations/0002_alter_matlablicense_user.py b/server/portal/apps/licenses/migrations/0002_alter_matlablicense_user.py index 4f49f09144..ecaf955b21 100644 --- a/server/portal/apps/licenses/migrations/0002_alter_matlablicense_user.py +++ b/server/portal/apps/licenses/migrations/0002_alter_matlablicense_user.py @@ -1,8 +1,8 @@ # Generated by Django 4.2.10 on 2024-03-19 16:38 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/server/portal/apps/licenses/models.py b/server/portal/apps/licenses/models.py index 5a2f69fceb..d5c740f718 100644 --- a/server/portal/apps/licenses/models.py +++ b/server/portal/apps/licenses/models.py @@ -1,6 +1,7 @@ +import logging + from django.conf import settings from django.db import models -import logging logger = logging.getLogger(__name__) @@ -24,7 +25,7 @@ class Meta: abstract = True def __str__(self): - return "%s: %s" % (self.license_type, self.user.username) + return f"{self.license_type}: {self.user.username}" def license_as_str(self): self.license_file_content = self.license_file_content.replace("\r\n", "\n") diff --git a/server/portal/apps/news/api/views.py b/server/portal/apps/news/api/views.py index 6d61a4fc04..68647043b1 100644 --- a/server/portal/apps/news/api/views.py +++ b/server/portal/apps/news/api/views.py @@ -1,11 +1,11 @@ from html import unescape +import requests from django.conf import settings from django.contrib.auth.decorators import login_required from django.http import JsonResponse from django.utils.decorators import method_decorator from django.utils.html import strip_tags -import requests from portal.exceptions.api import ApiException from portal.views.base import BaseApiView @@ -28,7 +28,7 @@ def get(self, request, *args, **kwargs): def _get_user_news(self): auth = requests.auth.HTTPBasicAuth(settings.TAS_CLIENT_KEY, settings.TAS_CLIENT_SECRET) - r = requests.get("{0}/announcements".format(settings.TAS_URL), auth=auth) + r = requests.get(f"{settings.TAS_URL}/announcements", auth=auth) resp = r.json() diff --git a/server/portal/apps/news/views.py b/server/portal/apps/news/views.py index c77a78c249..a3630975d5 100644 --- a/server/portal/apps/news/views.py +++ b/server/portal/apps/news/views.py @@ -1,7 +1,7 @@ -from django.views.generic.base import TemplateView from django.conf import settings from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator +from django.views.generic.base import TemplateView @method_decorator(login_required, name="dispatch") @@ -13,7 +13,7 @@ class IndexView(TemplateView): template_name = "portal/apps/workbench/index.html" def get_context_data(self, **kwargs): - context = super(IndexView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context["setup_complete"] = self.request.user.profile.setup_complete context["DEBUG"] = settings.DEBUG return context diff --git a/server/portal/apps/notifications/consumers.py b/server/portal/apps/notifications/consumers.py index 8f9362202d..0da7636972 100644 --- a/server/portal/apps/notifications/consumers.py +++ b/server/portal/apps/notifications/consumers.py @@ -1,6 +1,6 @@ import logging -from channels.generic.websocket import AsyncJsonWebsocketConsumer +from channels.generic.websocket import AsyncJsonWebsocketConsumer logger = logging.getLogger(__name__) diff --git a/server/portal/apps/notifications/consumers_unit_test.py b/server/portal/apps/notifications/consumers_unit_test.py index 6b0439802a..0c918a7641 100644 --- a/server/portal/apps/notifications/consumers_unit_test.py +++ b/server/portal/apps/notifications/consumers_unit_test.py @@ -1,6 +1,7 @@ import pytest -from channels.testing import WebsocketCommunicator from channels.layers import get_channel_layer +from channels.testing import WebsocketCommunicator + from .consumers import NotificationsConsumer diff --git a/server/portal/apps/notifications/migrations/0001_initial.py b/server/portal/apps/notifications/migrations/0001_initial.py index 65a02db552..e8ae293594 100644 --- a/server/portal/apps/notifications/migrations/0001_initial.py +++ b/server/portal/apps/notifications/migrations/0001_initial.py @@ -1,6 +1,7 @@ # Generated by Django 2.2.7 on 2019-12-05 22:37 import datetime + from django.db import migrations, models diff --git a/server/portal/apps/notifications/migrations/0002_auto_20200218_2115.py b/server/portal/apps/notifications/migrations/0002_auto_20200218_2115.py index 5375fe8003..9f4afb3442 100644 --- a/server/portal/apps/notifications/migrations/0002_auto_20200218_2115.py +++ b/server/portal/apps/notifications/migrations/0002_auto_20200218_2115.py @@ -1,7 +1,7 @@ # Generated by Django 2.2.7 on 2020-02-18 21:15 -from django.db import migrations, models import django.utils.timezone +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/server/portal/apps/notifications/models.py b/server/portal/apps/notifications/models.py index c324cc3e07..44535df249 100644 --- a/server/portal/apps/notifications/models.py +++ b/server/portal/apps/notifications/models.py @@ -1,9 +1,10 @@ -from django.db import models -from django.core.serializers.json import DjangoJSONEncoder -from django.utils import timezone -import logging import json +import logging + import six +from django.core.serializers.json import DjangoJSONEncoder +from django.db import models +from django.utils import timezone logger = logging.getLogger(__name__) @@ -67,7 +68,7 @@ def save(self, *args, **kwargs): logger.debug("Keys with error: %s . Value: %s", key, self.extra[key]) raise - super(BaseNotify, self).save(*args, **kwargs) + super().save(*args, **kwargs) @property def extra_content(self): @@ -92,7 +93,7 @@ def mark_deleted(self): self.save() def to_dict(self): - event_data = super(Notification, self).to_dict() + event_data = super().to_dict() event_data.update({"user": self.user, "read": self.read, "deleted": self.deleted}) return event_data @@ -101,6 +102,6 @@ class Broadcast(BaseNotify): group = models.CharField(max_length=20) def to_dict(self): - event_data = super(Broadcast, self).to_dict() + event_data = super().to_dict() event_data.update({"group": self.group}) return event_data diff --git a/server/portal/apps/notifications/unit_test.py b/server/portal/apps/notifications/unit_test.py index e6d249f91c..cdc8861a41 100644 --- a/server/portal/apps/notifications/unit_test.py +++ b/server/portal/apps/notifications/unit_test.py @@ -1,14 +1,13 @@ -from django.test import TestCase -from django.test import Client -from django.contrib.auth import get_user_model -from django.urls import reverse -from unittest import skip import json +import logging import os +from unittest import skip -from .models import Notification +from django.contrib.auth import get_user_model +from django.test import Client, TestCase +from django.urls import reverse -import logging +from .models import Notification logger = logging.getLogger(__name__) diff --git a/server/portal/apps/notifications/urls.py b/server/portal/apps/notifications/urls.py index 34d09b56f3..4b06e912e3 100644 --- a/server/portal/apps/notifications/urls.py +++ b/server/portal/apps/notifications/urls.py @@ -1,6 +1,6 @@ from django.urls import re_path -from portal.apps.notifications.views import ManageNotificationsView +from portal.apps.notifications.views import ManageNotificationsView app_name = "notifications" urlpatterns = [ diff --git a/server/portal/apps/notifications/views.py b/server/portal/apps/notifications/views.py index 2b77309b62..ecf0bf0a33 100644 --- a/server/portal/apps/notifications/views.py +++ b/server/portal/apps/notifications/views.py @@ -1,11 +1,11 @@ +import json import logging + from django.http import JsonResponse -from portal.apps.notifications.models import Notification +from portal.apps.notifications.models import Notification from portal.views.base import BaseApiView -import json - logger = logging.getLogger(__name__) diff --git a/server/portal/apps/onboarding/api/urls.py b/server/portal/apps/onboarding/api/urls.py index fa7a7940ff..4f2c21b4c2 100644 --- a/server/portal/apps/onboarding/api/urls.py +++ b/server/portal/apps/onboarding/api/urls.py @@ -4,8 +4,8 @@ """ from django.urls import path -from portal.apps.onboarding.api import views +from portal.apps.onboarding.api import views app_name = "portal_onboarding_api" urlpatterns = [ diff --git a/server/portal/apps/onboarding/api/views.py b/server/portal/apps/onboarding/api/views.py index b00c4638ac..a52948cbce 100644 --- a/server/portal/apps/onboarding/api/views.py +++ b/server/portal/apps/onboarding/api/views.py @@ -1,21 +1,23 @@ +import json import logging -from portal.views.base import BaseApiView + +from django.conf import settings +from django.contrib.admin.views.decorators import staff_member_required from django.contrib.auth import get_user_model -from django.core.exceptions import PermissionDenied, ObjectDoesNotExist +from django.contrib.auth.decorators import login_required +from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.http import ( Http404, - JsonResponse, HttpResponseBadRequest, + JsonResponse, ) -from django.contrib.auth.decorators import login_required -from django.contrib.admin.views.decorators import staff_member_required from django.utils.decorators import method_decorator -from django.conf import settings + +from portal.apps.onboarding.execute import execute_setup_steps, execute_single_step, load_setup_step, log_setup_state from portal.apps.onboarding.models import SetupEvent, SetupEventEncoder -from portal.apps.onboarding.execute import log_setup_state, load_setup_step, execute_single_step, execute_setup_steps from portal.apps.onboarding.state import SetupState from portal.apps.users.utils import q_to_model_queries -import json +from portal.views.base import BaseApiView logger = logging.getLogger(__name__) @@ -155,9 +157,7 @@ def complete(self, request, setup_step): if not request.user.is_staff: raise PermissionDenied setup_step.state = SetupState.COMPLETED - setup_step.log( - "{step} marked complete by {staff}".format(step=setup_step.display_name(), staff=request.user.username) - ) + setup_step.log(f"{setup_step.display_name()} marked complete by {request.user.username}") def reset(self, request, setup_step): """ @@ -165,16 +165,14 @@ def reset(self, request, setup_step): """ if not request.user.is_staff: raise PermissionDenied - setup_step.log("{step} reset by {staff}".format(step=setup_step.display_name(), staff=request.user.username)) + setup_step.log(f"{setup_step.display_name()} reset by {request.user.username}") # Mark the user's setup_complete as False setup_step.user.profile.setup_complete = False setup_step.user.profile.save() log_setup_state( setup_step.user, - "{user} setup marked incomplete, due to reset of {step}".format( - user=setup_step.user.username, step=setup_step.step_name() - ), + f"{setup_step.user.username} setup marked incomplete, due to reset of {setup_step.step_name()}", ) setup_step.prepare() @@ -182,11 +180,7 @@ def client_action(self, request, setup_step, action, data): """ Call client_action on a setup step """ - setup_step.log( - "{action} action on {step} by {username}".format( - action=action, step=setup_step.step_name(), username=request.user.username - ) - ) + setup_step.log(f"{action} action on {setup_step.step_name()} by {request.user.username}") setup_step.client_action(action, data, request) def post(self, request, username): diff --git a/server/portal/apps/onboarding/api/views_unit_test.py b/server/portal/apps/onboarding/api/views_unit_test.py index b3cb0d381b..03a614c7a4 100644 --- a/server/portal/apps/onboarding/api/views_unit_test.py +++ b/server/portal/apps/onboarding/api/views_unit_test.py @@ -1,11 +1,13 @@ -from mock import MagicMock -from django.http import JsonResponse import json +import logging +from unittest.mock import MagicMock + +import pytest +from django.http import JsonResponse + +from portal.apps.onboarding.api.views import SetupStepView, get_user_onboarding from portal.apps.onboarding.models import SetupEvent from portal.apps.onboarding.state import SetupState -from portal.apps.onboarding.api.views import SetupStepView, get_user_onboarding -import pytest -import logging logger = logging.getLogger(__name__) @@ -28,24 +30,24 @@ def mocked_log_setup_state(mocker): def test_get_user(client, authenticated_user): - response = client.get("/api/onboarding/user/{}/".format(authenticated_user.username)) + response = client.get(f"/api/onboarding/user/{authenticated_user.username}/") assert response.status_code == 200 result = json.loads(response.content) assert result["username"] == "username" def test_get_user_unauthenticated_forbidden(client, regular_user): - response = client.get("/api/onboarding/user/{}/".format(regular_user.username)) + response = client.get(f"/api/onboarding/user/{regular_user.username}/") assert response.status_code == 302 def test_get_other_user_forbidden(client, authenticated_user, regular_user2): - response = client.get("/api/onboarding/user/{}/".format(regular_user2.username)) + response = client.get(f"/api/onboarding/user/{regular_user2.username}/") assert response.status_code == 403 def test_get_user_as_staff(client, authenticated_staff, regular_user): - response = client.get("/api/onboarding/user/{}/".format(regular_user.username)) + response = client.get(f"/api/onboarding/user/{regular_user.username}/") assert response.status_code == 200 result = json.loads(response.content) assert result["username"] == regular_user.username @@ -68,7 +70,7 @@ def test_get_non_existent_user_as_staff(client, authenticated_staff): def test_get_user_as_user(client, settings, authenticated_user, mock_steps): # A user should be able to retrieve their own setup event info - response = client.get("/api/onboarding/user/{}".format(authenticated_user.username), follow=True) + response = client.get(f"/api/onboarding/user/{authenticated_user.username}", follow=True) result = response.json() result = json.loads(response.content) @@ -82,7 +84,7 @@ def test_get_user_as_user(client, settings, authenticated_user, mock_steps): def test_retry_step(client, settings, authenticated_user, mock_retry_step, mocker): mock_execute_single_step = mocker.patch("portal.apps.onboarding.api.views.execute_single_step") - response = client.get("/api/onboarding/user/{}".format(authenticated_user.username), follow=True) + response = client.get(f"/api/onboarding/user/{authenticated_user.username}", follow=True) mock_execute_single_step.apply_async.assert_called_with( args=[authenticated_user.username, "portal.apps.onboarding.steps.test_steps.MockStep"], countdown=2 ) @@ -96,14 +98,14 @@ def test_retry_step(client, settings, authenticated_user, mock_retry_step, mocke def test_incomplete_post(client, authenticated_user): # post should return HttpResponseBadRequest (400) if fields are missing response = client.post( - "/api/onboarding/user/{}/".format(authenticated_user), + f"/api/onboarding/user/{authenticated_user}/", content_type="application/json", data=json.dumps({"action": "user_confirm"}), ) assert response.status_code == 400 response = client.post( - "/api/onboarding/user/{}/".format(authenticated_user), + f"/api/onboarding/user/{authenticated_user}/", content_type="application/json", data=json.dumps({"step": "setupstep"}), ) @@ -123,7 +125,7 @@ def test_client_action(regular_user, rf): def test_reset_not_staff(client, authenticated_user): response = client.post( - "/api/onboarding/user/{}/".format(authenticated_user.username), + f"/api/onboarding/user/{authenticated_user.username}/", content_type="application/json", data=json.dumps({"action": "reset", "step": "portal.apps.onboarding.steps.test_steps.MockStep"}), ) @@ -149,13 +151,13 @@ def test_reset(rf, staff_user, regular_user, mocked_log_setup_state): def test_complete_not_staff(client, authenticated_user, regular_user2): - response = client.post("/api/onboarding/user/{}/".format(regular_user2)) + response = client.post(f"/api/onboarding/user/{regular_user2}/") assert response.status_code == 403 def test_complete(client, authenticated_staff, regular_user, mock_steps, mocked_executor): response = client.post( - "/api/onboarding/user/{}/".format(regular_user.username), + f"/api/onboarding/user/{regular_user.username}/", content_type="application/json", data=json.dumps({"action": "complete", "step": "portal.apps.onboarding.steps.test_steps.MockStep"}), ) diff --git a/server/portal/apps/onboarding/conftest.py b/server/portal/apps/onboarding/conftest.py index 51f7d1a9bf..876500a733 100644 --- a/server/portal/apps/onboarding/conftest.py +++ b/server/portal/apps/onboarding/conftest.py @@ -1,4 +1,5 @@ import pytest + from portal.apps.onboarding.models import SetupEvent from portal.apps.onboarding.state import SetupState diff --git a/server/portal/apps/onboarding/execute.py b/server/portal/apps/onboarding/execute.py index eb244111dc..2b30216bbd 100644 --- a/server/portal/apps/onboarding/execute.py +++ b/server/portal/apps/onboarding/execute.py @@ -1,12 +1,14 @@ -from inspect import isclass +import logging from importlib import import_module +from inspect import isclass + +from celery import shared_task from django.conf import settings -from portal.apps.onboarding.state import SetupState + +from portal.apps.accounts.models import PortalProfile from portal.apps.onboarding.models import SetupEvent +from portal.apps.onboarding.state import SetupState from portal.apps.onboarding.steps.abstract import AbstractStep -from celery import shared_task -from portal.apps.accounts.models import PortalProfile -import logging logger = logging.getLogger(__name__) @@ -18,18 +20,18 @@ class StepExecuteException(Exception): """ def __init__(self, message): - super(StepExecuteException, self).__init__(message) + super().__init__(message) def new_user_setup_check(user): extra_steps = getattr(settings, "PORTAL_USER_ACCOUNT_SETUP_STEPS", []) if len(extra_steps) == 0: - logger.info("No extra setup steps for user {username}".format(username=user.username)) + logger.info(f"No extra setup steps for user {user.username}") profile = PortalProfile.objects.get(user=user) profile.setup_complete = True profile.save() else: - logger.info("Preparing onboarding steps for user {username}".format(username=user.username)) + logger.info(f"Preparing onboarding steps for user {user.username}") prepare_setup_steps(user) @@ -50,10 +52,10 @@ def load_setup_step(user, step): module = import_module(module_str) call = getattr(module, callable_str) if not isclass(call): - raise ValueError("Setup step {step} is not a class".format(step=step)) + raise ValueError(f"Setup step {step} is not a class") setup_step = call(user) if not isinstance(setup_step, AbstractStep): - raise ValueError("Setup step {step} is not a subclass of AbstractStep".format(step=step)) + raise ValueError(f"Setup step {step} is not a subclass of AbstractStep") return setup_step @@ -76,7 +78,7 @@ def process_setup_step(setup_step): except Exception as err: logger.exception("Problem processing setup step") setup_step.state = SetupState.ERROR - setup_step.log("Exception: {err}".format(err=str(err))) + setup_step.log(f"Exception: {str(err)}") @shared_task() @@ -103,7 +105,7 @@ def execute_setup_steps(username): # a step failing to reach the COMPLETED state, mark the user as setup_complete user.profile.setup_complete = True user.profile.save() - log_setup_state(user, "{user} setup is now complete".format(user=user.username)) + log_setup_state(user, f"{user.username} setup is now complete") @shared_task() diff --git a/server/portal/apps/onboarding/execute_unit_test.py b/server/portal/apps/onboarding/execute_unit_test.py index 0bb14428f2..9aa1bae971 100644 --- a/server/portal/apps/onboarding/execute_unit_test.py +++ b/server/portal/apps/onboarding/execute_unit_test.py @@ -1,19 +1,20 @@ -from mock import MagicMock -from portal.apps.onboarding.steps.test_steps import MockProcessingCompleteStep +from unittest.mock import MagicMock + +import pytest + from portal.apps.accounts.models import PortalProfile -from portal.apps.onboarding.models import SetupEvent -from portal.apps.onboarding.state import SetupState from portal.apps.onboarding.execute import ( + StepExecuteException, execute_setup_steps, execute_single_step, - prepare_setup_steps, load_setup_step, log_setup_state, new_user_setup_check, - StepExecuteException, + prepare_setup_steps, ) -import pytest - +from portal.apps.onboarding.models import SetupEvent +from portal.apps.onboarding.state import SetupState +from portal.apps.onboarding.steps.test_steps import MockProcessingCompleteStep pytestmark = pytest.mark.django_db diff --git a/server/portal/apps/onboarding/migrations/0001_initial.py b/server/portal/apps/onboarding/migrations/0001_initial.py index b53eb797cc..3e1073374d 100644 --- a/server/portal/apps/onboarding/migrations/0001_initial.py +++ b/server/portal/apps/onboarding/migrations/0001_initial.py @@ -1,9 +1,9 @@ # Generated by Django 2.2.7 on 2019-11-05 19:50 -from django.conf import settings import django.contrib.postgres.fields.jsonb -from django.db import migrations, models import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/server/portal/apps/onboarding/migrations/0001_squashed_0002_alter_setupevent_data.py b/server/portal/apps/onboarding/migrations/0001_squashed_0002_alter_setupevent_data.py index c84ed2ae04..ac1c5cbc35 100644 --- a/server/portal/apps/onboarding/migrations/0001_squashed_0002_alter_setupevent_data.py +++ b/server/portal/apps/onboarding/migrations/0001_squashed_0002_alter_setupevent_data.py @@ -1,8 +1,8 @@ # Generated by Django 3.2.19 on 2023-05-04 22:00 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/server/portal/apps/onboarding/models.py b/server/portal/apps/onboarding/models.py index b0a295594f..7eed644306 100644 --- a/server/portal/apps/onboarding/models.py +++ b/server/portal/apps/onboarding/models.py @@ -3,9 +3,9 @@ :synopsis: Onboarding models """ -from django.db import models from django.conf import settings from django.core.serializers.json import DjangoJSONEncoder +from django.db import models class SetupEvent(models.Model): @@ -33,14 +33,7 @@ class SetupEvent(models.Model): data = models.JSONField(null=True) def __str__(self): - return "{username} {time} {step} ({state}) - {message} ({data})".format( - username=self.user.username, - time=self.time, - step=self.step, - state=self.state, - message=self.message, - data=self.data, - ) + return f"{self.user.username} {self.time} {self.step} ({self.state}) - {self.message} ({self.data})" def to_dict(self): return { @@ -59,4 +52,4 @@ def default(self, obj): # pylint: disable=method-hidden, arguments-differ event = obj return event.to_dict() else: - return super(SetupEventEncoder, self).default(obj) + return super().default(obj) diff --git a/server/portal/apps/onboarding/models_unit_test.py b/server/portal/apps/onboarding/models_unit_test.py index f36849a674..de9258e465 100644 --- a/server/portal/apps/onboarding/models_unit_test.py +++ b/server/portal/apps/onboarding/models_unit_test.py @@ -1,7 +1,7 @@ -from portal.apps.onboarding.state import SetupState -from portal.apps.onboarding.models import SetupEvent import pytest +from portal.apps.onboarding.models import SetupEvent +from portal.apps.onboarding.state import SetupState pytestmark = pytest.mark.django_db diff --git a/server/portal/apps/onboarding/steps/abstract.py b/server/portal/apps/onboarding/steps/abstract.py index 26dbc03ec8..bf0d899cd5 100644 --- a/server/portal/apps/onboarding/steps/abstract.py +++ b/server/portal/apps/onboarding/steps/abstract.py @@ -1,8 +1,10 @@ from abc import ABCMeta, abstractmethod + +from django.conf import settings from six import add_metaclass + from portal.apps.onboarding.models import SetupEvent from portal.apps.onboarding.state import SetupState -from django.conf import settings @add_metaclass(ABCMeta) @@ -70,12 +72,10 @@ def complete(self, message, data=None): self.log(message, data) def __str__(self): - return "<{step} for {username} is {state}>".format( - step=self.step_name(), state=self.state, username=self.user.username - ) + return f"<{self.step_name()} for {self.user.username} is {self.state}>" def step_name(self): - return "{module}.{classname}".format(module=self.__module__, classname=self.__class__.__name__) + return f"{self.__module__}.{self.__class__.__name__}" @abstractmethod def display_name(self): diff --git a/server/portal/apps/onboarding/steps/abstract_unit_test.py b/server/portal/apps/onboarding/steps/abstract_unit_test.py index 31887afe08..d2ba387d14 100644 --- a/server/portal/apps/onboarding/steps/abstract_unit_test.py +++ b/server/portal/apps/onboarding/steps/abstract_unit_test.py @@ -1,8 +1,9 @@ +import pytest +from django.db.models import signals + from portal.apps.onboarding.models import SetupEvent from portal.apps.onboarding.state import SetupState -from django.db.models import signals from portal.apps.onboarding.steps.test_steps import MockStep -import pytest @pytest.fixture(autouse=True) diff --git a/server/portal/apps/onboarding/steps/access.py b/server/portal/apps/onboarding/steps/access.py index 23e6577fef..daab3d01da 100644 --- a/server/portal/apps/onboarding/steps/access.py +++ b/server/portal/apps/onboarding/steps/access.py @@ -7,7 +7,7 @@ def __init__(self, user): """ Call super class constructor """ - super(RequestAccessStep, self).__init__(user) + super().__init__(user) self.user_confirm = "Request Portal Access" self.staff_approve = "Grant Portal Access" self.staff_deny = "Deny Access Request" @@ -20,7 +20,7 @@ def description(self): After sending the request, wait for their approval.""" def prepare(self): - super(RequestAccessStep, self).prepare() + super().prepare() self.state = SetupState.PENDING self.log("Waiting for access check") @@ -43,8 +43,8 @@ def client_action(self, action, data, request): return if action == "staff_approve": - self.complete("Portal access request approved by {user}".format(user=request.user.username)) + self.complete(f"Portal access request approved by {request.user.username}") elif action == "staff_deny": self.deny("Portal access request has not been approved.") else: - self.fail("Invalid client action {action}".format(action=action)) + self.fail(f"Invalid client action {action}") diff --git a/server/portal/apps/onboarding/steps/access_unit_test.py b/server/portal/apps/onboarding/steps/access_unit_test.py index ee9c10a3c2..07999f9375 100644 --- a/server/portal/apps/onboarding/steps/access_unit_test.py +++ b/server/portal/apps/onboarding/steps/access_unit_test.py @@ -1,15 +1,17 @@ -from django.test import TestCase, RequestFactory +from unittest.mock import ANY, patch + +import pytest from django.contrib.auth import get_user_model -from mock import patch, ANY +from django.test import RequestFactory, TestCase + from portal.apps.onboarding.state import SetupState from portal.apps.onboarding.steps.access import RequestAccessStep -import pytest @pytest.mark.django_db(transaction=True) class TestRequestAccessStep(TestCase): def setUp(self): - super(TestRequestAccessStep, self).setUp() + super().setUp() # Create a test user User = get_user_model() @@ -19,7 +21,7 @@ def setUp(self): self.staff.is_staff = True def tearDown(self): - super(TestRequestAccessStep, self).tearDown() + super().tearDown() @patch("portal.apps.onboarding.steps.access.RequestAccessStep.log") def test_prepare(self, mock_log): diff --git a/server/portal/apps/onboarding/steps/allocation.py b/server/portal/apps/onboarding/steps/allocation.py index f3f5d4b379..22d2d8b4be 100644 --- a/server/portal/apps/onboarding/steps/allocation.py +++ b/server/portal/apps/onboarding/steps/allocation.py @@ -1,5 +1,5 @@ -from portal.apps.onboarding.steps.abstract import AbstractStep from portal.apps.onboarding.state import SetupState +from portal.apps.onboarding.steps.abstract import AbstractStep from portal.apps.users.utils import get_allocations @@ -8,13 +8,16 @@ def __init__(self, user): """ Call super class constructor """ - super(AllocationStep, self).__init__(user) + super().__init__(user) def display_name(self): return "Allocations" def description(self): - return """Accessing your allocations. If unsuccessful, verify the PI has added you to the allocations for this project.""" + return ( + "Accessing your allocations. If unsuccessful, verify the PI " + "has added you to the allocations for this project." + ) def prepare(self): self.state = SetupState.PENDING @@ -32,7 +35,7 @@ def process(self): if not allocations.get("active"): self.state = SetupState.FAILED self.log( - """User {0} does not have any allocations""".format(self.user.username), + f"""User {self.user.username} does not have any allocations""", ) else: if "expected_hosts" in self.settings: @@ -44,8 +47,8 @@ def process(self): if missing_hosts: self.state = SetupState.FAILED - self.log("User {0} is missing allocations on: {1}".format(self.user.username, missing_hosts)) + self.log(f"User {self.user.username} is missing allocations on: {missing_hosts}") return - self.log("Expected host allocations found: {0}".format(matched_hosts)) + self.log(f"Expected host allocations found: {matched_hosts}") self.complete("Allocations retrieved", data=allocations) diff --git a/server/portal/apps/onboarding/steps/allocation_unit_test.py b/server/portal/apps/onboarding/steps/allocation_unit_test.py index 80d024764c..497a7c7bdc 100644 --- a/server/portal/apps/onboarding/steps/allocation_unit_test.py +++ b/server/portal/apps/onboarding/steps/allocation_unit_test.py @@ -1,7 +1,9 @@ -from portal.apps.onboarding.steps.allocation import AllocationStep -from django.conf import settings -from mock import ANY +from unittest.mock import ANY + import pytest +from django.conf import settings + +from portal.apps.onboarding.steps.allocation import AllocationStep @pytest.fixture diff --git a/server/portal/apps/onboarding/steps/mfa.py b/server/portal/apps/onboarding/steps/mfa.py index 0800fe89eb..0cef1d701a 100644 --- a/server/portal/apps/onboarding/steps/mfa.py +++ b/server/portal/apps/onboarding/steps/mfa.py @@ -1,13 +1,14 @@ -from portal.apps.onboarding.steps.abstract import AbstractStep -from portal.apps.onboarding.state import SetupState +import requests from django.conf import settings from requests.auth import HTTPBasicAuth -import requests + +from portal.apps.onboarding.state import SetupState +from portal.apps.onboarding.steps.abstract import AbstractStep class MFAStep(AbstractStep): def __init__(self, user): - super(MFAStep, self).__init__(user) + super().__init__(user) self.user_confirm = "Confirm MFA Pairing" def display_name(self): @@ -17,8 +18,9 @@ def description(self): return """First, set up multi-factor authentication on the TACC User Portal using the - TACC Token App, - then confirm the pairing was successful.""" + + TACC Token App + , then confirm the pairing was successful.""" def custom_status(self): if self.state == SetupState.COMPLETED: @@ -52,4 +54,4 @@ def client_action(self, action, data, request): self.prepare() def tas_pairings_url(self): - return "{0}/tup/users/{1}/pairings".format(settings.TAS_URL, self.user.username) + return f"{settings.TAS_URL}/tup/users/{self.user.username}/pairings" diff --git a/server/portal/apps/onboarding/steps/mfa_unit_test.py b/server/portal/apps/onboarding/steps/mfa_unit_test.py index 89f13de521..2f3ed01224 100644 --- a/server/portal/apps/onboarding/steps/mfa_unit_test.py +++ b/server/portal/apps/onboarding/steps/mfa_unit_test.py @@ -1,6 +1,7 @@ -from portal.apps.onboarding.steps.mfa import MFAStep import pytest +from portal.apps.onboarding.steps.mfa import MFAStep + @pytest.fixture def mock_mfa_check(mocker): diff --git a/server/portal/apps/onboarding/steps/project_membership.py b/server/portal/apps/onboarding/steps/project_membership.py index 8aa509476f..5ff57c87dc 100644 --- a/server/portal/apps/onboarding/steps/project_membership.py +++ b/server/portal/apps/onboarding/steps/project_membership.py @@ -1,13 +1,14 @@ import logging + from django.conf import settings -from requests.auth import HTTPBasicAuth from pytas.http import TASClient +from requests.auth import HTTPBasicAuth from rt import Rt -from portal.apps.onboarding.steps.abstract import AbstractStep + from portal.apps.onboarding.state import SetupState +from portal.apps.onboarding.steps.abstract import AbstractStep from portal.apps.users.tasks import index_allocations - logger = logging.getLogger(__name__) @@ -16,7 +17,7 @@ def __init__(self, user): """ Call super class constructor """ - super(ProjectMembershipStep, self).__init__(user) + super().__init__(user) self.project_sql_ids = ( self.settings["project_sql_id"] if isinstance(self.settings["project_sql_id"], list) @@ -72,9 +73,7 @@ def send_project_request(self, request): ticket_text = ticket_text.format( username=self.user.username, project=self.default_project["title"], - onboarding_url=request.build_absolute_uri( - "/workbench/onboarding/setup/{username}".format(username=self.user.username) - ), + onboarding_url=request.build_absolute_uri(f"/workbench/onboarding/setup/{self.user.username}"), ) try: @@ -145,7 +144,7 @@ def deny_project_request(self): ) tracker.edit_ticket(ticket_id, Status="resolved") else: - self.fail("The portal was unable to close RT Ticket {ticket_id}".format(ticket_id=ticket_id)) + self.fail(f"The portal was unable to close RT Ticket {ticket_id}") def close_project_request(self, deny=False): ticket_id = None @@ -168,7 +167,7 @@ def close_project_request(self, deny=False): ) tracker.edit_ticket(ticket_id, Status="resolved") else: - self.fail("The portal was unable to close RT Ticket {ticket_id}".format(ticket_id=ticket_id)) + self.fail(f"The portal was unable to close RT Ticket {ticket_id}") def process(self): if self.is_project_member(): @@ -189,13 +188,13 @@ def client_action(self, action, data, request): try: self.add_to_project() self.close_project_request() - self.complete("Portal access request approved by {user}".format(user=request.user.username)) + self.complete(f"Portal access request approved by {request.user.username}") except Exception as err: - logger.exception(msg="Error during staff_approve on {}".format(self.step_name())) + logger.exception(msg=f"Error during staff_approve on {self.step_name()}") logger.error(err.args) self.fail("An error occurred while trying to add this user to the project") elif action == "staff_deny": self.deny_project_request() self.deny("Portal access request has not been approved.") else: - self.fail("Invalid client action {action}".format(action=action)) + self.fail(f"Invalid client action {action}") diff --git a/server/portal/apps/onboarding/steps/project_membership_unit_test.py b/server/portal/apps/onboarding/steps/project_membership_unit_test.py index 708ecf7b6a..346eb83a10 100644 --- a/server/portal/apps/onboarding/steps/project_membership_unit_test.py +++ b/server/portal/apps/onboarding/steps/project_membership_unit_test.py @@ -1,10 +1,12 @@ -from django.conf import settings -from portal.apps.onboarding.steps.project_membership import ProjectMembershipStep -from portal.apps.onboarding.models import SetupEvent -from mock import MagicMock, ANY -import pytest import json import os +from unittest.mock import ANY, MagicMock + +import pytest +from django.conf import settings + +from portal.apps.onboarding.models import SetupEvent +from portal.apps.onboarding.steps.project_membership import ProjectMembershipStep @pytest.fixture diff --git a/server/portal/apps/onboarding/steps/system_access.py b/server/portal/apps/onboarding/steps/system_access.py index 32ef470d51..8b90b4109d 100644 --- a/server/portal/apps/onboarding/steps/system_access.py +++ b/server/portal/apps/onboarding/steps/system_access.py @@ -1,7 +1,9 @@ -from .project_membership import ProjectMembershipStep +import logging + from portal.apps.onboarding.state import SetupState from portal.apps.users.utils import get_allocations -import logging + +from .project_membership import ProjectMembershipStep logger = logging.getLogger(__name__) @@ -11,7 +13,7 @@ def __init__(self, user): """ Call super class constructor """ - super(SystemAccessStep, self).__init__(user) + super().__init__(user) self.user_confirm = "Request System Access" self.staff_deny = "Deny System Access Request" diff --git a/server/portal/apps/onboarding/steps/system_access_unit_test.py b/server/portal/apps/onboarding/steps/system_access_unit_test.py index a629236b65..0087718e83 100644 --- a/server/portal/apps/onboarding/steps/system_access_unit_test.py +++ b/server/portal/apps/onboarding/steps/system_access_unit_test.py @@ -1,9 +1,11 @@ -from django.conf import settings -from portal.apps.onboarding.steps.system_access import SystemAccessStep -import pytest import json import os +import pytest +from django.conf import settings + +from portal.apps.onboarding.steps.system_access import SystemAccessStep + @pytest.fixture def tas_client(mocker): diff --git a/server/portal/apps/onboarding/steps/system_access_v3.py b/server/portal/apps/onboarding/steps/system_access_v3.py index 6b00fd0097..414a898eb7 100644 --- a/server/portal/apps/onboarding/steps/system_access_v3.py +++ b/server/portal/apps/onboarding/steps/system_access_v3.py @@ -1,8 +1,10 @@ import logging -from portal.apps.onboarding.steps.abstract import AbstractStep + +from tapipy.errors import BaseTapyException + from portal.apps.onboarding.state import SetupState +from portal.apps.onboarding.steps.abstract import AbstractStep from portal.libs.agave.utils import service_account -from tapipy.errors import BaseTapyException from portal.utils.encryption import createKeyPair logger = logging.getLogger(__name__) @@ -86,7 +88,7 @@ def __init__(self, user): """ Call super class constructor """ - super(SystemAccessStepV3, self).__init__(user) + super().__init__(user) def display_name(self): return "System Access" diff --git a/server/portal/apps/onboarding/steps/test_steps.py b/server/portal/apps/onboarding/steps/test_steps.py index ad46c32289..24d3b8ccec 100644 --- a/server/portal/apps/onboarding/steps/test_steps.py +++ b/server/portal/apps/onboarding/steps/test_steps.py @@ -1,4 +1,5 @@ -from mock import MagicMock +from unittest.mock import MagicMock + from portal.apps.onboarding.state import SetupState from portal.apps.onboarding.steps.abstract import AbstractStep @@ -11,7 +12,7 @@ class MockStep(AbstractStep): def __init__(self, user): self.prepare_spy = MagicMock() - super(MockStep, self).__init__(user) + super().__init__(user) def display_name(self): return "Mock Step" @@ -29,7 +30,7 @@ class MockProcessingCompleteStep(AbstractStep): """ def __init__(self, user): - super(MockProcessingCompleteStep, self).__init__(user) + super().__init__(user) self.process_spy = MagicMock() def prepare(self): @@ -53,7 +54,7 @@ class MockProcessingFailStep(AbstractStep): """ def __init__(self, user): - super(MockProcessingFailStep, self).__init__(user) + super().__init__(user) self.process_spy = MagicMock() def prepare(self): @@ -78,7 +79,7 @@ class MockUserStep(AbstractStep): """ def __init__(self, user): - super(MockUserStep, self).__init__(user) + super().__init__(user) self.client_action_spy = MagicMock() def prepare(self): @@ -103,7 +104,7 @@ class MockStaffStep(AbstractStep): """ def __init__(self, user): - super(MockStaffStep, self).__init__(user) + super().__init__(user) self.staff_approve_spy = MagicMock() self.staff_deny_spy = MagicMock() @@ -122,10 +123,10 @@ def client_action(self, action, data, request): return if action == "staff_approve": - self.complete("Approved by {user}".format(user=request.user.username)) + self.complete(f"Approved by {request.user.username}") self.staff_approve_spy(action, data, request) elif action == "staff_deny": - self.fail("Denied by {user}".format(user=request.user.username)) + self.fail(f"Denied by {request.user.username}") self.staff_deny_spy(action, data, request) @@ -135,7 +136,7 @@ class MockErrorStep(AbstractStep): """ def __init__(self, user): - super(MockErrorStep, self).__init__(user) + super().__init__(user) def prepare(self): self.state = SetupState.PENDING diff --git a/server/portal/apps/portal_messages/admin.py b/server/portal/apps/portal_messages/admin.py index 945b443dc9..614c494394 100644 --- a/server/portal/apps/portal_messages/admin.py +++ b/server/portal/apps/portal_messages/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from portal.apps.portal_messages.models import CustomMessageTemplate diff --git a/server/portal/apps/portal_messages/intro_unit_test.py b/server/portal/apps/portal_messages/intro_unit_test.py index d58104aaa4..ec1c861671 100644 --- a/server/portal/apps/portal_messages/intro_unit_test.py +++ b/server/portal/apps/portal_messages/intro_unit_test.py @@ -1,5 +1,6 @@ import pytest -from portal.apps.portal_messages.models import IntroMessages, CustomMessageTemplate, CustomMessages + +from portal.apps.portal_messages.models import CustomMessages, CustomMessageTemplate, IntroMessages @pytest.fixture diff --git a/server/portal/apps/portal_messages/migrations/0001_initial.py b/server/portal/apps/portal_messages/migrations/0001_initial.py index 0124af887f..40ac60822a 100644 --- a/server/portal/apps/portal_messages/migrations/0001_initial.py +++ b/server/portal/apps/portal_messages/migrations/0001_initial.py @@ -1,9 +1,9 @@ # Generated by Django 2.2.17 on 2021-10-28 15:48 -from django.conf import settings -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/server/portal/apps/portal_messages/migrations/0002_custommessages_custommessagetemplate.py b/server/portal/apps/portal_messages/migrations/0002_custommessages_custommessagetemplate.py index 1d390170c7..5943885a75 100644 --- a/server/portal/apps/portal_messages/migrations/0002_custommessages_custommessagetemplate.py +++ b/server/portal/apps/portal_messages/migrations/0002_custommessages_custommessagetemplate.py @@ -1,8 +1,8 @@ # Generated by Django 2.2.27 on 2022-03-22 15:48 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/server/portal/apps/portal_messages/migrations/0006_migrate_intro_messages_cpu.py b/server/portal/apps/portal_messages/migrations/0006_migrate_intro_messages_cpu.py index 2e4e038e98..82ccf9180e 100644 --- a/server/portal/apps/portal_messages/migrations/0006_migrate_intro_messages_cpu.py +++ b/server/portal/apps/portal_messages/migrations/0006_migrate_intro_messages_cpu.py @@ -1,7 +1,6 @@ from django.conf import settings from django.db import migrations - CPU_DASHBOARD_MESSAGE = "This page allows you to monitor your job status and get help with tickets. " CPU_DATAFILES_MESSAGE = ( diff --git a/server/portal/apps/portal_messages/models.py b/server/portal/apps/portal_messages/models.py index b44496a904..476eb4bd73 100644 --- a/server/portal/apps/portal_messages/models.py +++ b/server/portal/apps/portal_messages/models.py @@ -1,6 +1,7 @@ import logging -from django.db import models + from django.conf import settings +from django.db import models from django.utils import timezone logger = logging.getLogger(__name__) @@ -69,7 +70,7 @@ def to_dict(self): } def __str__(self): - return "%s | %s | %s | %s" % ( + return "{} | {} | {} | {}".format( self.message_type, self.component, ("dismissible" if self.dismissible else "not dismissible"), diff --git a/server/portal/apps/portal_messages/urls.py b/server/portal/apps/portal_messages/urls.py index a9b21bc9e7..a30293c33b 100644 --- a/server/portal/apps/portal_messages/urls.py +++ b/server/portal/apps/portal_messages/urls.py @@ -1,8 +1,8 @@ """Message URLs""" from django.urls import path -from portal.apps.portal_messages import views +from portal.apps.portal_messages import views app_name = "message" urlpatterns = [path("intro/", views.IntroMessagesView.as_view()), path("custom/", views.CustomMessagesView.as_view())] diff --git a/server/portal/apps/portal_messages/views.py b/server/portal/apps/portal_messages/views.py index 16f18ad7b2..349ed44355 100644 --- a/server/portal/apps/portal_messages/views.py +++ b/server/portal/apps/portal_messages/views.py @@ -4,14 +4,15 @@ and generate CustomMessages """ +import json import logging -from portal.views.base import BaseApiView -from django.http import JsonResponse -from portal.apps.portal_messages.models import IntroMessages, CustomMessages, CustomMessageTemplate + from django.contrib.auth.decorators import login_required +from django.http import JsonResponse from django.utils.decorators import method_decorator -import json +from portal.apps.portal_messages.models import CustomMessages, CustomMessageTemplate, IntroMessages +from portal.views.base import BaseApiView logger = logging.getLogger(__name__) diff --git a/server/portal/apps/projects/admin.py b/server/portal/apps/projects/admin.py index 11cfaa16b0..6639e34dcf 100644 --- a/server/portal/apps/projects/admin.py +++ b/server/portal/apps/projects/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from portal.apps.projects.models.base import ProjectId diff --git a/server/portal/apps/projects/conftest.py b/server/portal/apps/projects/conftest.py index 0f12423148..bcefc8237b 100644 --- a/server/portal/apps/projects/conftest.py +++ b/server/portal/apps/projects/conftest.py @@ -1,4 +1,5 @@ import pytest + from portal.apps.projects.models.base import Project diff --git a/server/portal/apps/projects/exceptions.py b/server/portal/apps/projects/exceptions.py index c8f9349d92..258aff1aea 100644 --- a/server/portal/apps/projects/exceptions.py +++ b/server/portal/apps/projects/exceptions.py @@ -5,8 +5,8 @@ """ import logging -from portal.exceptions.api import ApiException +from portal.exceptions.api import ApiException LOGGER = logging.getLogger(__name__) @@ -27,4 +27,4 @@ def __init__(self, message=None, status=None, extra=None, **kwargs): """ msg = "User is not Authorized." sts = 403 - super(NotAuthorizedError, self).__init__(message=message or msg, status=status or sts, extra=extra, **kwargs) + super().__init__(message=message or msg, status=status or sts, extra=extra, **kwargs) diff --git a/server/portal/apps/projects/management/commands/migrate-projects.py b/server/portal/apps/projects/management/commands/migrate-projects.py index 54135d01f2..ace7f7201f 100644 --- a/server/portal/apps/projects/management/commands/migrate-projects.py +++ b/server/portal/apps/projects/management/commands/migrate-projects.py @@ -1,13 +1,14 @@ """Management command.""" +import logging + +from django.contrib.auth import get_user_model from django.core.management.base import BaseCommand -from portal.libs.agave.utils import service_account + from portal.apps.projects.models.base import Project from portal.apps.projects.models.metadata import LegacyProjectMetadata from portal.apps.search.tasks import index_project -from django.contrib.auth import get_user_model -import logging - +from portal.libs.agave.utils import service_account logger = logging.getLogger(__name__) @@ -40,16 +41,14 @@ def handle(self, *args, **options): ) ) if len(admins) != 1: - raise Exception("Not exactly one admin for {project_id}".format(project_id=meta.project_id)) + raise Exception(f"Not exactly one admin for {meta.project_id}") # Get first role tuple, first item in tuple which is username admin = get_user_model().objects.get(username=admins[0][0]) project.add_pi(admin) - logger.info( - "Set {admin} as PI on {project_id}".format(admin=admin.username, project_id=meta.project_id) - ) + logger.info(f"Set {admin.username} as PI on {meta.project_id}") except Exception as e: - logger.error("Could not migrate {project_id}".format(project_id=meta.project_id)) + logger.error(f"Could not migrate {meta.project_id}") logger.exception(e) index_project.apply_async(args=[meta.project_id]) diff --git a/server/portal/apps/projects/management/commands/migrate-projects_unit_test.py b/server/portal/apps/projects/management/commands/migrate-projects_unit_test.py index 097dd8bfd8..3aec544cf5 100644 --- a/server/portal/apps/projects/management/commands/migrate-projects_unit_test.py +++ b/server/portal/apps/projects/management/commands/migrate-projects_unit_test.py @@ -1,9 +1,9 @@ -from portal.apps.projects.models.base import Project -from portal.apps.projects.models.metadata import LegacyProjectMetadata +import pytest from django.core import management from django.db.models import signals -import pytest +from portal.apps.projects.models.base import Project +from portal.apps.projects.models.metadata import LegacyProjectMetadata pytestmark = pytest.mark.django_db diff --git a/server/portal/apps/projects/management/commands/projects_id.py b/server/portal/apps/projects/management/commands/projects_id.py index ae42a8f55f..7c569527f5 100644 --- a/server/portal/apps/projects/management/commands/projects_id.py +++ b/server/portal/apps/projects/management/commands/projects_id.py @@ -1,10 +1,11 @@ """Management command.""" -from django.db import transaction from django.core.exceptions import ObjectDoesNotExist from django.core.management.base import BaseCommand +from django.db import transaction + from portal.apps.projects.models.base import ProjectId -from portal.apps.projects.models.utils import get_latest_project_storage, get_latest_project_directory +from portal.apps.projects.models.utils import get_latest_project_directory, get_latest_project_storage class Command(BaseCommand): @@ -57,8 +58,8 @@ def handle(self, *args, **options): max_project_id = options["max_project_id"] if max_project_id: self.stdout.write( - "NOTE(!!!!): Ignoring project ids >= {} when " - "processing/updating the storage systems and directories".format(max_project_id) + f"NOTE(!!!!): Ignoring project ids >= {max_project_id} when " + "processing/updating the storage systems and directories" ) latest_storage_system_id = get_latest_project_storage(max_project_id=max_project_id) @@ -69,13 +70,13 @@ def handle(self, *args, **options): if latest_project_id == -1: self.stdout.write("There are no project directories.") - self.stdout.write("Latest storage system project id: {}".format(latest_storage_system_id)) - self.stdout.write("Latest directory project id: {}".format(latest_project_id)) + self.stdout.write(f"Latest storage system project id: {latest_storage_system_id}") + self.stdout.write(f"Latest directory project id: {latest_project_id}") try: with transaction.atomic(): model_project_id = ProjectId.objects.select_for_update().latest("last_updated").value - self.stdout.write("Latest project id in ProjectId model: {}".format(model_project_id)) + self.stdout.write(f"Latest project id in ProjectId model: {model_project_id}") except ObjectDoesNotExist: self.stdout.write("Latest project id in ProjectId model: None") @@ -84,5 +85,5 @@ def handle(self, *args, **options): ProjectId.update(options.get("update")) elif options["update_using_max_value_found"]: max_value_found = max(latest_storage_system_id, latest_project_id, 0) - self.stdout.write("Updating to value latest storage system id: {}".format(max_value_found)) + self.stdout.write(f"Updating to value latest storage system id: {max_value_found}") ProjectId.update(max_value_found) diff --git a/server/portal/apps/projects/management/commands/projects_id_unit_test.py b/server/portal/apps/projects/management/commands/projects_id_unit_test.py index 44e9d0da7b..aeae7bea99 100644 --- a/server/portal/apps/projects/management/commands/projects_id_unit_test.py +++ b/server/portal/apps/projects/management/commands/projects_id_unit_test.py @@ -1,8 +1,9 @@ from io import StringIO + import pytest from django.core.management import call_command -from portal.apps.projects.models.utils import get_latest_project_storage, get_latest_project_directory +from portal.apps.projects.models.utils import get_latest_project_directory, get_latest_project_storage pytestmark = pytest.mark.django_db diff --git a/server/portal/apps/projects/managers/base.py b/server/portal/apps/projects/managers/base.py index 092737959c..5c31dbc253 100644 --- a/server/portal/apps/projects/managers/base.py +++ b/server/portal/apps/projects/managers/base.py @@ -5,18 +5,19 @@ """ import logging + from django.conf import settings from django.contrib.auth import get_user_model +from django.core.exceptions import ObjectDoesNotExist + +from portal.apps.projects.models import Project, ProjectId +from portal.apps.projects.models.utils import get_latest_project_directory, get_latest_project_storage +from portal.apps.projects.serializers import MetadataJSONSerializer from portal.libs.agave.utils import service_account # TODOv3: deprecate with projects # from portal.libs.agave.models.systems.storage import StorageSystem from portal.libs.elasticsearch.docs.base import IndexedProject -from portal.apps.projects.models import Project, ProjectId -from portal.apps.projects.serializers import MetadataJSONSerializer -from portal.apps.projects.models.utils import get_latest_project_storage, get_latest_project_directory -from django.core.exceptions import ObjectDoesNotExist - # pylint: disable=invalid-name logger = logging.getLogger(__name__) @@ -24,7 +25,7 @@ # pylint: enable=invalid-name -class ProjectsManager(object): +class ProjectsManager: """Projects Manager.""" meta_serializer_cls = MetadataJSONSerializer @@ -46,7 +47,7 @@ def _add_acls(self, username, project_id, project_root): client = service_account() job = client.jobs.submit( body={ - "name": "{username}-{project_id}-acls".format(username=username, project_id=project_id), + "name": f"{username}-{project_id}-acls", "appId": settings.PORTAL_PROJECTS_PEMS_APP_ID, "archive": False, "parameters": { @@ -69,7 +70,7 @@ def _remove_acls(self, username, project_id, project_root): client = service_account() job = client.jobs.submit( body={ - "name": "{username}-{project_id}-acls".format(username=username, project_id=project_id), + "name": f"{username}-{project_id}-acls", "appId": settings.PORTAL_PROJECTS_PEMS_APP_ID, "archive": False, "parameters": { @@ -151,29 +152,27 @@ def create(self, title): ProjectId.objects.create(value=max_value_found) prjId = ProjectId.next_id() - project_id = "{prefix}-{prjId}".format(prefix=settings.PORTAL_PROJECTS_ID_PREFIX, prjId=prjId) + project_id = f"{settings.PORTAL_PROJECTS_ID_PREFIX}-{prjId}" try: prj = Project.create(self.user.tapis_oauth.client, title, project_id, self.user) except ValueError: # Tapis StorageSystem or ProjectMetadata with this ProjectID already exists, # try to update to latest project value and recreate - logger.info("Project with id: {} already exists".format(project_id)) + logger.info(f"Project with id: {project_id} already exists") latest_storage_system_id = get_latest_project_storage() latest_project_id = get_latest_project_directory() max_value_found = max(latest_storage_system_id, latest_project_id, 0) - logger.info("Updating ProjectId to latest project dir or storage system id: {}".format(max_value_found)) + logger.info(f"Updating ProjectId to latest project dir or storage system id: {max_value_found}") ProjectId.update(max_value_found) - project_id = "{prefix}-{prj_id}".format( - prefix=settings.PORTAL_PROJECTS_ID_PREFIX, prj_id=ProjectId.next_id() - ) + project_id = f"{settings.PORTAL_PROJECTS_ID_PREFIX}-{ProjectId.next_id()}" prj = Project.create(self.user.tapis_oauth.client, title, project_id, self.user) prj.storage.update_role(self.user.username, "ADMIN") - METRICS.info("user:{} created project: id={}, title:{}".format(self.user.username, project_id, title)) + METRICS.info(f"user:{self.user.username} created project: id={project_id}, title:{title}") return prj diff --git a/server/portal/apps/projects/managers/unit_test.py b/server/portal/apps/projects/managers/unit_test.py index f4abeb3e8c..8b193ff479 100644 --- a/server/portal/apps/projects/managers/unit_test.py +++ b/server/portal/apps/projects/managers/unit_test.py @@ -6,10 +6,12 @@ import logging import os + +import pytest from django.conf import settings + from portal.apps.projects.managers.base import ProjectsManager from portal.apps.projects.models.base import ProjectId -import pytest LOGGER = logging.getLogger(__name__) diff --git a/server/portal/apps/projects/migrations/0001_initial.py b/server/portal/apps/projects/migrations/0001_initial.py index 54172f8d31..205965615f 100644 --- a/server/portal/apps/projects/migrations/0001_initial.py +++ b/server/portal/apps/projects/migrations/0001_initial.py @@ -1,8 +1,8 @@ # Generated by Django 2.2.16 on 2020-11-12 20:02 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/server/portal/apps/projects/migrations/0003_alter_abstractprojectmetadata_co_pis_and_more.py b/server/portal/apps/projects/migrations/0003_alter_abstractprojectmetadata_co_pis_and_more.py index 6d7c9e212e..265c635d0f 100644 --- a/server/portal/apps/projects/migrations/0003_alter_abstractprojectmetadata_co_pis_and_more.py +++ b/server/portal/apps/projects/migrations/0003_alter_abstractprojectmetadata_co_pis_and_more.py @@ -1,8 +1,8 @@ # Generated by Django 4.2.10 on 2024-03-19 16:38 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/server/portal/apps/projects/migrations/0004_projectsmetadata_squashed_0008_delete_projectsmetadata.py b/server/portal/apps/projects/migrations/0004_projectsmetadata_squashed_0008_delete_projectsmetadata.py index 8006a92e2d..1930d89ee9 100644 --- a/server/portal/apps/projects/migrations/0004_projectsmetadata_squashed_0008_delete_projectsmetadata.py +++ b/server/portal/apps/projects/migrations/0004_projectsmetadata_squashed_0008_delete_projectsmetadata.py @@ -4,9 +4,10 @@ import django.core.validators import django.db.models.deletion import django.utils.timezone -import portal.apps.projects.models.project_metadata from django.conf import settings from django.db import migrations, models + +import portal.apps.projects.models.project_metadata from portal.apps.projects.schema_models import constants diff --git a/server/portal/apps/projects/models/base.py b/server/portal/apps/projects/models/base.py index 754ba957ef..bef36c4e1b 100644 --- a/server/portal/apps/projects/models/base.py +++ b/server/portal/apps/projects/models/base.py @@ -14,21 +14,22 @@ import logging import os -from django.db import models, transaction -from django.core.exceptions import ObjectDoesNotExist + from django.conf import settings from django.contrib.auth import get_user_model -from portal.utils import encryption as EncryptionUtil -from portal.libs.agave.utils import service_account +from django.core.exceptions import ObjectDoesNotExist +from django.db import models, transaction + +from portal.apps.accounts.models import SSHKeys # TODOv3: deprecate with projects # from portal.libs.agave.models.systems.storage import StorageSystem # from portal.libs.agave.serializers import BaseAgaveSystemSerializer from portal.apps.projects import utils as ProjectsUtils -from portal.apps.projects.models.metadata import LegacyProjectMetadata from portal.apps.projects.exceptions import NotAuthorizedError -from portal.apps.accounts.models import SSHKeys - +from portal.apps.projects.models.metadata import LegacyProjectMetadata +from portal.libs.agave.utils import service_account +from portal.utils import encryption as EncryptionUtil # pylint: disable=invalid-name logger = logging.getLogger(__name__) @@ -62,7 +63,7 @@ def set_storage_auth(storage): return storage -class Project(object): +class Project: """Project class.""" metadata_name = settings.PORTAL_PROJECTS_SYSTEM_PREFIX @@ -420,8 +421,8 @@ def change_project_role(self, user, old_role, new_role): old_role = "member" if new_role == "team_member": new_role = "member" - add_new_role = getattr(self, "add_{}".format(new_role)) - remove_old_role = getattr(self, "remove_{}".format(old_role)) + add_new_role = getattr(self, f"add_{new_role}") + remove_old_role = getattr(self, f"remove_{old_role}") remove_old_role(user) add_new_role(user) @@ -478,9 +479,7 @@ def change_storage_system_role(self, user, new_role): def __repr__(self): """Repr.""" - return "Project({project_id}, {metadata}, {storage})".format( - project_id=self.project_id, metadata=self.metadata, storage=self.storage - ) + return f"Project({self.project_id}, {self.metadata}, {self.storage})" def __str__(self): """Str -> self.project_id.""" diff --git a/server/portal/apps/projects/models/metadata.py b/server/portal/apps/projects/models/metadata.py index a7ec0b2ede..a82913b192 100644 --- a/server/portal/apps/projects/models/metadata.py +++ b/server/portal/apps/projects/models/metadata.py @@ -5,6 +5,7 @@ """ import logging + from django.conf import settings from django.db import models @@ -71,7 +72,7 @@ class AbstractProjectMetadata(models.Model): def __str__(self): """Str -> self.prj_id - self.title.""" - return "{prj_id} - {title}".format(prj_id=self.project_id, title=self.title) + return f"{self.project_id} - {self.title}" class LegacyProjectMetadata(AbstractProjectMetadata): diff --git a/server/portal/apps/projects/models/project_metadata.py b/server/portal/apps/projects/models/project_metadata.py index c3b24851bb..e7c23a4959 100644 --- a/server/portal/apps/projects/models/project_metadata.py +++ b/server/portal/apps/projects/models/project_metadata.py @@ -1,11 +1,13 @@ """Models for representing project metadata""" import uuid -from django.utils import timezone -from django.db import models -from django.core.validators import MinLengthValidator -from django.core.serializers.json import DjangoJSONEncoder + from django.contrib.auth import get_user_model +from django.core.serializers.json import DjangoJSONEncoder +from django.core.validators import MinLengthValidator +from django.db import models +from django.utils import timezone + from portal.apps.projects.schema_models import constants user_model = get_user_model() diff --git a/server/portal/apps/projects/models/unit_test.py b/server/portal/apps/projects/models/unit_test.py index 8555e157ff..9980da91c0 100644 --- a/server/portal/apps/projects/models/unit_test.py +++ b/server/portal/apps/projects/models/unit_test.py @@ -4,14 +4,14 @@ :synopsis: Projects app unit tests. """ -from portal.apps.projects.models.metadata import LegacyProjectMetadata -from portal.apps.projects.models.base import Project -from portal.apps.projects.models.utils import get_latest_project_storage - # TODOv3: deprecate with projects # from portal.libs.agave.models.systems.storage import StorageSystem import pytest +from portal.apps.projects.models.base import Project +from portal.apps.projects.models.metadata import LegacyProjectMetadata +from portal.apps.projects.models.utils import get_latest_project_storage + @pytest.fixture() def agave_client(mocker): diff --git a/server/portal/apps/projects/models/utils.py b/server/portal/apps/projects/models/utils.py index 73866e8c77..cdeac29413 100644 --- a/server/portal/apps/projects/models/utils.py +++ b/server/portal/apps/projects/models/utils.py @@ -1,7 +1,8 @@ from django.conf import settings -from portal.libs.agave.utils import service_account -from portal.libs.agave.operations import iterate_listing + from portal.apps.projects.models.base import Project +from portal.libs.agave.operations import iterate_listing +from portal.libs.agave.utils import service_account def get_latest_project_storage(max_project_id=None): diff --git a/server/portal/apps/projects/schema_models/base_metadata.py b/server/portal/apps/projects/schema_models/base_metadata.py index 92a0a68a09..44de617d16 100644 --- a/server/portal/apps/projects/schema_models/base_metadata.py +++ b/server/portal/apps/projects/schema_models/base_metadata.py @@ -1,7 +1,7 @@ """Portal-agnostic base Pydantic models for the project metadata graph.""" from functools import partial -from typing import Literal, Optional +from typing import Literal from pydantic import BaseModel, ConfigDict from pydantic.alias_generators import to_camel @@ -29,7 +29,7 @@ class BaseFileMetadata(BaseMetadataModel): model_config = ConfigDict(extra="ignore") data_type: Literal["file"] - name: Optional[str] = None + name: str | None = None class FileObj(BaseMetadataModel): @@ -38,12 +38,12 @@ class FileObj(BaseMetadataModel): system: str name: str path: str - legacy_path: Optional[str] = None + legacy_path: str | None = None type: Literal["file", "dir"] - length: Optional[int] = None - last_modified: Optional[str] = None - uuid: Optional[str] = None - value: Optional[dict] = None + length: int | None = None + last_modified: str | None = None + uuid: str | None = None + value: dict | None = None class PartialTrashEntity(BaseMetadataModel): @@ -64,7 +64,7 @@ class ProjectRelatedDatasets(BaseMetadataModel): """A dataset related to a project.""" dataset_title: str - dataset_description: Optional[str] = None + dataset_description: str | None = None dataset_link: str = "" @@ -82,11 +82,11 @@ class ProjectRelatedPublications(BaseMetadataModel): publication_type: Literal["context", "linked_dataset", "cited_by"] publication_title: str publication_link: str - publication_author: Optional[str] = None - publication_doi: Optional[str] = None - publication_date_of_publication: Optional[str] = None - publication_publisher: Optional[str] = None - publication_description: Optional[str] = None + publication_author: str | None = None + publication_doi: str | None = None + publication_date_of_publication: str | None = None + publication_publisher: str | None = None + publication_description: str | None = None class GuestUser(BaseMetadataModel): @@ -108,17 +108,17 @@ class BaseProjectMetadata(BaseMetadataModel): project_id: str title: str description: str = "" - license: Optional[str] = None - doi: Optional[str] = None - institution: Optional[str] = None - keywords: Optional[str | list[str]] = None + license: str | None = None + doi: str | None = None + institution: str | None = None + keywords: str | list[str] | None = None related_datasets: list[ProjectRelatedDatasets] = [] related_software: list[ProjectRelatedSoftware] = [] related_publications: list[ProjectRelatedPublications] = [] - publication_date: Optional[str] = None + publication_date: str | None = None authors: list[dict] = [] file_objs: list[FileObj] = [] - is_review_project: Optional[bool] = None - is_published_project: Optional[bool] = None + is_review_project: bool | None = None + is_published_project: bool | None = None guest_users: list[GuestUser] = [] - cover_image: Optional[str] = None + cover_image: str | None = None diff --git a/server/portal/apps/projects/schema_models/schema.py b/server/portal/apps/projects/schema_models/schema.py index ae93db258b..1a99299a3f 100644 --- a/server/portal/apps/projects/schema_models/schema.py +++ b/server/portal/apps/projects/schema_models/schema.py @@ -7,8 +7,8 @@ from portal.apps.projects.schema_models import constants from portal.apps.projects.schema_models.base_metadata import ( - BaseProjectMetadata, BaseFileMetadata, + BaseProjectMetadata, PartialTrashEntity, ) diff --git a/server/portal/apps/projects/serializers.py b/server/portal/apps/projects/serializers.py index c2fcb8b532..3fc55498f0 100644 --- a/server/portal/apps/projects/serializers.py +++ b/server/portal/apps/projects/serializers.py @@ -4,10 +4,12 @@ :synopsis: Serializer classes for project objects. """ -import logging import datetime import json +import logging + from django.contrib.auth import get_user_model + from portal.apps.projects.models.metadata import LegacyProjectMetadata from portal.libs.agave.utils import to_camel_case @@ -61,4 +63,4 @@ def default(self, obj): # pylint: disable=method-hidden, arguments-differ else: ret[attname] = field.value_to_string(obj) return ret - return super(MetadataJSONSerializer, self).default(obj) + return super().default(obj) diff --git a/server/portal/apps/projects/tasks.py b/server/portal/apps/projects/tasks.py index a0e433914a..1dd3b316f7 100644 --- a/server/portal/apps/projects/tasks.py +++ b/server/portal/apps/projects/tasks.py @@ -1,20 +1,23 @@ -import os import base64 import logging +import os from pathlib import Path + from celery import shared_task from django.db import transaction -from portal.libs.agave.utils import user_account + +from portal.apps.notifications.models import Notification from portal.apps.projects.models.project_metadata import ProjectMetadata from portal.apps.projects.schema_models import constants +from portal.apps.projects.schema_models.base_metadata import FileObj +from portal.apps.projects.workspace_operations.graph_operations import get_path_uuid_mapping from portal.apps.projects.workspace_operations.project_meta_operations import ( add_file_associations, create_file_obj, get_file_obj, get_ordered_value, ) -from portal.apps.projects.workspace_operations.graph_operations import get_path_uuid_mapping -from portal.apps.projects.schema_models.base_metadata import FileObj +from portal.libs.agave.utils import user_account from portal.libs.files.file_processing import ( binary_correction, conf_raw, @@ -23,7 +26,6 @@ create_histogram, create_thumbnail, ) -from portal.apps.notifications.models import Notification logger = logging.getLogger(__name__) diff --git a/server/portal/apps/projects/urls.py b/server/portal/apps/projects/urls.py index 6d1c6b6a14..df46cf4a8a 100644 --- a/server/portal/apps/projects/urls.py +++ b/server/portal/apps/projects/urls.py @@ -1,8 +1,9 @@ """Data Depot API Urls""" -from portal.apps.projects import views from django.urls import path +from portal.apps.projects import views + app_name = "projects" urlpatterns = [ path("system//", views.ProjectInstanceApiView.as_view(), name="project_sys"), diff --git a/server/portal/apps/projects/utils.py b/server/portal/apps/projects/utils.py index 2d5132cd77..1f8fa9efe4 100644 --- a/server/portal/apps/projects/utils.py +++ b/server/portal/apps/projects/utils.py @@ -4,9 +4,11 @@ """ import logging + from django.conf import settings + +from portal.libs.agave.operations import delete, mkdir from portal.libs.agave.utils import service_account -from portal.libs.agave.operations import mkdir, delete # pylint: disable=invalid-name logger = logging.getLogger(__name__) @@ -38,4 +40,4 @@ def project_id_to_system_id(project_id): :param str project_id: Project Id. """ - return "{prefix}.{prj_id}".format(prefix=settings.PORTAL_PROJECTS_SYSTEM_PREFIX, prj_id=project_id) + return f"{settings.PORTAL_PROJECTS_SYSTEM_PREFIX}.{project_id}" diff --git a/server/portal/apps/projects/views.py b/server/portal/apps/projects/views.py index f91fa0080d..9f9036f02b 100644 --- a/server/portal/apps/projects/views.py +++ b/server/portal/apps/projects/views.py @@ -6,36 +6,28 @@ import json import logging -from django.http import HttpRequest, JsonResponse from hashlib import sha256 -from django.contrib.auth.decorators import login_required +from pathlib import Path + from django.conf import settings +from django.contrib.auth.decorators import login_required +from django.db import models, transaction +from django.http import HttpRequest, JsonResponse +from django.http.multipartparser import MultiPartParser from django.utils.decorators import method_decorator -from portal.libs.agave.utils import service_account -from portal.utils import check_group_membership, get_client_ip -from portal.utils.decorators import agave_jwt_login -from portal.exceptions.api import ApiException -from portal.views.base import BaseApiView -from portal.apps.projects.managers.base import ProjectsManager -from portal.apps.projects.workspace_operations.shared_workspace_operations import ( - list_projects, - get_project, - create_shared_workspace, - update_project, - get_workspace_role, - change_user_role, - add_user_to_workspace, - remove_user, - transfer_ownership, - increment_workspace_count, -) -from portal.apps.search.tasks import tapis_project_listing_indexer -from portal.libs.elasticsearch.indexes import IndexedProject from elasticsearch_dsl import Q + +from portal.apps.projects.managers.base import ProjectsManager from portal.apps.projects.models.project_metadata import ProjectMetadata -from django.db import transaction +from portal.apps.projects.schema_models import constants from portal.apps.projects.schema_models.schema import SCHEMA_MAPPING -from django.db import models +from portal.apps.projects.tasks import sync_files_without_metadata +from portal.apps.projects.workspace_operations.graph_operations import ( + add_node_to_project, + build_project_tree, + get_node_from_path, + initialize_project_graph, +) from portal.apps.projects.workspace_operations.project_meta_operations import ( create_entity_metadata, create_project_metadata, @@ -45,18 +37,27 @@ patch_file_obj_entity, patch_project_entity, ) -from portal.libs.agave.operations import mkdir -from pathlib import Path -from portal.apps.projects.schema_models import constants -from portal.apps.projects.workspace_operations.graph_operations import ( - add_node_to_project, - initialize_project_graph, - get_node_from_path, - build_project_tree, +from portal.apps.projects.workspace_operations.shared_workspace_operations import ( + add_user_to_workspace, + change_user_role, + create_shared_workspace, + get_project, + get_workspace_role, + increment_workspace_count, + list_projects, + remove_user, + transfer_ownership, + update_project, ) -from portal.apps.projects.tasks import sync_files_without_metadata +from portal.apps.search.tasks import tapis_project_listing_indexer +from portal.exceptions.api import ApiException +from portal.libs.agave.operations import mkdir +from portal.libs.agave.utils import service_account +from portal.libs.elasticsearch.indexes import IndexedProject from portal.libs.files.file_processing import resize_cover_image -from django.http.multipartparser import MultiPartParser +from portal.utils import check_group_membership, get_client_ip +from portal.utils.decorators import agave_jwt_login +from portal.views.base import BaseApiView LOGGER = logging.getLogger(__name__) METRICS = logging.getLogger(f"metrics.{__name__}") diff --git a/server/portal/apps/projects/views_unit_test.py b/server/portal/apps/projects/views_unit_test.py index a90e5b87a9..b322a801dc 100644 --- a/server/portal/apps/projects/views_unit_test.py +++ b/server/portal/apps/projects/views_unit_test.py @@ -1,18 +1,20 @@ -import pytest +import json from hashlib import sha256 +from unittest.mock import MagicMock + +import pytest +from django.conf import settings +from django.contrib.auth.models import Group +from django.test import override_settings +from django.test.client import BOUNDARY, MULTIPART_CONTENT, encode_multipart +from tapipy.tapis import TapisResult + from portal.apps.projects.managers.base import ProjectsManager from portal.apps.projects.models.project_metadata import ProjectMetadata +from portal.apps.projects.schema_models import constants from portal.apps.projects.views import get_project_client, get_project_for_user from portal.apps.search.tasks import tapis_project_listing_indexer from portal.libs.elasticsearch.indexes import IndexedProject -from mock import MagicMock -import json -from tapipy.tapis import TapisResult -from django.conf import settings -from django.contrib.auth.models import Group -from django.test import override_settings -from django.test.client import encode_multipart, BOUNDARY, MULTIPART_CONTENT -from portal.apps.projects.schema_models import constants @pytest.fixture diff --git a/server/portal/apps/projects/workspace_operations/datacite_operations.py b/server/portal/apps/projects/workspace_operations/datacite_operations.py index 4435d632f9..875f3f24ad 100644 --- a/server/portal/apps/projects/workspace_operations/datacite_operations.py +++ b/server/portal/apps/projects/workspace_operations/datacite_operations.py @@ -1,8 +1,8 @@ import datetime -from typing import Optional import json -import requests + import networkx as nx +import requests from django.conf import settings @@ -119,7 +119,7 @@ def get_datacite_json(pub_graph: nx.DiGraph): return datacite_json -def upsert_datacite_json(datacite_json: dict, doi: Optional[str] = None): +def upsert_datacite_json(datacite_json: dict, doi: str | None = None): """ Create a draft DOI in datacite with the specified metadata. If a DOI is specified, the metadata for that DOI is updated instead. diff --git a/server/portal/apps/projects/workspace_operations/graph_operations.py b/server/portal/apps/projects/workspace_operations/graph_operations.py index a9a2ac5114..0dc8f13cac 100644 --- a/server/portal/apps/projects/workspace_operations/graph_operations.py +++ b/server/portal/apps/projects/workspace_operations/graph_operations.py @@ -1,11 +1,13 @@ -from typing import Any, Dict -import networkx as nx -from django.db import transaction -import uuid import copy +import uuid +from typing import Any + +import networkx as nx from django.conf import settings -from portal.apps.projects.schema_models import constants +from django.db import transaction + from portal.apps.projects.models.project_metadata import ProjectMetadata +from portal.apps.projects.schema_models import constants def _get_next_child_order(graph: nx.DiGraph, parent_node: str) -> int: @@ -89,7 +91,7 @@ def traverse_graph(project_graph, root_node, path_components): return {"id": current_node, **project_graph.nodes[current_node]} -def get_node_from_path(project_id: str, path: str) -> Dict[str, Any]: +def get_node_from_path(project_id: str, path: str) -> dict[str, Any]: """Return the node ID for the parent of a node with the given path.""" graph_model = ProjectMetadata.objects.get(name=constants.PROJECT_GRAPH, base_project__value__projectId=project_id) @@ -105,7 +107,7 @@ def get_node_from_path(project_id: str, path: str) -> Dict[str, Any]: return node -def get_root_node(project_id: str) -> Dict[str, Any]: +def get_root_node(project_id: str) -> dict[str, Any]: """Return the root node for a project graph.""" graph_model = ProjectMetadata.objects.get(name=constants.PROJECT_GRAPH, base_project__value__projectId=project_id) project_graph = nx.node_link_graph(graph_model.value) diff --git a/server/portal/apps/projects/workspace_operations/project_meta_operations.py b/server/portal/apps/projects/workspace_operations/project_meta_operations.py index 16e040f217..511c118119 100644 --- a/server/portal/apps/projects/workspace_operations/project_meta_operations.py +++ b/server/portal/apps/projects/workspace_operations/project_meta_operations.py @@ -1,15 +1,17 @@ import operator import os import uuid -from django.db import models, transaction -from django.conf import settings from pathlib import Path from typing import get_args + import networkx as nx -from portal.apps.projects.schema_models.schema import SCHEMA_MAPPING -from portal.apps.projects.schema_models import constants +from django.conf import settings +from django.db import models, transaction + from portal.apps.projects.models.project_metadata import ProjectMetadata -from portal.apps.projects.schema_models.base_metadata import PartialEntityWithFiles, FileObj +from portal.apps.projects.schema_models import constants +from portal.apps.projects.schema_models.base_metadata import FileObj, PartialEntityWithFiles +from portal.apps.projects.schema_models.schema import SCHEMA_MAPPING from portal.apps.projects.workspace_operations.graph_operations import ( get_node_from_path, get_node_from_uuid, @@ -71,7 +73,8 @@ def get_value(project_id, path): def get_ordered_value(name, value): """ Return the metadata in the order defined in the Pydantic model. - Also converts camelCase keys to snake_case. This is a temporary workaround until fields in settings_forms.py can be updated to use camelCase. + Also converts camelCase keys to snake_case. This is a temporary workaround until fields in settings_forms.py can be + updated to use camelCase. """ schema = SCHEMA_MAPPING.get(name) diff --git a/server/portal/apps/projects/workspace_operations/project_publish_operations.py b/server/portal/apps/projects/workspace_operations/project_publish_operations.py index f49575a043..f948637658 100644 --- a/server/portal/apps/projects/workspace_operations/project_publish_operations.py +++ b/server/portal/apps/projects/workspace_operations/project_publish_operations.py @@ -1,25 +1,26 @@ -from typing import Optional -from django.conf import settings -import logging import json +import logging from io import StringIO -from portal.apps.projects.workspace_operations.shared_workspace_operations import remove_user -from portal.apps.projects.models.project_metadata import ProjectMetadata + import networkx as nx from celery import shared_task +from django.conf import settings +from django.contrib.auth import get_user_model +from django.core.mail import send_mail +from django.db import transaction + +from portal.apps.projects.models.project_metadata import ProjectMetadata from portal.apps.projects.schema_models import constants -from portal.libs.agave.utils import user_account, service_account -from portal.apps.publications.models import Publication, PublicationRequest from portal.apps.projects.workspace_operations.datacite_operations import ( get_datacite_json, - upsert_datacite_json, publish_datacite_doi, + upsert_datacite_json, ) -from django.db import transaction from portal.apps.projects.workspace_operations.graph_operations import remove_trash_nodes +from portal.apps.projects.workspace_operations.shared_workspace_operations import remove_user +from portal.apps.publications.models import Publication, PublicationRequest from portal.apps.search.tasks import index_publication -from django.contrib.auth import get_user_model -from django.core.mail import send_mail +from portal.libs.agave.utils import service_account, user_account logger = logging.getLogger(__name__) @@ -111,7 +112,9 @@ def publication_request_callback( with transaction.atomic(): # Commented out cleanup to prevent breaking admin role functionality # user_client.systems.unShareSystem(systemId=source_system_id, users=[portal_admin_username]) - # user_client.systems.revokeUserPerms(systemId=source_system_id, userName=portal_admin_username, permissions=["READ", "MODIFY", "EXECUTE"]) + # user_client.systems.revokeUserPerms( + # systemId=source_system_id, userName=portal_admin_username, permissions=["READ", "MODIFY", "EXECUTE"] + # ) # user_client.files.deletePermissions(systemId=source_system_id, username=portal_admin_username, path="/") # logger.info(f'Removed service account from workspace {source_workspace_id}') @@ -190,7 +193,7 @@ def archive_publication_files(project_id: str): @shared_task(bind=True, max_retries=3, queue="default") -def publish_project(self, project_id: str, version: Optional[int] = 1): +def publish_project(self, project_id: str, version: int | None = 1): review_system_prefix = settings.PORTAL_PROJECTS_REVIEW_SYSTEM_PREFIX published_system_prefix = settings.PORTAL_PROJECTS_PUBLISHED_SYSTEM_PREFIX diff --git a/server/portal/apps/projects/workspace_operations/shared_workspace_migration.py b/server/portal/apps/projects/workspace_operations/shared_workspace_migration.py index b91dc790e5..36f0f9d40e 100644 --- a/server/portal/apps/projects/workspace_operations/shared_workspace_migration.py +++ b/server/portal/apps/projects/workspace_operations/shared_workspace_migration.py @@ -4,18 +4,18 @@ import requests from django.conf import settings +from django.core.exceptions import MultipleObjectsReturned +from tapipy.errors import BaseTapyException, NotFoundError + from portal.apps.projects.models import LegacyProjectMetadata from portal.apps.projects.workspace_operations.shared_workspace_operations import ( - create_workspace_system, add_user_to_workspace, + create_workspace_system, ) from portal.libs.agave.utils import service_account - +from portal.settings import settings_custom from portal.settings.settings_secret import _AGAVE_SUPER_TOKEN as v2_token from portal.settings.settings_secret import _AGAVE_TENANT_BASEURL as v2_url -from portal.settings import settings_custom -from django.core.exceptions import MultipleObjectsReturned -from tapipy.errors import NotFoundError, BaseTapyException ROLE_MAP = { "USER": "writer", @@ -26,8 +26,11 @@ def get_role(project_id, username): - system_id = f"{getattr(settings_custom, '_PORTAL_PROJECTS_SYSTEM_PREFIX_V2', settings.PORTAL_PROJECTS_SYSTEM_PREFIX)}.{project_id}" - headers = {"Authorization": "Bearer {}".format(v2_token)} + system_prefix = getattr( + settings_custom, "_PORTAL_PROJECTS_SYSTEM_PREFIX_V2", settings.PORTAL_PROJECTS_SYSTEM_PREFIX + ) + system_id = f"{system_prefix}.{project_id}" + headers = {"Authorization": f"Bearer {v2_token}"} req = requests.get(f"{v2_url}/systems/v2/{system_id}/roles/{username}", headers=headers) if req.status_code != 200: return None diff --git a/server/portal/apps/projects/workspace_operations/shared_workspace_operations.py b/server/portal/apps/projects/workspace_operations/shared_workspace_operations.py index c6bcb1023c..80bd9aa984 100644 --- a/server/portal/apps/projects/workspace_operations/shared_workspace_operations.py +++ b/server/portal/apps/projects/workspace_operations/shared_workspace_operations.py @@ -1,15 +1,16 @@ # from portal.utils.encryption import createKeyPair -from portal.libs.agave.utils import service_account -from portal.apps.projects.models.project_metadata import ProjectMetadata -from portal.apps.projects.schema_models import constants -from tapipy.tapis import Tapis +import logging from typing import Literal -from django.db import transaction + from django.conf import settings from django.contrib.auth import get_user_model -from portal.apps.projects.workspace_operations.project_meta_operations import create_project_metadata, get_ordered_value +from django.db import transaction +from tapipy.tapis import Tapis -import logging +from portal.apps.projects.models.project_metadata import ProjectMetadata +from portal.apps.projects.schema_models import constants +from portal.apps.projects.workspace_operations.project_meta_operations import create_project_metadata, get_ordered_value +from portal.libs.agave.utils import service_account logger = logging.getLogger(__name__) diff --git a/server/portal/apps/public_data/urls.py b/server/portal/apps/public_data/urls.py index 96229804d6..15b1941444 100644 --- a/server/portal/apps/public_data/urls.py +++ b/server/portal/apps/public_data/urls.py @@ -4,8 +4,10 @@ """ import re -from django.urls import re_path + from django.conf import settings +from django.urls import re_path + from portal.apps.public_data.views import IndexView app_name = "public_data" diff --git a/server/portal/apps/public_data/views.py b/server/portal/apps/public_data/views.py index 60c651f79a..5e96bdd450 100644 --- a/server/portal/apps/public_data/views.py +++ b/server/portal/apps/public_data/views.py @@ -1,10 +1,12 @@ import json import logging + import networkx as nx -from django.views.generic.base import TemplateView from django.conf import settings -from portal.apps.publications.models import Publication +from django.views.generic.base import TemplateView + from portal.apps.projects.workspace_operations.datacite_operations import get_datacite_json +from portal.apps.publications.models import Publication logger = logging.getLogger(__name__) @@ -40,7 +42,7 @@ class IndexView(TemplateView): template_name = "portal/apps/workbench/index.html" def get_context_data(self, **kwargs): - context = super(IndexView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) project_id = kwargs.get("project_id") if project_id: try: @@ -61,4 +63,4 @@ def get_context_data(self, **kwargs): return context def dispatch(self, request, *args, **kwargs): - return super(IndexView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) diff --git a/server/portal/apps/publications/models.py b/server/portal/apps/publications/models.py index 3c4caf8d56..5b90394894 100644 --- a/server/portal/apps/publications/models.py +++ b/server/portal/apps/publications/models.py @@ -5,11 +5,13 @@ """ import logging + from django.conf import settings +from django.core.serializers.json import DjangoJSONEncoder from django.db import models from django.utils import timezone + from portal.apps.projects.models.project_metadata import ProjectMetadata -from django.core.serializers.json import DjangoJSONEncoder # pylint: disable=invalid-name logger = logging.getLogger(__name__) diff --git a/server/portal/apps/publications/urls.py b/server/portal/apps/publications/urls.py index ee71a5bfcf..af861b6cc5 100644 --- a/server/portal/apps/publications/urls.py +++ b/server/portal/apps/publications/urls.py @@ -1,8 +1,9 @@ """Publications API Urls""" -from portal.apps.publications import views from django.urls import path +from portal.apps.publications import views + app_name = "publications_api" urlpatterns = [ path("publication-request/", views.PublicationRequestView.as_view(), name="publication_request"), diff --git a/server/portal/apps/publications/views.py b/server/portal/apps/publications/views.py index cf83b194b0..e98e7f2f38 100644 --- a/server/portal/apps/publications/views.py +++ b/server/portal/apps/publications/views.py @@ -6,30 +6,31 @@ import json import logging -from django.contrib.auth.decorators import login_required + from django.conf import settings +from django.contrib.auth import get_user_model +from django.contrib.auth.decorators import login_required +from django.core.exceptions import ObjectDoesNotExist +from django.db import models, transaction from django.http import JsonResponse from django.utils.decorators import method_decorator -from portal.exceptions.api import ApiException -from portal.views.base import BaseApiView -from portal.apps.projects.workspace_operations.shared_workspace_operations import create_publication_workspace +from elasticsearch_dsl import Q + +from portal.apps.notifications.models import Notification +from portal.apps.projects.models.project_metadata import ProjectMetadata +from portal.apps.projects.views import get_project_for_user from portal.apps.projects.workspace_operations.project_publish_operations import ( copy_graph_and_files_for_review_system, publish_project, - update_and_cleanup_review_project, send_publication_rejected_email_to_authors, send_publication_reviewed_email_to_reviewers, + update_and_cleanup_review_project, ) -from django.db import transaction -from portal.apps.notifications.models import Notification +from portal.apps.projects.workspace_operations.shared_workspace_operations import create_publication_workspace from portal.apps.publications.models import Publication, PublicationRequest -from portal.apps.projects.models.project_metadata import ProjectMetadata -from portal.apps.projects.views import get_project_for_user -from django.db import models -from django.core.exceptions import ObjectDoesNotExist -from django.contrib.auth import get_user_model +from portal.exceptions.api import ApiException from portal.libs.elasticsearch.docs.base import IndexedPublication -from elasticsearch_dsl import Q +from portal.views.base import BaseApiView logger = logging.getLogger(__name__) diff --git a/server/portal/apps/request_access/api/unit_test.py b/server/portal/apps/request_access/api/unit_test.py index 19a3095d0c..87aaba88a1 100644 --- a/server/portal/apps/request_access/api/unit_test.py +++ b/server/portal/apps/request_access/api/unit_test.py @@ -1,6 +1,7 @@ -import pytest import json import os + +import pytest from django.conf import settings diff --git a/server/portal/apps/request_access/api/urls.py b/server/portal/apps/request_access/api/urls.py index 3360ba9855..774adfce04 100644 --- a/server/portal/apps/request_access/api/urls.py +++ b/server/portal/apps/request_access/api/urls.py @@ -1,4 +1,5 @@ from django.urls import path + from portal.apps.request_access.api import views app_name = "request_access_api" diff --git a/server/portal/apps/request_access/api/views.py b/server/portal/apps/request_access/api/views.py index 651f35b0cd..8c12e030f1 100644 --- a/server/portal/apps/request_access/api/views.py +++ b/server/portal/apps/request_access/api/views.py @@ -1,9 +1,11 @@ import logging -from django.http import JsonResponse, HttpResponseBadRequest + from django.conf import settings +from django.http import HttpResponseBadRequest, JsonResponse +from pytas.http import TASClient + from portal.apps.tickets import utils from portal.views.base import BaseApiView -from pytas.http import TASClient logger = logging.getLogger(__name__) @@ -32,7 +34,7 @@ def post(self, request): else: return JsonResponse({"message": "Incorrect password"}, status=401) except Exception as e: - logger.error("Incorrect password for user: {user}. {exc}".format(user=username, exc=e)) + logger.error(f"Incorrect password for user: {username}. {e}") return JsonResponse({"message": "Incorrect password"}, status=401) if email is None or problem_description is None: diff --git a/server/portal/apps/request_access/urls.py b/server/portal/apps/request_access/urls.py index 724dbdf2ca..a6d2a4b54a 100644 --- a/server/portal/apps/request_access/urls.py +++ b/server/portal/apps/request_access/urls.py @@ -1,4 +1,5 @@ from django.urls import path + from portal.apps.request_access.views import IndexView app_name = "request_access" diff --git a/server/portal/apps/request_access/views.py b/server/portal/apps/request_access/views.py index 7001e55b2b..46dfb9336f 100644 --- a/server/portal/apps/request_access/views.py +++ b/server/portal/apps/request_access/views.py @@ -1,6 +1,6 @@ -from django.views.generic.base import TemplateView -from django.shortcuts import redirect from django.conf import settings +from django.shortcuts import redirect +from django.views.generic.base import TemplateView class IndexView(TemplateView): @@ -15,9 +15,9 @@ def dispatch(self, request, *args, **kwargs): if request.user.is_authenticated: return redirect("/workbench/dashboard/") - return super(IndexView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): - context = super(IndexView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context["DEBUG"] = settings.DEBUG return context diff --git a/server/portal/apps/search/management/commands/reindex-files.py b/server/portal/apps/search/management/commands/reindex-files.py index f421d3f75a..590b298595 100644 --- a/server/portal/apps/search/management/commands/reindex-files.py +++ b/server/portal/apps/search/management/commands/reindex-files.py @@ -1,8 +1,9 @@ -from django.core.management import BaseCommand -from django.conf import settings import elasticsearch +from django.conf import settings +from django.core.management import BaseCommand from elasticsearch_dsl import Index from elasticsearch_dsl.connections import connections # noqa: F401 + from portal.libs.elasticsearch.indexes import setup_files_index @@ -44,9 +45,8 @@ def handle(self, *args, **options): if not swap_only: confirm = input( - 'This will delete any documents in the index "{}" and recreate the index. Continue? (Y/n) '.format( - reindex_index_alias - ) + f'This will delete any documents in the index "{reindex_index_alias}" and recreate the index. ' + "Continue? (Y/n) " ) if confirm != "Y": self.stdout.write("Aborting reindex.") diff --git a/server/portal/apps/search/management/commands/unit_test.py b/server/portal/apps/search/management/commands/unit_test.py index d4e3597a88..e04d696a9f 100644 --- a/server/portal/apps/search/management/commands/unit_test.py +++ b/server/portal/apps/search/management/commands/unit_test.py @@ -1,6 +1,7 @@ -from mock import patch, MagicMock -from django.test import TestCase +from unittest.mock import MagicMock, patch + from django.core.management import call_command +from django.test import TestCase class TestSwapReindex(TestCase): diff --git a/server/portal/apps/search/tasks.py b/server/portal/apps/search/tasks.py index 4ec1e44756..bb94b86295 100644 --- a/server/portal/apps/search/tasks.py +++ b/server/portal/apps/search/tasks.py @@ -1,11 +1,13 @@ import logging -from django.conf import settings + from celery import shared_task -from portal.libs.agave.utils import user_account, service_account -from portal.libs.elasticsearch.utils import index_listing, index_project_listing +from django.conf import settings +from elasticsearch.exceptions import NotFoundError + from portal.apps.projects.models.metadata import LegacyProjectMetadata +from portal.libs.agave.utils import service_account, user_account from portal.libs.elasticsearch.docs.base import IndexedProject, IndexedPublication -from elasticsearch.exceptions import NotFoundError +from portal.libs.elasticsearch.utils import index_listing, index_project_listing logger = logging.getLogger(__name__) @@ -28,8 +30,8 @@ def tapis_indexer( ): return - from portal.libs.elasticsearch.utils import index_level from portal.libs.agave.utils import walk_levels + from portal.libs.elasticsearch.utils import index_level client = user_account(access_token) if access_token else service_account() @@ -40,7 +42,7 @@ def tapis_indexer( filePath, folders, files = walk_levels(client, systemId, filePath, ignore_hidden=ignore_hidden).__next__() index_level(filePath, folders, files, systemId, reindex=reindex) except Exception as exc: - logger.error("Error walking files under system {} and path {}".format(systemId, filePath)) + logger.error(f"Error walking files under system {systemId} and path {filePath}") raise self.retry(exc=exc) if recurse: diff --git a/server/portal/apps/signals/receivers.py b/server/portal/apps/signals/receivers.py index 383a76a19e..10748393f5 100644 --- a/server/portal/apps/signals/receivers.py +++ b/server/portal/apps/signals/receivers.py @@ -1,15 +1,17 @@ -from django.dispatch import receiver -from portal.apps.signals.signals import portal_event +import copy +import logging + +from asgiref.sync import async_to_sync +from channels.layers import get_channel_layer +from django.contrib.auth import get_user_model from django.db.models.signals import post_save +from django.dispatch import receiver + from portal.apps.notifications.models import Notification from portal.apps.onboarding.models import SetupEvent from portal.apps.projects.models.metadata import LegacyProjectMetadata from portal.apps.search.tasks import index_project -from django.contrib.auth import get_user_model -import logging -import copy -from asgiref.sync import async_to_sync -from channels.layers import get_channel_layer +from portal.apps.signals.signals import portal_event channel_layer = get_channel_layer() diff --git a/server/portal/apps/site_search/api/unit_test.py b/server/portal/apps/site_search/api/unit_test.py index ab71870ced..0a9c228293 100644 --- a/server/portal/apps/site_search/api/unit_test.py +++ b/server/portal/apps/site_search/api/unit_test.py @@ -1,6 +1,7 @@ -from mock import MagicMock -from tapipy.errors import BaseTapyException +from unittest.mock import MagicMock + import pytest +from tapipy.errors import BaseTapyException @pytest.fixture diff --git a/server/portal/apps/site_search/api/urls.py b/server/portal/apps/site_search/api/urls.py index 843151754c..b7e84a8285 100644 --- a/server/portal/apps/site_search/api/urls.py +++ b/server/portal/apps/site_search/api/urls.py @@ -4,6 +4,7 @@ """ from django.urls import re_path + from portal.apps.site_search.api.views import SiteSearchApiView app_name = "site_search" diff --git a/server/portal/apps/site_search/api/views.py b/server/portal/apps/site_search/api/views.py index e2ae182a2d..0f2bf92bc4 100644 --- a/server/portal/apps/site_search/api/views.py +++ b/server/portal/apps/site_search/api/views.py @@ -1,11 +1,13 @@ +import logging + +from django.conf import settings from django.http import JsonResponse from elasticsearch_dsl import Search +from tapipy.errors import BaseTapyException + from portal.libs.agave.operations import search as search_operation -from portal.views.base import BaseApiView -from django.conf import settings from portal.libs.agave.utils import service_account -import logging -from tapipy.errors import BaseTapyException +from portal.views.base import BaseApiView logger = logging.getLogger(__name__) @@ -111,6 +113,6 @@ def _handle_tapis_ssh_exception(self, e): # in case of these error types, user is not authenticated # or does not have access do not fail the entire search # request, log the issue. - logger.exception("Error retrieving search results due to TAPIS SSH related error: {}".format(str(e))) + logger.exception(f"Error retrieving search results due to TAPIS SSH related error: {str(e)}") else: raise diff --git a/server/portal/apps/site_search/urls.py b/server/portal/apps/site_search/urls.py index 5a22c7a7de..97cb4c24d7 100644 --- a/server/portal/apps/site_search/urls.py +++ b/server/portal/apps/site_search/urls.py @@ -4,6 +4,7 @@ """ from django.urls import re_path + from portal.apps.site_search.views import IndexView app_name = "site_search" diff --git a/server/portal/apps/site_search/views.py b/server/portal/apps/site_search/views.py index 6d37d651be..6e2862a19c 100644 --- a/server/portal/apps/site_search/views.py +++ b/server/portal/apps/site_search/views.py @@ -1,5 +1,5 @@ -from django.views.generic.base import TemplateView from django.conf import settings +from django.views.generic.base import TemplateView class IndexView(TemplateView): @@ -10,10 +10,10 @@ class IndexView(TemplateView): template_name = "portal/apps/workbench/index.html" def dispatch(self, request, *args, **kwargs): - return super(IndexView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): - context = super(IndexView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context["setup_complete"] = ( False if self.request.user.is_anonymous else self.request.user.profile.setup_complete ) diff --git a/server/portal/apps/system_monitor/unit_test.py b/server/portal/apps/system_monitor/unit_test.py index 1bf320ff4b..9360d318dd 100644 --- a/server/portal/apps/system_monitor/unit_test.py +++ b/server/portal/apps/system_monitor/unit_test.py @@ -1,6 +1,7 @@ -import pytest import json import os + +import pytest from django.conf import settings from django.http import Http404 diff --git a/server/portal/apps/system_monitor/urls.py b/server/portal/apps/system_monitor/urls.py index 9e608820fc..ff4c2f8b1b 100644 --- a/server/portal/apps/system_monitor/urls.py +++ b/server/portal/apps/system_monitor/urls.py @@ -1,4 +1,5 @@ from django.urls import path + from portal.apps.system_monitor import views app_name = "system_monitor" diff --git a/server/portal/apps/system_monitor/views.py b/server/portal/apps/system_monitor/views.py index b708af2619..cbdaf974c8 100644 --- a/server/portal/apps/system_monitor/views.py +++ b/server/portal/apps/system_monitor/views.py @@ -1,10 +1,12 @@ -from django.conf import settings -from portal.views.base import BaseApiView -from django.http import JsonResponse -import requests import json import logging +import requests +from django.conf import settings +from django.http import JsonResponse + +from portal.views.base import BaseApiView + logger = logging.getLogger(__name__) @@ -41,16 +43,14 @@ def get(self, request, system_name=None): systems_json = requests.get(settings.SYSTEM_MONITOR_URL).json() for sys in requested_systems: if sys not in systems_json: - logger.info("System information for {} is missing. Assuming not operational status.".format(sys)) + logger.info(f"System information for {sys} is missing. Assuming not operational status.") systems.append(_get_unoperational_system(sys)) continue try: system = System(systems_json[sys]).to_dict() systems.append(system) except Exception: - logger.exception( - "Problem gather system information for {}: Assuming not operational status".format(sys) - ) + logger.exception(f"Problem gather system information for {sys}: Assuming not operational status") systems.append(_get_unoperational_system(sys)) return JsonResponse(systems, safe=False) diff --git a/server/portal/apps/tickets/api/unit_test.py b/server/portal/apps/tickets/api/unit_test.py index 16972b382d..2db43adff0 100644 --- a/server/portal/apps/tickets/api/unit_test.py +++ b/server/portal/apps/tickets/api/unit_test.py @@ -1,7 +1,8 @@ -import pytest +import io import json import os -import io + +import pytest from django.conf import settings @@ -171,7 +172,7 @@ def test_tickets_get_history(client, authenticated_user, mock_rtutil): assert response.status_code == 200 result = json.loads(response.content) assert len(result["ticket_history"]) == 5 - full_name = "{} {}".format(authenticated_user.first_name, authenticated_user.last_name) + full_name = f"{authenticated_user.first_name} {authenticated_user.last_name}" assert result["ticket_history"][2]["Creator"] == full_name assert result["ticket_history"][3]["Creator"] == "RT System" assert result["ticket_history"][4]["Creator"] == full_name @@ -190,7 +191,7 @@ def test_tickets_get_history_handle_service_accounts( response = client.get("/api/tickets/1/history") assert response.status_code == 200 result = json.loads(response.content) - full_name = "{} {}".format(authenticated_user.first_name, authenticated_user.last_name) + full_name = f"{authenticated_user.first_name} {authenticated_user.last_name}" assert result["ticket_history"][2]["Creator"] == full_name assert result["ticket_history"][3]["Creator"] == "RT System" assert result["ticket_history"][4]["Creator"] == full_name @@ -209,7 +210,7 @@ def test_tickets_post_history_reply_with_text(client, authenticated_user, mock_r mock_rtutil.replyToTicket.assert_called_with( ticket_id=1, files=[], - reply_text="reply text\n[Reply submitted on behalf of {}]".format(authenticated_user.username), + reply_text=f"reply text\n[Reply submitted on behalf of {authenticated_user.username}]", ) @@ -222,7 +223,7 @@ def test_tickets_post_history_reply_with_multiline_text( mock_rtutil.replyToTicket.assert_called_with( ticket_id=1, files=[], - reply_text="reply text\n[Reply submitted on behalf of {}]".format(authenticated_user.username), + reply_text=f"reply text\n[Reply submitted on behalf of {authenticated_user.username}]", ) @@ -250,4 +251,4 @@ def test_tickets_post_history_reply_with_text_and_attachment( _, kwargs = mock_rtutil.replyToTicket.call_args assert kwargs["ticket_id"] == 1 assert len(kwargs["files"]) == 1 - assert kwargs["reply_text"] == "reply text\n[Reply submitted on behalf of {}]".format(authenticated_user.username) + assert kwargs["reply_text"] == f"reply text\n[Reply submitted on behalf of {authenticated_user.username}]" diff --git a/server/portal/apps/tickets/api/urls.py b/server/portal/apps/tickets/api/urls.py index b9678d5e71..6f857772cf 100644 --- a/server/portal/apps/tickets/api/urls.py +++ b/server/portal/apps/tickets/api/urls.py @@ -1,4 +1,5 @@ from django.urls import path + from portal.apps.tickets.api import views app_name = "portal_tickets_api" diff --git a/server/portal/apps/tickets/api/views.py b/server/portal/apps/tickets/api/views.py index 47f48d6288..d92c2c155d 100644 --- a/server/portal/apps/tickets/api/views.py +++ b/server/portal/apps/tickets/api/views.py @@ -1,17 +1,18 @@ import logging import re from functools import wraps -from django.core.files.base import ContentFile -from django.http import JsonResponse, HttpResponseBadRequest, HttpResponse + +from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.decorators import login_required -from django.utils.decorators import method_decorator from django.core.exceptions import PermissionDenied -from portal.apps.tickets import rtUtil -from portal.apps.tickets import utils -from portal.views.base import BaseApiView +from django.core.files.base import ContentFile +from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse +from django.utils.decorators import method_decorator + +from portal.apps.tickets import rtUtil, utils from portal.exceptions.api import ApiException -from django.conf import settings +from portal.views.base import BaseApiView logger = logging.getLogger(__name__) @@ -131,7 +132,7 @@ def _get_ticket_history(self, rt, requesting_username, ticket_id): known_user = get_user_model().objects.filter(username=entry["Creator"]).first() if known_user: - entry["Creator"] = "{} {}".format(known_user.first_name, known_user.last_name) + entry["Creator"] = f"{known_user.first_name} {known_user.last_name}" return ticket_history def _get_matching_history_entry(self, ticket_history, content): @@ -150,7 +151,7 @@ def post(self, request, ticket_id): return HttpResponseBadRequest() # Add information on which user submitted this reply (as this is being done by a service account) - modified_reply = reply + "\n[Reply submitted on behalf of {}]".format(request.user.username) + modified_reply = reply + f"\n[Reply submitted on behalf of {request.user.username}]" attachments = [(f.name, ContentFile(f.read()), f.content_type) for f in request.FILES.getlist("attachments")] diff --git a/server/portal/apps/tickets/rtUtil.py b/server/portal/apps/tickets/rtUtil.py index a9d871d4bc..7d47bc4d6f 100644 --- a/server/portal/apps/tickets/rtUtil.py +++ b/server/portal/apps/tickets/rtUtil.py @@ -1,6 +1,7 @@ +from datetime import datetime + import rt from django.conf import settings -from datetime import datetime from requests.auth import HTTPBasicAuth diff --git a/server/portal/apps/tickets/unit_test.py b/server/portal/apps/tickets/unit_test.py index eacd97a0a0..19ff5b6d07 100644 --- a/server/portal/apps/tickets/unit_test.py +++ b/server/portal/apps/tickets/unit_test.py @@ -1,8 +1,8 @@ import pytest -from django.http import HttpResponse -from django.http import HttpRequest -from portal.apps.tickets.utils import get_recaptcha_verification +from django.http import HttpRequest, HttpResponse + from portal.apps.tickets import rtUtil +from portal.apps.tickets.utils import get_recaptcha_verification @pytest.fixture(autouse=True) diff --git a/server/portal/apps/tickets/urls.py b/server/portal/apps/tickets/urls.py index 10812fa59f..273968d352 100644 --- a/server/portal/apps/tickets/urls.py +++ b/server/portal/apps/tickets/urls.py @@ -1,4 +1,5 @@ from django.urls import path + from portal.apps.tickets import views app_name = "tickets" diff --git a/server/portal/apps/tickets/utils.py b/server/portal/apps/tickets/utils.py index e09166da4c..d3500b42e2 100644 --- a/server/portal/apps/tickets/utils.py +++ b/server/portal/apps/tickets/utils.py @@ -1,7 +1,8 @@ -from portal.apps.tickets import rtUtil -from django.http import JsonResponse, HttpResponseBadRequest -from django.conf import settings import requests +from django.conf import settings +from django.http import HttpResponseBadRequest, JsonResponse + +from portal.apps.tickets import rtUtil METADATA_HEADER = "*** Ticket Metadata ***" @@ -12,20 +13,20 @@ def create_ticket(username, first_name, last_name, email, cc, subject, problem_d if subject is None or email is None or problem_description is None: return HttpResponseBadRequest() - metadata = "{}\n\n".format(METADATA_HEADER) - metadata += "Client info:\n{}\n\n".format(info) + metadata = f"{METADATA_HEADER}\n\n" + metadata += f"Client info:\n{info}\n\n" for key in ["HTTP_REFERER", "HTTP_USER_AGENT", "HTTP_HOST"]: metadata += "{}:\n{}\n\n".format(key, meta.get(key, "None")) if username: - metadata += "authenticated_user:\n{}\n\n".format(username) - metadata += "authenticated_user_email:\n{}\n\n".format(email) - metadata += "authenticated_user_first_name:\n{}\n\n".format(first_name) - metadata += "authenticated_user_last_name:\n{}\n\n".format(last_name) + metadata += f"authenticated_user:\n{username}\n\n" + metadata += f"authenticated_user_email:\n{email}\n\n" + metadata += f"authenticated_user_first_name:\n{first_name}\n\n" + metadata += f"authenticated_user_last_name:\n{last_name}\n\n" else: - metadata += "user_first_name:\n{}\n\n".format(first_name) - metadata += "user_last_name:\n{}\n\n".format(last_name) + metadata += f"user_first_name:\n{first_name}\n\n" + metadata += f"user_last_name:\n{last_name}\n\n" problem_description += "\n\n" + metadata diff --git a/server/portal/apps/tickets/views.py b/server/portal/apps/tickets/views.py index 93b6d51e9e..af98ea3001 100644 --- a/server/portal/apps/tickets/views.py +++ b/server/portal/apps/tickets/views.py @@ -1,7 +1,7 @@ -from django.views.decorators.csrf import ensure_csrf_cookie -from django.shortcuts import redirect, render -from django.contrib.auth.decorators import login_required from django.conf import settings +from django.contrib.auth.decorators import login_required +from django.shortcuts import redirect, render +from django.views.decorators.csrf import ensure_csrf_cookie @login_required diff --git a/server/portal/apps/users/tasks.py b/server/portal/apps/users/tasks.py index 1c67807ea4..d8df36abbb 100644 --- a/server/portal/apps/users/tasks.py +++ b/server/portal/apps/users/tasks.py @@ -1,7 +1,9 @@ import json -from django.conf import settings + from celery import shared_task +from django.conf import settings from pytas.http import TASClient + from portal.libs.elasticsearch.docs.base import IndexedAllocation from portal.libs.elasticsearch.utils import get_sha256_hash diff --git a/server/portal/apps/users/unit_test.py b/server/portal/apps/users/unit_test.py index f3a8a72819..0559a305a3 100644 --- a/server/portal/apps/users/unit_test.py +++ b/server/portal/apps/users/unit_test.py @@ -1,16 +1,18 @@ import json import os +from unittest.mock import patch + import pytest -from mock import patch -from django.test import TestCase -from django.contrib.auth import get_user_model from django.conf import settings -from pytas.http import TASClient +from django.contrib.auth import get_user_model +from django.test import TestCase from elasticsearch.exceptions import NotFoundError +from pytas.http import TASClient from zeep.exceptions import Fault + from portal.apps.auth.models import TapisOAuthToken -from portal.apps.users.utils import get_allocations from portal.apps.users.tasks import get_tas_allocations +from portal.apps.users.utils import get_allocations class AttrDict(dict): @@ -60,7 +62,10 @@ def test_auth_view_noauth(self): def test_usage_view(self, mocked_file): # TODO: this is hideous, there must be a better way to write that or # re-write the route to be less disgusting. - mocked_file.search.return_value.filter.return_value.extra.return_value.execute.return_value.to_dict.return_value = { + mocked_search = mocked_file.search.return_value + mocked_filter = mocked_search.filter.return_value + mocked_extra = mocked_filter.extra.return_value + mocked_extra.execute.return_value.to_dict.return_value = { "aggregations": {"total_storage_bytes": {"value": 10}} } self.client.login(username="test", password="test") @@ -76,12 +81,12 @@ def test_usage_view_noauth(self): class TestGetAllocations(TestCase): def setUp(self): - super(TestGetAllocations, self).setUp() + super().setUp() self.mock_tas_patcher = patch("portal.apps.users.tasks.TASClient", spec=TASClient) self.mock_tas = self.mock_tas_patcher.start() def tearDown(self): - super(TestGetAllocations, self).tearDown() + super().tearDown() self.mock_tas_patcher.stop() @patch("portal.apps.users.utils.IndexedAllocation") @@ -218,7 +223,7 @@ def tas_delete_user_error_response(): def test_add_user(client, requests_mock, authenticated_user, tas_add_user_response): - requests_mock.post("{}/v1/projects/1234/users/5678".format(settings.TAS_URL), json=tas_add_user_response) + requests_mock.post(f"{settings.TAS_URL}/v1/projects/1234/users/5678", json=tas_add_user_response) response = client.post("/api/users/team/manage/1234/5678") assert response.status_code == 200 assert response.json() == {"response": "ok"} @@ -230,13 +235,13 @@ def test_add_user_unauthenticated(client): def test_add_user_failure(client, requests_mock, authenticated_user, tas_add_user_error_response): - requests_mock.post("{}/v1/projects/1234/users/5678".format(settings.TAS_URL), json=tas_add_user_error_response) + requests_mock.post(f"{settings.TAS_URL}/v1/projects/1234/users/5678", json=tas_add_user_error_response) response = client.post("/api/users/team/manage/1234/5678") assert response.status_code == 400 def test_delete_user(client, requests_mock, authenticated_user, tas_delete_user_response): - requests_mock.delete("{}/v1/projects/1234/users/5678".format(settings.TAS_URL), json=tas_delete_user_response) + requests_mock.delete(f"{settings.TAS_URL}/v1/projects/1234/users/5678", json=tas_delete_user_response) response = client.delete("/api/users/team/manage/1234/5678") assert response.status_code == 200 assert response.json() == {"response": "ok"} @@ -248,7 +253,7 @@ def test_delete_user_unauthenticated(client): def test_delete_user_failure(client, requests_mock, authenticated_user, tas_delete_user_error_response): - requests_mock.delete("{}/v1/projects/1234/users/5678".format(settings.TAS_URL), json=tas_delete_user_error_response) + requests_mock.delete(f"{settings.TAS_URL}/v1/projects/1234/users/5678", json=tas_delete_user_error_response) response = client.delete("/api/users/team/manage/1234/5678") assert response.status_code == 400 diff --git a/server/portal/apps/users/urls.py b/server/portal/apps/users/urls.py index 447d956522..d8c0fca3c4 100644 --- a/server/portal/apps/users/urls.py +++ b/server/portal/apps/users/urls.py @@ -1,14 +1,15 @@ from django.urls import path, re_path + from portal.apps.users.views import ( - SearchView, - AuthenticatedView, - UsageView, + AllocationManagementView, AllocationsView, + AllocationUsageView, + AuthenticatedView, + SearchView, + TasUsersView, TeamView, + UsageView, UserDataView, - TasUsersView, - AllocationUsageView, - AllocationManagementView, ) app_name = "users" diff --git a/server/portal/apps/users/utils.py b/server/portal/apps/users/utils.py index d7a236cafb..dd11a1fc3c 100644 --- a/server/portal/apps/users/utils.py +++ b/server/portal/apps/users/utils.py @@ -1,14 +1,16 @@ -from django.db.models import Q +import logging + +import requests from django.conf import settings +from django.db.models import Q +from elasticsearch.exceptions import NotFoundError from pytas.http import TASClient + +from portal.exceptions.api import ApiException from portal.libs.elasticsearch.docs.base import IndexedAllocation -from elasticsearch.exceptions import NotFoundError from portal.libs.elasticsearch.utils import get_sha256_hash -from .tasks import index_allocations, get_tas_allocations -import logging -import requests -from portal.exceptions.api import ApiException +from .tasks import get_tas_allocations, index_allocations logger = logging.getLogger(__name__) @@ -57,7 +59,7 @@ def get_allocations(username, force=False): """ try: if force: - logger.info("Forcing TAS allocation retrieval for user:{}".format(username)) + logger.info(f"Forcing TAS allocation retrieval for user:{username}") raise NotFoundError result = {"hosts": {}, "portal_alloc": None, "active": [], "inactive": []} result.update(IndexedAllocation.from_username(username).value.to_dict()) @@ -79,7 +81,7 @@ def get_project_users_from_name(project_name): : rtype: list """ auth = requests.auth.HTTPBasicAuth(settings.TAS_CLIENT_KEY, settings.TAS_CLIENT_SECRET) - r = requests.get("{0}/v1/projects/name/{1}/users".format(settings.TAS_URL, project_name), auth=auth) + r = requests.get(f"{settings.TAS_URL}/v1/projects/name/{project_name}/users", auth=auth) resp = r.json() if resp["status"] == "success": return resp["result"] @@ -94,7 +96,7 @@ def get_project_users_from_id(project_id): : rtype: list """ auth = requests.auth.HTTPBasicAuth(settings.TAS_CLIENT_KEY, settings.TAS_CLIENT_SECRET) - r = requests.get("{0}/v1/projects/{1}/users".format(settings.TAS_URL, project_id), auth=auth) + r = requests.get(f"{settings.TAS_URL}/v1/projects/{project_id}/users", auth=auth) resp = r.json() if resp["status"] == "success": return resp["result"] @@ -109,7 +111,7 @@ def get_project_from_name(project_name): : rtype: dict """ auth = requests.auth.HTTPBasicAuth(settings.TAS_CLIENT_KEY, settings.TAS_CLIENT_SECRET) - r = requests.get("{0}/v1/projects/name/{1}".format(settings.TAS_URL, project_name), auth=auth) + r = requests.get(f"{settings.TAS_URL}/v1/projects/name/{project_name}", auth=auth) resp = r.json() if resp["status"] == "success": return resp["result"] @@ -124,7 +126,7 @@ def get_project_from_id(project_id): : rtype: dict """ auth = requests.auth.HTTPBasicAuth(settings.TAS_CLIENT_KEY, settings.TAS_CLIENT_SECRET) - r = requests.get("{0}/v1/projects/{1}".format(settings.TAS_URL, project_id), auth=auth) + r = requests.get(f"{settings.TAS_URL}/v1/projects/{project_id}", auth=auth) resp = r.json() if resp["status"] == "success": return resp["result"] @@ -148,7 +150,7 @@ def get_user_data(username): def get_per_user_allocation_usage(allocation_id): auth = requests.auth.HTTPBasicAuth(settings.TAS_CLIENT_KEY, settings.TAS_CLIENT_SECRET) - r = requests.get("{0}/v1/allocations/{1}/usage".format(settings.TAS_URL, allocation_id), auth=auth) + r = requests.get(f"{settings.TAS_URL}/v1/allocations/{allocation_id}/usage", auth=auth) resp = r.json() if resp["status"] == "success": return resp["result"] @@ -158,7 +160,7 @@ def get_per_user_allocation_usage(allocation_id): def add_user(project_id, user_id): auth = requests.auth.HTTPBasicAuth(settings.TAS_CLIENT_KEY, settings.TAS_CLIENT_SECRET) - uri = "{0}/v1/projects/{1}/users/{2}".format(settings.TAS_URL, project_id, user_id) + uri = f"{settings.TAS_URL}/v1/projects/{project_id}/users/{user_id}" r = requests.post(uri, auth=auth) resp = r.json() if resp["status"] != "success": @@ -168,7 +170,7 @@ def add_user(project_id, user_id): def remove_user(project_id, user_id): auth = requests.auth.HTTPBasicAuth(settings.TAS_CLIENT_KEY, settings.TAS_CLIENT_SECRET) - r = requests.delete("{0}/v1/projects/{1}/users/{2}".format(settings.TAS_URL, project_id, user_id), auth=auth) + r = requests.delete(f"{settings.TAS_URL}/v1/projects/{project_id}/users/{user_id}", auth=auth) resp = r.json() if resp["status"] != "success": raise ApiException("Failed to delete user: '{}'".format(resp["message"])) @@ -179,7 +181,5 @@ def check_user_groups(username, groups): try: return any(user["username"] == str(username) for group in groups for user in get_project_users_from_name(group)) except Exception as e: - logger.error( - "Issue checking user groups for user:{} which failed with the following exception:{}".format(username, e) - ) + logger.error(f"Issue checking user groups for user:{username} which failed with the following exception:{e}") return False diff --git a/server/portal/apps/users/views.py b/server/portal/apps/users/views.py index 5374ec15d7..6fef39ec3c 100644 --- a/server/portal/apps/users/views.py +++ b/server/portal/apps/users/views.py @@ -1,32 +1,33 @@ -import logging -import requests import json -from zeep import Client -from zeep.transports import Transport -from zeep.cache import InMemoryCache -from zeep.exceptions import Fault +import logging -from portal.views.base import BaseApiView -from portal.apps.users import utils as users_utils +import requests +from django.conf import settings from django.contrib.auth import get_user_model -from django.forms.models import model_to_dict -from django.http import HttpResponseNotFound, HttpResponseBadRequest, JsonResponse -from django.core.exceptions import ObjectDoesNotExist from django.contrib.auth.decorators import login_required +from django.core.exceptions import ObjectDoesNotExist +from django.forms.models import model_to_dict +from django.http import HttpResponseBadRequest, HttpResponseNotFound, JsonResponse from django.utils.decorators import method_decorator -from django.conf import settings from elasticsearch_dsl import Q -from portal.libs.elasticsearch.docs.base import IndexedFile from pytas.http import TASClient +from zeep import Client +from zeep.cache import InMemoryCache +from zeep.exceptions import Fault +from zeep.transports import Transport + +from portal.apps.users import utils as users_utils from portal.apps.users.utils import ( + add_user, get_allocations, - get_user_data, get_per_user_allocation_usage, - add_user, - remove_user, get_project_from_id, get_project_users_from_id, + get_user_data, + remove_user, ) +from portal.libs.elasticsearch.docs.base import IndexedFile +from portal.views.base import BaseApiView logger = logging.getLogger(__name__) @@ -129,12 +130,14 @@ class AllocationsView(BaseApiView): def get(self, request): """Returns active user allocations on TACC resources - : returns: {'response': {'active': allocations, 'portal_alloc': settings.PORTAL_ALLOCATION, 'inactive': inactive, 'hosts': hosts}} + : returns: {'response': {'active': allocations, + 'portal_alloc': settings.PORTAL_ALLOCATION, 'inactive': inactive, + 'hosts': hosts}} : rtype: dict """ data = get_allocations(request.user.username) - # This line iterates through all the projects in the active allocations and filters out the ones that are excluded in settings. + # Filter active allocations to remove projects excluded in settings. filtered_projects = [ project for project in data["active"] if project.get("projectName") not in settings.ALLOCATIONS_TO_EXCLUDE ] @@ -244,7 +247,8 @@ def put(self, request): tas_client.service.EditProjectUser(user_id, user_role) except Exception: raise Exception( - f"Error assigning user: {user_id} new role: {user_role} to project name:id : {project_name}:{project_id}" + f"Error assigning user: {user_id} new role: {user_role} " + f"to project name:id : {project_name}:{project_id}" ) return JsonResponse({"response": "ok"}) @@ -260,11 +264,11 @@ def get(self, request, allocation_id): @method_decorator(login_required, name="dispatch") class AllocationManagementView(BaseApiView): def post(self, request, project_id, user_id): - logger.info("Adding {} to TAS project {}".format(user_id, project_id)) + logger.info(f"Adding {user_id} to TAS project {project_id}") add_user(project_id, user_id) return JsonResponse({"response": "ok"}) def delete(self, request, project_id, user_id): - logger.info("Deleting {} to TAS project {}".format(user_id, project_id)) + logger.info(f"Deleting {user_id} to TAS project {project_id}") remove_user(project_id, user_id) return JsonResponse({"response": "ok"}) diff --git a/server/portal/apps/webhooks/callback.py b/server/portal/apps/webhooks/callback.py index e627346657..ae43f95bfe 100644 --- a/server/portal/apps/webhooks/callback.py +++ b/server/portal/apps/webhooks/callback.py @@ -1,7 +1,7 @@ from abc import abstractmethod -class WebhookCallback(object): +class WebhookCallback: """class WebhookCallback An abstract base class for executing callback functions upon diff --git a/server/portal/apps/webhooks/fields.py b/server/portal/apps/webhooks/fields.py index f44929bae0..3a820871f5 100644 --- a/server/portal/apps/webhooks/fields.py +++ b/server/portal/apps/webhooks/fields.py @@ -4,7 +4,6 @@ from django.contrib.postgres.fields import JSONField as DjangoJSONField from django.db.models import Field - # Hack for loading Field class appropriate to either Postgres or SQLite (testing db) # from https://medium.com/@philamersune/using-postgresql-jsonfield-in-sqlite-95ad4ad2e5f1 diff --git a/server/portal/apps/webhooks/migrations/0001_initial.py b/server/portal/apps/webhooks/migrations/0001_initial.py index dd7f654449..4121be0924 100644 --- a/server/portal/apps/webhooks/migrations/0001_initial.py +++ b/server/portal/apps/webhooks/migrations/0001_initial.py @@ -1,9 +1,11 @@ # Generated by Django 2.2.7 on 2020-09-17 19:05 import datetime + +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion + import portal.apps.webhooks.fields diff --git a/server/portal/apps/webhooks/migrations/0001_squashed_0003_alter_externalcall_callback_data.py b/server/portal/apps/webhooks/migrations/0001_squashed_0003_alter_externalcall_callback_data.py index a120000022..eae4eb6b65 100644 --- a/server/portal/apps/webhooks/migrations/0001_squashed_0003_alter_externalcall_callback_data.py +++ b/server/portal/apps/webhooks/migrations/0001_squashed_0003_alter_externalcall_callback_data.py @@ -1,9 +1,10 @@ # Generated by Django 3.2.19 on 2023-05-04 22:02 import datetime + +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/server/portal/apps/webhooks/migrations/0002_auto_20221221_2114.py b/server/portal/apps/webhooks/migrations/0002_auto_20221221_2114.py index 528a2948b6..cd310fc2c8 100644 --- a/server/portal/apps/webhooks/migrations/0002_auto_20221221_2114.py +++ b/server/portal/apps/webhooks/migrations/0002_auto_20221221_2114.py @@ -1,6 +1,7 @@ # Generated by Django 2.2.28 on 2022-12-21 21:14 from django.db import migrations + import portal.utils.fields diff --git a/server/portal/apps/webhooks/migrations/0004_alter_externalcall_time.py b/server/portal/apps/webhooks/migrations/0004_alter_externalcall_time.py index b59163d643..4fd68584df 100644 --- a/server/portal/apps/webhooks/migrations/0004_alter_externalcall_time.py +++ b/server/portal/apps/webhooks/migrations/0004_alter_externalcall_time.py @@ -1,7 +1,7 @@ # Generated by Django 4.2.10 on 2024-03-19 16:38 -from django.db import migrations, models import django.utils.timezone +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/server/portal/apps/webhooks/models.py b/server/portal/apps/webhooks/models.py index 60a78a87da..1ee5b7b378 100644 --- a/server/portal/apps/webhooks/models.py +++ b/server/portal/apps/webhooks/models.py @@ -1,5 +1,5 @@ -from django.db import models from django.conf import settings +from django.db import models from django.utils import timezone diff --git a/server/portal/apps/webhooks/unit_test.py b/server/portal/apps/webhooks/unit_test.py index 1668f2f9aa..215dbd4233 100644 --- a/server/portal/apps/webhooks/unit_test.py +++ b/server/portal/apps/webhooks/unit_test.py @@ -1,16 +1,18 @@ import json import os +from unittest.mock import MagicMock, patch from urllib.parse import urlencode -from mock import patch, MagicMock -from django.test import TestCase, TransactionTestCase, override_settings + from django.contrib.auth import get_user_model from django.db.models import signals +from django.test import TestCase, TransactionTestCase, override_settings from django.urls import reverse from tapipy.tapis import TapisResult + from portal.apps.notifications.models import Notification from portal.apps.signals.receivers import send_notification_ws -from portal.libs.exceptions import PortalLibException from portal.apps.webhooks.views import validate_tapis_job +from portal.libs.exceptions import PortalLibException class TestValidateTapisJob(TestCase): diff --git a/server/portal/apps/webhooks/urls.py b/server/portal/apps/webhooks/urls.py index 4828be8b07..3dbe55f849 100644 --- a/server/portal/apps/webhooks/urls.py +++ b/server/portal/apps/webhooks/urls.py @@ -1,8 +1,8 @@ """Webhooks URLs""" from django.urls import path -from portal.apps.webhooks import views +from portal.apps.webhooks import views app_name = "webhooks" urlpatterns = [ diff --git a/server/portal/apps/webhooks/utils.py b/server/portal/apps/webhooks/utils.py index 53486ec920..5c6f033ced 100644 --- a/server/portal/apps/webhooks/utils.py +++ b/server/portal/apps/webhooks/utils.py @@ -1,12 +1,14 @@ -from portal.apps.webhooks.callback import WebhookCallback -from portal.apps.webhooks.models import ExternalCall -from django.conf import settings -from django.core.exceptions import ObjectDoesNotExist +import logging import random import string -from inspect import isclass from importlib import import_module -import logging +from inspect import isclass + +from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist + +from portal.apps.webhooks.callback import WebhookCallback +from portal.apps.webhooks.models import ExternalCall logger = logging.getLogger(__name__) @@ -24,7 +26,7 @@ def register_webhook(callback=None, callback_data=None, user=None): external_call = ExternalCall.objects.create( callback=callback, callback_data=callback_data, user=user, webhook_id=get_webhook_id() ) - return "{}/webhooks/callbacks/{}/".format(settings.VANITY_BASE_URL, external_call.webhook_id) + return f"{settings.VANITY_BASE_URL}/webhooks/callbacks/{external_call.webhook_id}/" def validate_webhook(webhook_id): @@ -46,10 +48,10 @@ def load_callback(callback_name): module = import_module(module_str) call = getattr(module, callable_str) if not isclass(call): - raise ValueError("{callback_name} is not a class".format(callback_name=callback_name)) + raise ValueError(f"{callback_name} is not a class") callback_instance = call() if not isinstance(callback_instance, WebhookCallback): - raise ValueError("{callback_name} is not a subclass of WebhookCallback".format(callback_name=callback_name)) + raise ValueError(f"{callback_name} is not a subclass of WebhookCallback") return callback_instance diff --git a/server/portal/apps/webhooks/utils_unit_test.py b/server/portal/apps/webhooks/utils_unit_test.py index 0cabee9330..65ed0a4716 100644 --- a/server/portal/apps/webhooks/utils_unit_test.py +++ b/server/portal/apps/webhooks/utils_unit_test.py @@ -1,8 +1,8 @@ -from portal.apps.webhooks.models import ExternalCall -from portal.apps.webhooks.callback import WebhookCallback -from portal.apps.webhooks.utils import load_callback, register_webhook, validate_webhook, execute_callback import pytest +from portal.apps.webhooks.callback import WebhookCallback +from portal.apps.webhooks.models import ExternalCall +from portal.apps.webhooks.utils import execute_callback, load_callback, register_webhook, validate_webhook pytestmark = pytest.mark.django_db @@ -13,7 +13,7 @@ def callback(self, external_call, webhook_request): assert webhook_request == "mock_request" -class InvalidCallback(object): +class InvalidCallback: pass diff --git a/server/portal/apps/webhooks/views.py b/server/portal/apps/webhooks/views.py index c7a3595b98..4cef360508 100644 --- a/server/portal/apps/webhooks/views.py +++ b/server/portal/apps/webhooks/views.py @@ -1,25 +1,23 @@ import json import logging +from django.conf import settings from django.contrib.auth import get_user_model -from django.views.decorators.csrf import csrf_exempt -from django.utils.decorators import method_decorator +from django.core.exceptions import ObjectDoesNotExist from django.db import transaction from django.http import HttpResponse, HttpResponseBadRequest -from django.core.exceptions import ObjectDoesNotExist - +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_exempt from requests import HTTPError from tapipy.errors import BaseTapyException from portal.apps.notifications.models import Notification from portal.apps.search.tasks import tapis_indexer -from portal.views.base import BaseApiView -from portal.libs.exceptions import PortalLibException -from portal.exceptions.api import ApiException -from portal.apps.webhooks.utils import validate_webhook, execute_callback +from portal.apps.webhooks.utils import execute_callback, validate_webhook from portal.apps.workspace.api.utils import check_job_for_timeout - -from django.conf import settings +from portal.exceptions.api import ApiException +from portal.libs.exceptions import PortalLibException +from portal.views.base import BaseApiView logger = logging.getLogger(__name__) @@ -43,9 +41,8 @@ def validate_tapis_job(job_uuid, job_owner, disallowed_states=[]): # Validate the job UUID against the owner if job_data.owner != job_owner: logger.error( - "Tapis job (owner='{}', status='{}) for this event (owner='{}') is not valid".format( - job_data.owner, job_data.status, job_owner - ) + f"Tapis job (owner='{job_data.owner}', status='{job_data.status}) " + " for this event (owner='{job_owner}') is not valid" ) raise PortalLibException("Unable to find a related valid job for this notification.") @@ -94,9 +91,7 @@ def post(self, request, *args, **kwargs): # Do nothing on job status not in portal notification states if job_status not in settings.PORTAL_JOB_NOTIFICATION_STATES: logger.info( - "Job UUID {} for owner {} entered {} state (no notification sent)".format( - job_uuid, username, job_status - ) + f"Job UUID {job_uuid} for owner {username} entered {job_status} state (no notification sent)" ) return HttpResponse("OK") @@ -104,7 +99,7 @@ def post(self, request, *args, **kwargs): if job_status == job_old_status: return HttpResponse("OK") - logger.info("JOB STATUS CHANGE: UUID={} status={}".format(job_uuid, job_status)) + logger.info(f"JOB STATUS CHANGE: UUID={job_uuid} status={job_status}") event_data = { Notification.EVENT_TYPE: "job", @@ -121,7 +116,7 @@ def post(self, request, *args, **kwargs): event_data[Notification.EXTRA]["status"] = job_details.status try: - logger.info("Indexing job output for job={}".format(job_uuid)) + logger.info(f"Indexing job output for job={job_uuid}") tapis_indexer.apply_async( kwargs={ @@ -131,7 +126,7 @@ def post(self, request, *args, **kwargs): } ) except Exception as e: - logger.exception("Error starting async task to index job output: {}".format(e)) + logger.exception(f"Error starting async task to index job output: {e}") with transaction.atomic(): Notification.objects.create(**event_data) @@ -179,9 +174,7 @@ def post(self, request, *args, **kwargs): try: valid_state = validate_tapis_job(job_uuid, job_owner, TERMINAL_JOB_STATES) if not valid_state: - raise PortalLibException( - "Interactive Job UUID {} for user {} was in invalid state".format(job_uuid, job_owner) - ) + raise PortalLibException(f"Interactive Job UUID {job_uuid} for user {job_owner} was in invalid state") event_data[Notification.EXTRA] = { "name": valid_state.name, "status": valid_state.status, diff --git a/server/portal/apps/webhooks/views_unit_test.py b/server/portal/apps/webhooks/views_unit_test.py index ac138459a0..84574a0679 100644 --- a/server/portal/apps/webhooks/views_unit_test.py +++ b/server/portal/apps/webhooks/views_unit_test.py @@ -1,9 +1,10 @@ -from portal.apps.webhooks.utils import register_webhook -from portal.apps.webhooks.callback import WebhookCallback -from django.conf import settings import json + import pytest +from django.conf import settings +from portal.apps.webhooks.callback import WebhookCallback +from portal.apps.webhooks.utils import register_webhook pytestmark = pytest.mark.django_db diff --git a/server/portal/apps/workbench/api/unit_test.py b/server/portal/apps/workbench/api/unit_test.py index b60650a8ff..86562a6d8d 100644 --- a/server/portal/apps/workbench/api/unit_test.py +++ b/server/portal/apps/workbench/api/unit_test.py @@ -1,4 +1,5 @@ import json + from django.conf import settings diff --git a/server/portal/apps/workbench/api/urls.py b/server/portal/apps/workbench/api/urls.py index 3c41256e3e..1682ed3e48 100644 --- a/server/portal/apps/workbench/api/urls.py +++ b/server/portal/apps/workbench/api/urls.py @@ -1,4 +1,5 @@ from django.urls import path + from portal.apps.workbench.api import views app_name = "workbench_api" diff --git a/server/portal/apps/workbench/urls.py b/server/portal/apps/workbench/urls.py index 6079bbbafa..af074d3381 100644 --- a/server/portal/apps/workbench/urls.py +++ b/server/portal/apps/workbench/urls.py @@ -4,6 +4,7 @@ """ from django.urls import re_path + from portal.apps.workbench.views import IndexView app_name = "workbench" diff --git a/server/portal/apps/workbench/views.py b/server/portal/apps/workbench/views.py index 3835c2fa3a..ab5c72103f 100644 --- a/server/portal/apps/workbench/views.py +++ b/server/portal/apps/workbench/views.py @@ -1,7 +1,7 @@ -from django.views.generic.base import TemplateView +from django.conf import settings from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator -from django.conf import settings +from django.views.generic.base import TemplateView @method_decorator(login_required, name="dispatch") @@ -13,10 +13,10 @@ class IndexView(TemplateView): template_name = "portal/apps/workbench/index.html" def dispatch(self, request, *args, **kwargs): - return super(IndexView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): - context = super(IndexView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context["setup_complete"] = self.request.user.profile.setup_complete context["DEBUG"] = settings.DEBUG return context diff --git a/server/portal/apps/workspace/admin.py b/server/portal/apps/workspace/admin.py index 35507b699a..d1d9c8bf7f 100644 --- a/server/portal/apps/workspace/admin.py +++ b/server/portal/apps/workspace/admin.py @@ -1,5 +1,6 @@ from django.contrib import admin -from portal.apps.workspace.models import AppTrayEntry, AppTrayCategory + +from portal.apps.workspace.models import AppTrayCategory, AppTrayEntry @admin.register(AppTrayCategory) diff --git a/server/portal/apps/workspace/api/handlers/tapis_handlers.py b/server/portal/apps/workspace/api/handlers/tapis_handlers.py index 1d504edd2f..062b1b837e 100644 --- a/server/portal/apps/workspace/api/handlers/tapis_handlers.py +++ b/server/portal/apps/workspace/api/handlers/tapis_handlers.py @@ -1,6 +1,5 @@ import logging - logger = logging.getLogger(__name__) diff --git a/server/portal/apps/workspace/api/lookups.py b/server/portal/apps/workspace/api/lookups.py index b3fa1ebd3f..f31adb3efa 100644 --- a/server/portal/apps/workspace/api/lookups.py +++ b/server/portal/apps/workspace/api/lookups.py @@ -2,11 +2,12 @@ :synopsis: Function to lookup manager classes """ -from importlib import import_module import logging +from importlib import import_module + from django.conf import settings -from portal.exceptions.api import ApiException +from portal.exceptions.api import ApiException logger = logging.getLogger(__name__) diff --git a/server/portal/apps/workspace/api/unit_test.py b/server/portal/apps/workspace/api/unit_test.py index 14cb57f6ea..1e52cd4984 100644 --- a/server/portal/apps/workspace/api/unit_test.py +++ b/server/portal/apps/workspace/api/unit_test.py @@ -1,8 +1,9 @@ import json +from unittest.mock import patch + import pytest -from mock import patch -from django.test import TestCase from django.contrib.auth import get_user_model +from django.test import TestCase @pytest.mark.django_db(transaction=True) @@ -20,7 +21,7 @@ def tearDown(self): def test_job_history_get(self): job_uuid = "032142c3-ac6a-42cb-841e-fbc26a2d951c-007" self.mock_tapis_client.jobs.getJobHistory.return_value = "mock_response" - response = self.client.get("/api/workspace/jobs/{}/history".format(job_uuid)) + response = self.client.get(f"/api/workspace/jobs/{job_uuid}/history") self.mock_tapis_client.jobs.getJobHistory.assert_called_with( jobUuid=job_uuid, headers={"X-Tapis-Tracking-ID": f"portals.{self.client.session.session_key}"}, diff --git a/server/portal/apps/workspace/api/urls.py b/server/portal/apps/workspace/api/urls.py index 680035ceb1..f26e242a40 100644 --- a/server/portal/apps/workspace/api/urls.py +++ b/server/portal/apps/workspace/api/urls.py @@ -1,8 +1,8 @@ """Workpace API Urls""" from django.urls import re_path -from portal.apps.workspace.api import views +from portal.apps.workspace.api import views app_name = "workspace_api" urlpatterns = [ diff --git a/server/portal/apps/workspace/api/utils.py b/server/portal/apps/workspace/api/utils.py index 46746b78d8..c23f070621 100644 --- a/server/portal/apps/workspace/api/utils.py +++ b/server/portal/apps/workspace/api/utils.py @@ -2,8 +2,10 @@ import json import logging + from django.conf import settings -from tapipy.errors import BaseTapyException, UnauthorizedError, ForbiddenError +from tapipy.errors import BaseTapyException, ForbiddenError, UnauthorizedError + from portal.apps.onboarding.steps.system_access_v3 import create_system_credentials_with_tms from portal.exceptions.api import ApiException @@ -13,7 +15,8 @@ def get_tapis_timeout_error_messages(job_id): return [ "JOBS_EARLY_TERMINATION Job terminated by Tapis because: TIME_EXPIRED", - f'JOBS_USER_APP_FAILURE The user application ({job_id}) ended with remote status "TIMEOUT" and returned exit code: 0:0.', + f"JOBS_USER_APP_FAILURE The user application ({job_id}) ended with remote " + 'status "TIMEOUT" and returned exit code: 0:0.', ] diff --git a/server/portal/apps/workspace/api/utils_unit_test.py b/server/portal/apps/workspace/api/utils_unit_test.py index fe76ec4913..a6b3fe30d9 100644 --- a/server/portal/apps/workspace/api/utils_unit_test.py +++ b/server/portal/apps/workspace/api/utils_unit_test.py @@ -1,6 +1,8 @@ -from tapipy.tapis import TapisResult +from unittest.mock import patch + from tapipy.errors import UnauthorizedError -from mock import patch +from tapipy.tapis import TapisResult + from portal.apps.workspace.api.utils import ( push_keys_required_if_not_credentials_ensured, ) @@ -10,7 +12,8 @@ def test_push_keys_required_if_not_credentials_ensured_successful_credential_cre authenticated_user, mock_tapis_client ): """ - Test that the push_keys_required_if_not_credentials_ensured function returns False when the user has system credentials + Test that the push_keys_required_if_not_credentials_ensured function + returns False when the user has system credentials and the system does not require keys to be pushed. """ system = { diff --git a/server/portal/apps/workspace/api/views.py b/server/portal/apps/workspace/api/views.py index 8f62fbd857..d80fe9aefe 100644 --- a/server/portal/apps/workspace/api/views.py +++ b/server/portal/apps/workspace/api/views.py @@ -3,34 +3,35 @@ :synopsys: Views to handle Workspace API """ -import logging import json +import logging from urllib.parse import urlparse -from django.http import JsonResponse + from django.conf import settings from django.contrib.auth.decorators import login_required -from django.utils.decorators import method_decorator -from django.urls import reverse -from django.db.models.functions import Coalesce from django.core.exceptions import PermissionDenied +from django.db.models.functions import Coalesce +from django.http import JsonResponse +from django.urls import reverse +from django.utils.decorators import method_decorator from tapipy.tapis import TapisResult -from portal.views.base import BaseApiView -from portal.exceptions.api import ApiException + +from portal.apps.datafiles.utils import evaluate_datafiles_storage_system from portal.apps.licenses.models import LICENSE_TYPES, get_license_info -from portal.libs.agave.utils import service_account -from portal.libs.agave.serializers import BaseTapisResultSerializer +from portal.apps.workspace.api.utils import check_job_for_timeout, push_keys_required_if_not_credentials_ensured # TODOv3: dropV2Jobs -from portal.apps.workspace.models import JobSubmission -from portal.apps.workspace.models import AppTrayCategory, AppTrayEntry -from .handlers.tapis_handlers import tapis_get_handler -from portal.apps.workspace.api.utils import check_job_for_timeout, push_keys_required_if_not_credentials_ensured +from portal.apps.workspace.models import AppTrayCategory, AppTrayEntry, JobSubmission +from portal.exceptions.api import ApiException +from portal.libs.agave.serializers import BaseTapisResultSerializer +from portal.libs.agave.utils import service_account from portal.utils import get_client_ip -from portal.apps.datafiles.utils import evaluate_datafiles_storage_system +from portal.views.base import BaseApiView +from .handlers.tapis_handlers import tapis_get_handler logger = logging.getLogger(__name__) -METRICS = logging.getLogger("metrics.{}".format(__name__)) +METRICS = logging.getLogger(f"metrics.{__name__}") def _app_license_type(app_def): @@ -277,14 +278,16 @@ def has_timeout_message(job): qs_upper = query_string.upper() if query_string else "" qs_title = query_string.title() if query_string else "" - # TODO WP-1116: all status search add-ons to be removed and added to drop-down feature on frontend + # TODO WP-1116: all status search add-ons to be removed and added to + # drop-down feature on frontend if status_searches: enhanced_status_conditions = [] for status in status_searches: if status == "FINISHED": enhanced_status_conditions.append( - "(status = 'FINISHED' OR (status = 'FAILED' AND (lastMessage LIKE '%TIME_EXPIRED%' OR lastMessage LIKE '%TIMEOUT%')))" + "(status = 'FINISHED' OR (status = 'FAILED' AND " + "(lastMessage LIKE '%TIME_EXPIRED%' OR lastMessage LIKE '%TIMEOUT%')))" ) elif status == "FAILED": enhanced_status_conditions.append("(status = 'FAILED')") @@ -336,7 +339,8 @@ def has_timeout_message(job): # (ii) FAILED jobs that are interactive and have the timeout/expired message (will be shown as FINISHED on UI) # For "Failed" search, want to get all - # (i) FAILED jobs except those that were interactive and have the timeout/expired message (excluded because they are shown as FINISHED on UI) + # (i) FAILED jobs except those that were interactive and have the timeout/expired message + # (excluded because they are shown as FINISHED on UI) is_finished = "FINISHED" in status_searches is_failed = "FAILED" in status_searches @@ -437,7 +441,7 @@ def post(self, request, *args, **kwargs): if job_uuid and job_action: if job_action == "resubmit": - logger.info("user:{} is resubmitting job uuid:{}".format(username, job_uuid)) + logger.info(f"user:{username} is resubmitting job uuid:{job_uuid}") data = tapis.jobs.resubmitJob( jobUuid=job_uuid, headers={"X-Tapis-Tracking-ID": f"portals.{request.session.session_key}"} ) @@ -461,7 +465,7 @@ def post(self, request, *args, **kwargs): ) elif job_action == "cancel": - logger.info("user:{} is canceling/stopping job uuid:{}".format(username, job_uuid)) + logger.info(f"user:{username} is canceling/stopping job uuid:{job_uuid}") data = tapis.jobs.cancelJob( jobUuid=job_uuid, headers={"X-Tapis-Tracking-ID": f"portals.{request.session.session_key}"} ) @@ -485,9 +489,8 @@ def post(self, request, *args, **kwargs): ) else: raise ApiException( - "user:{} is trying to run an unsupported job action: {} for job uuid: {}".format( - username, job_action, job_uuid - ), + f"user:{username} is trying to run an unsupported job action: {job_action}" + f" for job uuid: {job_uuid}", status=400, ) @@ -501,15 +504,13 @@ def post(self, request, *args, **kwargs): elif not job_post: raise ApiException( - "user:{} is submitting a request with no job body.".format( - username, - ), + f"user:{username} is submitting a request with no job body.", status=400, ) # submit job else: - logger.info("processing job submission for user:{}: {}".format(username, job_post)) + logger.info(f"processing job submission for user:{username}: {job_post}") # Provide default job archive configuration if none is provided and portal has default system if settings.PORTAL_DATAFILES_DEFAULT_STORAGE_SYSTEM: @@ -540,13 +541,16 @@ def post(self, request, *args, **kwargs): if lic is None: raise ApiException("You are missing the required license for this application.") - # TODOv3: Multistring licenses break environment variables. Determine how to handle multistring licenses, if needed at all. + # TODOv3: Multistring licenses break environment variables. + # Determine how to handle multistring licenses, if needed at all. # https://jira.tacc.utexas.edu/browse/WP-70 # license_var = { # "key": "_license", # "value": lic.license_as_str() # } - # job_post['parameterSet']['envVariables'] = job_post['parameterSet'].get('envVariables', []) + [license_var] + # job_post["parameterSet"]["envVariables"] = job_post["parameterSet"].get("envVariables", []) + [ + # license_var + # ] # Test file listing on relevant systems to determine whether keys need to be pushed manually for system_id in list(filter(None, [job_post.get("archiveSystemId"), execSystemId])): @@ -596,7 +600,7 @@ def post(self, request, *args, **kwargs): } ] - logger.info("user:{} is submitting job:{}".format(username, job_post)) + logger.info(f"user:{username} is submitting job:{job_post}") response = tapis.jobs.submitJob( **job_post, headers={"X-Tapis-Tracking-ID": f"portals.{request.session.session_key}"} ) @@ -636,11 +640,11 @@ def get(self, request, *args, **kwargs): user_role = request.GET.get("user_role") system_id = request.GET.get("system_id") if roles: - logger.info("user:{} tapis.systems.listRoles system_id:{}".format(request.user.username, system_id)) + logger.info(f"user:{request.user.username} tapis.systems.listRoles system_id:{system_id}") agc = service_account() data = agc.systems.listRoles(systemId=system_id) elif user_role: - logger.info("user:{} tapis.systems.getRoleForUser system_id:{}".format(request.user.username, system_id)) + logger.info(f"user:{request.user.username} tapis.systems.getRoleForUser system_id:{system_id}") agc = service_account() data = agc.systems.getRoleForUser(systemId=system_id, username=request.user.username) return JsonResponse({"response": data}) @@ -649,7 +653,7 @@ def post(self, request, *args, **kwargs): body = json.loads(request.body) role = body["role"] system_id = body["system_id"] - logger.info("user:{} tapis.systems.updateRole system_id:{}".format(request.user.username, system_id)) + logger.info(f"user:{request.user.username} tapis.systems.updateRole system_id:{system_id}") role_body = {"username": request.user.username, "role": role} agc = service_account() data = agc.systems.updateRole(systemId=system_id, body=role_body) @@ -824,7 +828,8 @@ def get(self, request): """ tabs, html_definitions = self.getPublicApps(request.user) - # Add "My Apps" tab with all of the user's private and shared apps that are enabled, or all available apps if no public apps are enabled + # Add "My Apps" tab with all of the user's private and shared apps + # that are enabled, or all available apps if no public apps are enabled my_apps = self.getPrivateApps(request.user) shared_apps = self.getSharedApps(request.user) @@ -843,7 +848,7 @@ def get(self, request, operation=None): return JsonResponse({"message": "This view requires authentication."}, status=403) get_params = request.GET.dict() - logger.info("user:%s op:%s query_params:%s" % (request.user.username, operation, get_params)) + logger.info(f"user:{request.user.username} op:{operation} query_params:{get_params}") response = tapis_get_handler(client, operation, **get_params) return JsonResponse({"data": response}) diff --git a/server/portal/apps/workspace/api/views_unit_test.py b/server/portal/apps/workspace/api/views_unit_test.py index 832dd3aadc..a8b8489688 100644 --- a/server/portal/apps/workspace/api/views_unit_test.py +++ b/server/portal/apps/workspace/api/views_unit_test.py @@ -1,13 +1,13 @@ -from django.conf import settings -from portal.apps.workspace.api.views import AppsTrayView -from portal.apps.workspace.models import AppTrayCategory -from portal.apps.workspace.models import JobSubmission import json import os + import pytest -from tapipy.tapis import TapisResult +from django.conf import settings from django.core.management import call_command +from tapipy.tapis import TapisResult +from portal.apps.workspace.api.views import AppsTrayView +from portal.apps.workspace.models import AppTrayCategory, JobSubmission pytest.mark.django_db(transaction=True) @@ -187,7 +187,7 @@ def test_job_post_is_logged_for_metrics( # Ensure metric-related logging is being performed logging_metric_mock.assert_called_with( - "user:{} is submitting job:{}".format(authenticated_user.username, tapis_job_submission) + f"user:{authenticated_user.username} is submitting job:{tapis_job_submission}" ) diff --git a/server/portal/apps/workspace/management/commands/import-apps.py b/server/portal/apps/workspace/management/commands/import-apps.py index e9b86da5be..cdffd51e16 100644 --- a/server/portal/apps/workspace/management/commands/import-apps.py +++ b/server/portal/apps/workspace/management/commands/import-apps.py @@ -1,10 +1,11 @@ import logging -from django.core.management import BaseCommand + from django.conf import settings +from django.core.management import BaseCommand from tapipy.errors import NotFoundError -from portal.libs.agave.utils import service_account -from portal.apps.workspace.models import AppTrayCategory, AppTrayEntry +from portal.apps.workspace.models import AppTrayCategory, AppTrayEntry +from portal.libs.agave.utils import service_account logger = logging.getLogger(__name__) @@ -63,10 +64,10 @@ def import_apps(self, portal_names): appId=app.get("id"), ) - logger.info("Imported {}".format(app_entry)) + logger.info(f"Imported {app_entry}") except Exception: logger.exception("Error importing application") - logger.info("Following app could not be imported: {}".format(app)) + logger.info(f"Following app could not be imported: {app}") def handle(self, *args, **options): if options["clean"]: diff --git a/server/portal/apps/workspace/management/commands/import-jobs.py b/server/portal/apps/workspace/management/commands/import-jobs.py index 3cc45d29a3..32e1918466 100644 --- a/server/portal/apps/workspace/management/commands/import-jobs.py +++ b/server/portal/apps/workspace/management/commands/import-jobs.py @@ -1,8 +1,9 @@ -from django.core.management import BaseCommand -from portal.libs.agave.utils import service_account +import dateutil.parser from django.contrib.auth import get_user_model +from django.core.management import BaseCommand + from portal.apps.workspace.models import JobSubmission -import dateutil.parser +from portal.libs.agave.utils import service_account class Command(BaseCommand): @@ -34,4 +35,4 @@ def handle(self, *args, **options): offset += 100 done = len(jobs) < 100 total += len(jobs) - print("{} jobs for {}".format(total, user.username)) + print(f"{total} jobs for {user.username}") diff --git a/server/portal/apps/workspace/management/commands/unit_test.py b/server/portal/apps/workspace/management/commands/unit_test.py index a1466309ab..54a441f7a7 100644 --- a/server/portal/apps/workspace/management/commands/unit_test.py +++ b/server/portal/apps/workspace/management/commands/unit_test.py @@ -1,8 +1,10 @@ -from mock import patch +from unittest.mock import patch + import pytest -from django.test import TransactionTestCase from django.contrib.auth import get_user_model from django.core.management import call_command +from django.test import TransactionTestCase + from portal.apps.workspace.models import JobSubmission diff --git a/server/portal/apps/workspace/migrations/0001_initial.py b/server/portal/apps/workspace/migrations/0001_initial.py index c4ef3f9a6c..b767d435c0 100644 --- a/server/portal/apps/workspace/migrations/0001_initial.py +++ b/server/portal/apps/workspace/migrations/0001_initial.py @@ -1,9 +1,10 @@ # Generated by Django 2.2.7 on 2019-11-05 19:50 import datetime + +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/server/portal/apps/workspace/migrations/0001_squashed_0006_alter_jobsubmission_data.py b/server/portal/apps/workspace/migrations/0001_squashed_0006_alter_jobsubmission_data.py index 0b38d84553..9b1cb5af7d 100644 --- a/server/portal/apps/workspace/migrations/0001_squashed_0006_alter_jobsubmission_data.py +++ b/server/portal/apps/workspace/migrations/0001_squashed_0006_alter_jobsubmission_data.py @@ -1,9 +1,9 @@ # Generated by Django 3.2.19 on 2023-05-04 22:05 -from django.conf import settings -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/server/portal/apps/workspace/migrations/0002_auto_20200218_2115.py b/server/portal/apps/workspace/migrations/0002_auto_20200218_2115.py index df8bfbfb31..4f4a403987 100644 --- a/server/portal/apps/workspace/migrations/0002_auto_20200218_2115.py +++ b/server/portal/apps/workspace/migrations/0002_auto_20200218_2115.py @@ -1,7 +1,7 @@ # Generated by Django 2.2.7 on 2020-02-18 21:15 -from django.db import migrations, models import django.utils.timezone +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/server/portal/apps/workspace/migrations/0003_apptraycategory_apptrayentry.py b/server/portal/apps/workspace/migrations/0003_apptraycategory_apptrayentry.py index 25f43b388b..ce3ec93f94 100644 --- a/server/portal/apps/workspace/migrations/0003_apptraycategory_apptrayentry.py +++ b/server/portal/apps/workspace/migrations/0003_apptraycategory_apptrayentry.py @@ -1,7 +1,7 @@ # Generated by Django 2.2.16 on 2020-11-17 17:17 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/server/portal/apps/workspace/migrations/0004_jobsubmission_data.py b/server/portal/apps/workspace/migrations/0004_jobsubmission_data.py index f42683aefd..0650a7c6b4 100644 --- a/server/portal/apps/workspace/migrations/0004_jobsubmission_data.py +++ b/server/portal/apps/workspace/migrations/0004_jobsubmission_data.py @@ -1,6 +1,7 @@ # Generated by Django 2.2.28 on 2022-12-21 21:14 from django.db import migrations + import portal.utils.fields diff --git a/server/portal/apps/workspace/models.py b/server/portal/apps/workspace/models.py index 2b61566370..3528f42f3f 100644 --- a/server/portal/apps/workspace/models.py +++ b/server/portal/apps/workspace/models.py @@ -1,5 +1,5 @@ -from django.db import models from django.conf import settings +from django.db import models from django.utils import timezone @@ -29,7 +29,7 @@ class AppTrayCategory(models.Model): ) def __str__(self): - return "%s" % (self.category) + return f"{self.category}" class AppTrayEntry(models.Model): @@ -48,8 +48,8 @@ class AppTrayEntry(models.Model): def __str__(self): if self.appType == "html": - return "%s: %s (HTML)" % (self.label, self.appId) - return "%s%s%s" % ( + return f"{self.label}: {self.appId} (HTML)" + return "{}{}{}".format( f"{self.label}: " if self.label else "", self.appId, f"-{self.version}" if self.version else "", diff --git a/server/portal/apps/workspace/models_unit_test.py b/server/portal/apps/workspace/models_unit_test.py index 6a1fc6cd96..21b3e0b25b 100644 --- a/server/portal/apps/workspace/models_unit_test.py +++ b/server/portal/apps/workspace/models_unit_test.py @@ -1,4 +1,4 @@ -from portal.apps.workspace.models import JobSubmission, AppTrayCategory, AppTrayEntry +from portal.apps.workspace.models import AppTrayCategory, AppTrayEntry, JobSubmission def test_job_submission_model(django_db_reset_sequences, regular_user): diff --git a/server/portal/apps/workspace/unit_test.py b/server/portal/apps/workspace/unit_test.py index e22af05f19..ed6f5bf8f1 100644 --- a/server/portal/apps/workspace/unit_test.py +++ b/server/portal/apps/workspace/unit_test.py @@ -1,10 +1,11 @@ import json import os -from mock import patch -from django.test import TestCase +from unittest.mock import patch + +import pytest from django.conf import settings from django.contrib.auth import get_user_model -import pytest +from django.test import TestCase @pytest.mark.django_db(transaction=True) @@ -13,7 +14,7 @@ class TestAppsApiViews(TestCase): @classmethod def setUpClass(cls): - super(TestAppsApiViews, cls).setUpClass() + super().setUpClass() cls.mock_client_patcher = patch("portal.apps.auth.models.TapisOAuthToken.client") cls.mock_client = cls.mock_client_patcher.start() cls.mock_get_user_data_patcher = patch("portal.apps.users.utils.get_user_data") @@ -24,7 +25,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - super(TestAppsApiViews, cls).tearDownClass() + super().tearDownClass() cls.mock_get_user_data_patcher.stop() cls.mock_client_patcher.stop() diff --git a/server/portal/apps/workspace/urls.py b/server/portal/apps/workspace/urls.py index 0220e5165d..9585b452db 100644 --- a/server/portal/apps/workspace/urls.py +++ b/server/portal/apps/workspace/urls.py @@ -1,6 +1,7 @@ """Workspace URLs""" from django.urls import re_path + from portal.apps.workspace import views urlpatterns = [ diff --git a/server/portal/apps/workspace/views.py b/server/portal/apps/workspace/views.py index ee83bfe391..a95d861b8c 100644 --- a/server/portal/apps/workspace/views.py +++ b/server/portal/apps/workspace/views.py @@ -3,10 +3,10 @@ :synopsis: Views to handle Workspace """ -from django.views.generic.base import TemplateView +from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator from django.views.decorators.csrf import ensure_csrf_cookie -from django.contrib.auth.decorators import login_required +from django.views.generic.base import TemplateView @method_decorator(login_required, name="dispatch") @@ -18,4 +18,4 @@ class WorkspaceView(TemplateView): @method_decorator(ensure_csrf_cookie) def dispatch(self, request, *args, **kwargs): """Overwrite dispatch to ensure csrf cookie""" - return super(WorkspaceView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) diff --git a/server/portal/asgi.py b/server/portal/asgi.py index 620514d02a..f4d5d683e1 100644 --- a/server/portal/asgi.py +++ b/server/portal/asgi.py @@ -4,12 +4,13 @@ """ import os + import django +from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter from django.core.asgi import get_asgi_application -from channels.auth import AuthMiddlewareStack -import portal.apps.notifications.routing +import portal.apps.notifications.routing os.environ.setdefault("DJANGO_SETTINGS_MODULE", "portal.settings.settings") django.setup() diff --git a/server/portal/celery.py b/server/portal/celery.py index 5ac2f1f9f9..a436ee3d90 100644 --- a/server/portal/celery.py +++ b/server/portal/celery.py @@ -1,4 +1,5 @@ import os + from celery import Celery from celery.schedules import crontab from django.conf import settings @@ -28,4 +29,4 @@ @app.task(bind=True) def debug_task(self): - print(("Request: {0!r}".format(self.request))) + print(f"Request: {self.request!r}") diff --git a/server/portal/exceptions/api.py b/server/portal/exceptions/api.py index df8564ac4d..613b3fca08 100644 --- a/server/portal/exceptions/api.py +++ b/server/portal/exceptions/api.py @@ -44,7 +44,7 @@ def __init__(self, message=None, status=None, extra=None, *args, **kwargs): ... response=e.response ... ) """ - super(ApiException, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) response = self.response or Response() response.status_code = status or response.status_code response.reason = message or response.reason diff --git a/server/portal/libs/agave/exceptions.py b/server/portal/libs/agave/exceptions.py index afaba55e04..fe75d25a86 100644 --- a/server/portal/libs/agave/exceptions.py +++ b/server/portal/libs/agave/exceptions.py @@ -4,11 +4,12 @@ """ import logging + from portal.libs.exceptions import PortalLibException # pylint: disable=invalid-name logger = logging.getLogger(__name__) -METRICS = logging.getLogger("metrics.{}".format(__name__)) +METRICS = logging.getLogger(f"metrics.{__name__}") # pylint: enable=invalid-name diff --git a/server/portal/libs/agave/models/applications.py b/server/portal/libs/agave/models/applications.py index 79a71ecdd8..35341ba23f 100644 --- a/server/portal/libs/agave/models/applications.py +++ b/server/portal/libs/agave/models/applications.py @@ -3,10 +3,12 @@ :synopsis: Classes to represent Agave Applications """ -from collections import namedtuple import logging +from collections import namedtuple + from cached_property import cached_property_with_ttl -from portal.libs.agave.exceptions import ValidationError, CreationError, DeletionError, APIError + +from portal.libs.agave.exceptions import APIError, CreationError, DeletionError, ValidationError from portal.libs.agave.models.base import BaseAgaveResource from portal.libs.agave.models.permissions import ApplicationPermissions @@ -76,7 +78,7 @@ def __init__(self, client, id=None, load=True, ignore_error=404, **kwargs): wrapped.update(**kwargs) - super(Application, self).__init__(client, **wrapped) + super().__init__(client, **wrapped) self.id = getattr(self, "id", None) self.name = getattr(self, "name", None) @@ -126,7 +128,7 @@ def permissions(self): return ApplicationPermissions(self._ac, pems, self) def __str__(self): - return "{id}".format(id=self.id) + return f"{self.id}" # def __repr__(self): # return '{class_name}(id={id}, label={label})'.format( @@ -191,7 +193,7 @@ def validate_execution_type(self): """Validate self.execution_type""" types = self._EXECUTION_TYPE if self.execution_type not in types: - raise ValidationError("'execution_type' should be one of: {types}".format(types=types)) + raise ValidationError(f"'execution_type' should be one of: {types}") def validate_version(self): """Validate self.version""" @@ -212,7 +214,7 @@ def validate_parallelism(self): """Validate self.parallelism""" types = self._PARALLELISM if self.parallelism not in types: - raise ValidationError("'parallelism' should be one of: {types}".format(types=types)) + raise ValidationError(f"'parallelism' should be one of: {types}") def validate_short_description(self): """Validate self.short_description""" diff --git a/server/portal/libs/agave/models/permissions.py b/server/portal/libs/agave/models/permissions.py index 7089897d34..acf5e3dc00 100644 --- a/server/portal/libs/agave/models/permissions.py +++ b/server/portal/libs/agave/models/permissions.py @@ -4,6 +4,7 @@ """ import logging + from portal.libs.agave.exceptions import CreationError # pylint: disable=invalid-name @@ -11,7 +12,7 @@ # pylint: enable=invalid-name -class Permission(object): +class Permission: """A single permission""" READ = "READ" @@ -58,14 +59,13 @@ def to_dict(self): def __str__(self): """String -> self.username [R,W,E]""" - return "{username} {recursive}[{read}, {write}, {execute}]".format( - username=self.username, recursive=self.recursive, read=self.read, write=self.write, execute=self.execute - ) + return f"{self.username} {self.recursive}[{self.read}, {self.write}, {self.execute}]" def __repr__(self): """Repr -> Permissions(username, R, W, E)""" - return ("Permissions({username},recursive={recursive},read={read},write={write},execute={execute})").format( - username=self.username, recursive=self.recursive, read=self.read, write=self.write, execute=self.execute + return ( + f"Permissions({self.username},recursive={self.recursive},read={self.read}," + f"write={self.write},execute={self.execute})" ) def __eq__(self, other): @@ -79,7 +79,7 @@ def __eq__(self, other): ) -class Permissions(object): +class Permissions: """Permissions""" def __init__(self, client, permissions): @@ -173,7 +173,7 @@ def __init__(self, client, permissions, parent): agave's pems endpoint. :param parent: Agave File object. """ - super(FilePermissions, self).__init__(client, permissions) + super().__init__(client, permissions) self.parent = parent def save(self): @@ -198,7 +198,7 @@ def __init__(self, client, permissions, parent): agave's pems endpoint. :param parent: Agave Metadata object. """ - super(MetadataPermissions, self).__init__(client, permissions) + super().__init__(client, permissions) self.parent = parent def save(self): @@ -223,7 +223,7 @@ def __init__(self, client, permissions, parent): agave's pems endpoint. :param parent: Agave Application object. """ - super(ApplicationPermissions, self).__init__(client, permissions) + super().__init__(client, permissions) self.parent = parent def save(self): diff --git a/server/portal/libs/agave/operations.py b/server/portal/libs/agave/operations.py index 48e3ffb753..650c99a0d0 100644 --- a/server/portal/libs/agave/operations.py +++ b/server/portal/libs/agave/operations.py @@ -1,19 +1,22 @@ -import os import io +import logging +import os +import urllib +from pathlib import Path from urllib.parse import quote + +import httpx from django.conf import settings from django.db import transaction -import logging -import urllib from elasticsearch_dsl import Q -import httpx -from portal.libs.elasticsearch.indexes import IndexedFile -from portal.apps.search.tasks import tapis_indexer, tapis_listing_indexer -from portal.exceptions.api import ApiException -from portal.libs.agave.utils import text_preview, get_file_size, increment_file_name -from portal.libs.agave.filter_mapping import filter_mapping -from pathlib import Path from tapipy.errors import BaseTapyException + +from portal.apps.projects.schema_models import constants +from portal.apps.projects.workspace_operations.graph_operations import ( + get_node_from_path, + get_or_create_trash_entity, + get_root_node, +) from portal.apps.projects.workspace_operations.project_meta_operations import ( add_file_associations, create_file_obj, @@ -25,13 +28,11 @@ patch_file_association, remove_file_obj_by_path, ) -from portal.apps.projects.schema_models import constants -from portal.apps.projects.workspace_operations.graph_operations import ( - get_or_create_trash_entity, - get_root_node, - get_node_from_path, -) - +from portal.apps.search.tasks import tapis_indexer, tapis_listing_indexer +from portal.exceptions.api import ApiException +from portal.libs.agave.filter_mapping import filter_mapping +from portal.libs.agave.utils import get_file_size, increment_file_name, text_preview +from portal.libs.elasticsearch.indexes import IndexedFile logger = logging.getLogger(__name__) @@ -468,9 +469,7 @@ def copy(client, src_system, src_path, dest_system, dest_path, file_name=None, m def makepublic(client, src_system, src_path, dest_path="/", *args, **kwargs): - dest_system = next( - (sys["system"] for sys in settings.PORTAL_DATAFILES_STORAGE_SYSTEMS if sys["scheme"] == "public") - ) + dest_system = next(sys["system"] for sys in settings.PORTAL_DATAFILES_STORAGE_SYSTEMS if sys["scheme"] == "public") return copy(client, src_system, src_path, dest_system, dest_path, *args, **kwargs) @@ -663,11 +662,11 @@ def preview(client, system, path, max_uses=3, lifetime=600, **kwargs): file_type = "object" elif file_ext in settings.SUPPORTED_MS_OFFICE: file_type = "ms-office" - url = "https://view.officeapps.live.com/op/view.aspx?src={}".format(url) + url = f"https://view.officeapps.live.com/op/view.aspx?src={url}" elif file_ext in settings.SUPPORTED_IPYNB_PREVIEW_EXTS: file_type = "ipynb" tmp = url.replace("https://", "") - url = "https://nbviewer.jupyter.org/urls/{tmp}".format(tmp=tmp) + url = f"https://nbviewer.jupyter.org/urls/{tmp}" elif file_ext in settings.SUPPORTED_NEW_WINDOW_PREVIEW_EXTS: error = "This file type must be previewed in a new window." else: diff --git a/server/portal/libs/agave/operations_unit_test.py b/server/portal/libs/agave/operations_unit_test.py index 6e48dd3767..6de37a4e9f 100644 --- a/server/portal/libs/agave/operations_unit_test.py +++ b/server/portal/libs/agave/operations_unit_test.py @@ -1,10 +1,12 @@ -from mock import patch, MagicMock +from unittest.mock import MagicMock, patch + from django.test import TestCase -from tapipy.tapis import TapisResult from elasticsearch_dsl import Q from elasticsearch_dsl.response import Hit -from portal.libs.agave.operations import listing, search, mkdir, move, copy, rename, makepublic +from tapipy.tapis import TapisResult + from portal.exceptions.api import ApiException +from portal.libs.agave.operations import copy, listing, makepublic, mkdir, move, rename, search class TestOperations(TestCase): @@ -79,7 +81,7 @@ def test_search(self, mock_search, mock_listing): mock_search().query().filter.assert_called_with(~Q("query_string", query="\\/.Trash\\/", fields=["path"])) mock_search().query().filter().filter.assert_called_with("prefix", **{"path._exact": "path"}) mock_search().query().filter().filter().filter.assert_called_with("term", **{"system._exact": "test.system"}) - mock_search().query().filter().filter().filter().extra.assert_called_with(from_=int(0), size=int(100)) + mock_search().query().filter().filter().filter().extra.assert_called_with(from_=0, size=100) self.assertEqual( search_res, {"listing": [{"system": "test.system", "path": "/path/to/file"}], "reachedEnd": True, "count": 1}, diff --git a/server/portal/libs/agave/serializers.py b/server/portal/libs/agave/serializers.py index ec8a2c66e4..e869f74d87 100644 --- a/server/portal/libs/agave/serializers.py +++ b/server/portal/libs/agave/serializers.py @@ -4,8 +4,9 @@ wrapps an agave object into a dict. """ -import logging import json +import logging + from tapipy.tapis import TapisResult # pylint: disable=invalid-name diff --git a/server/portal/libs/agave/unit_test.py b/server/portal/libs/agave/unit_test.py index 98b36f88aa..54a9163299 100644 --- a/server/portal/libs/agave/unit_test.py +++ b/server/portal/libs/agave/unit_test.py @@ -3,16 +3,18 @@ :synopsis: Unit tests for Agave libraries. """ +import copy +import json import logging import os -import json -import copy -from mock import patch, call -from django.test import TestCase +from unittest.mock import call, patch + from django.conf import settings -from portal.libs.agave import utils as AgaveUtils +from django.test import TestCase from tapipy.tapis import TapisResult +from portal.libs.agave import utils as AgaveUtils + # pylint: disable=invalid-name logger = logging.getLogger(__name__) # pylint: enable=invalid-name @@ -23,7 +25,7 @@ class TestAgaveUtils(TestCase): @classmethod def setUpClass(cls): - super(TestAgaveUtils, cls).setUpClass() + super().setUpClass() cls.magave_patcher = patch("portal.apps.auth.models.TapisOAuthToken.client", autospec=True) cls.magave = cls.magave_patcher.start() diff --git a/server/portal/libs/agave/utils.py b/server/portal/libs/agave/utils.py index ba77bc81bf..5e5e0f6f7c 100644 --- a/server/portal/libs/agave/utils.py +++ b/server/portal/libs/agave/utils.py @@ -5,9 +5,10 @@ import logging import os + +import requests from django.conf import settings from tapipy.tapis import Tapis -import requests logger = logging.getLogger(__name__) @@ -116,8 +117,7 @@ def walk_levels(client, system, path, bottom_up=False, ignore_hidden=False): if not bottom_up: yield (path, folders, files) for child in folders: - for child_path, child_folders, child_files in walk_levels(client, system, child["path"], bottom_up=bottom_up): - yield (child_path, child_folders, child_files) + yield from walk_levels(client, system, child["path"], bottom_up=bottom_up) if bottom_up: yield (path, folders, files) @@ -168,13 +168,13 @@ def increment_file_name(listing, file_name): inc = 1 _ext = os.path.splitext(file_name)[1] _name = os.path.splitext(file_name)[0] - _inc = "({})".format(inc) - file_name = "{}{}{}".format(_name, _inc, _ext) + _inc = f"({inc})" + file_name = f"{_name}{_inc}{_ext}" while any(x.name for x in listing if x.name == file_name): inc += 1 - _inc = "({})".format(inc) - file_name = "{}{}{}".format(_name, _inc, _ext) + _inc = f"({inc})" + file_name = f"{_name}{_inc}{_ext}" return file_name diff --git a/server/portal/libs/agave/utils_unit_test.py b/server/portal/libs/agave/utils_unit_test.py index 95b46e1b02..316d2f52a0 100644 --- a/server/portal/libs/agave/utils_unit_test.py +++ b/server/portal/libs/agave/utils_unit_test.py @@ -1,7 +1,8 @@ import pytest -from portal.libs.agave.utils import text_preview, get_file_size from tapipy.tapis import TapisResult +from portal.libs.agave.utils import get_file_size, text_preview + POSTIT_URL = "https://tapis.example/postit/something" diff --git a/server/portal/libs/elasticsearch/analyzers.py b/server/portal/libs/elasticsearch/analyzers.py index 75e99857f5..f23361399c 100644 --- a/server/portal/libs/elasticsearch/analyzers.py +++ b/server/portal/libs/elasticsearch/analyzers.py @@ -4,8 +4,8 @@ """ import logging -from elasticsearch_dsl import analyzer, token_filter, tokenizer +from elasticsearch_dsl import analyzer, token_filter, tokenizer logger = logging.getLogger(__name__) diff --git a/server/portal/libs/elasticsearch/docs/base.py b/server/portal/libs/elasticsearch/docs/base.py index 2020e4228f..26d4f56459 100644 --- a/server/portal/libs/elasticsearch/docs/base.py +++ b/server/portal/libs/elasticsearch/docs/base.py @@ -3,15 +3,17 @@ :synopsis: Wrapper classes for ES different doc types. """ -import logging import datetime +import logging + from django.conf import settings from elasticsearch import Elasticsearch -from elasticsearch_dsl import Document, Date, Object, Text, Long, Boolean, Keyword +from elasticsearch_dsl import Boolean, Date, Document, Keyword, Long, Object, Text + from portal.libs.elasticsearch.analyzers import ( - path_analyzer, file_analyzer, file_pattern_analyzer, + path_analyzer, reverse_file_analyzer, ) from portal.libs.elasticsearch.utils import file_uuid_sha256, get_sha256_hash @@ -81,14 +83,14 @@ def save(self, *args, **kwargs): Sets `lastUpdated` attribute on save. Otherwise see elasticsearch_dsl.Document.save() """ self.lastUpdated = datetime.datetime.now() - return super(IndexedFile, self).save(*args, **kwargs) + return super().save(*args, **kwargs) def update(self, *args, **kwargs): """ Sets `lastUpdated` attribute on save. Otherwise see elasticsearch_dsl.Document.update() """ lastUpdated = datetime.datetime.now() - return super(IndexedFile, self).update(lastUpdated=lastUpdated, *args, **kwargs) + return super().update(lastUpdated=lastUpdated, *args, **kwargs) @classmethod def from_path(cls, system, path): diff --git a/server/portal/libs/elasticsearch/docs/unit_test.py b/server/portal/libs/elasticsearch/docs/unit_test.py index faf3d74f13..ca9bfc9092 100644 --- a/server/portal/libs/elasticsearch/docs/unit_test.py +++ b/server/portal/libs/elasticsearch/docs/unit_test.py @@ -1,6 +1,8 @@ -from mock import patch, MagicMock +from unittest.mock import MagicMock, patch + from django.test import TestCase -from portal.libs.elasticsearch.docs.base import IndexedFile, IndexedAllocation, IndexedProject + +from portal.libs.elasticsearch.docs.base import IndexedAllocation, IndexedFile, IndexedProject class TestIndexedFile(TestCase): diff --git a/server/portal/libs/elasticsearch/indexes.py b/server/portal/libs/elasticsearch/indexes.py index 34795ef411..8e829c3904 100644 --- a/server/portal/libs/elasticsearch/indexes.py +++ b/server/portal/libs/elasticsearch/indexes.py @@ -3,13 +3,14 @@ :synopsis: ElasticSearch Index setup """ -from datetime import datetime import logging +from datetime import datetime + from django.conf import settings from elasticsearch_dsl import Index -from portal.libs.elasticsearch.docs.base import IndexedFile, IndexedAllocation, IndexedProject, IndexedPublication -from portal.libs.elasticsearch.analyzers import file_query_analyzer +from portal.libs.elasticsearch.analyzers import file_query_analyzer +from portal.libs.elasticsearch.docs.base import IndexedAllocation, IndexedFile, IndexedProject, IndexedPublication logger = logging.getLogger(__name__) @@ -27,7 +28,7 @@ def setup_indexes(doc_type, reindex=False, force=False): index with that alias and the provided name. """ baseName = settings.ES_INDEX_PREFIX.format(doc_type) - indexName = "{}-{}".format(baseName, index_time_string()) + indexName = f"{baseName}-{index_time_string()}" alias = baseName if reindex: alias += "-reindex" diff --git a/server/portal/libs/elasticsearch/unit_test.py b/server/portal/libs/elasticsearch/unit_test.py index 3427a62a3c..305929fd78 100644 --- a/server/portal/libs/elasticsearch/unit_test.py +++ b/server/portal/libs/elasticsearch/unit_test.py @@ -1,16 +1,17 @@ -from mock import patch, call +from unittest.mock import call, patch + from django.test import TestCase from elasticsearch_dsl import Q from elasticsearch_dsl.response.hit import Hit -from portal.libs.elasticsearch.indexes import setup_files_index, setup_projects_index, setup_indexes +from portal.libs.elasticsearch.indexes import setup_files_index, setup_indexes, setup_projects_index from portal.libs.elasticsearch.utils import ( - index_listing, - index_level, + delete_recursive, file_uuid_sha256, - walk_children, grouper, - delete_recursive, + index_level, + index_listing, + walk_children, ) diff --git a/server/portal/libs/elasticsearch/utils.py b/server/portal/libs/elasticsearch/utils.py index 5db76a63f9..a517b78251 100644 --- a/server/portal/libs/elasticsearch/utils.py +++ b/server/portal/libs/elasticsearch/utils.py @@ -2,14 +2,16 @@ Utilities to help on elastic search implementations. """ -import os -import logging import datetime +import logging +import os +from hashlib import sha256 +from itertools import zip_longest + from elasticsearch.helpers import bulk from elasticsearch_dsl import Q from elasticsearch_dsl.connections import get_connection -from hashlib import sha256 -from itertools import zip_longest + # from portal.apps.projects.models import ProjectMetadata # pylint: disable=invalid-name @@ -60,7 +62,7 @@ def file_uuid_sha256(system, path): """ if not path.startswith("/"): - path = "/{}".format(path) + path = f"/{path}" # str representation of the hash of e.g. "cep.home.user/path/to/file" return sha256((system + path).encode()).hexdigest() @@ -111,8 +113,7 @@ def walk_children(system, path, include_parent=False, recurse=False): else: search = search.filter(basepath_query) - for hit in search.scan(): - yield hit + yield from search.scan() def delete_recursive(system, path): diff --git a/server/portal/libs/exceptions.py b/server/portal/libs/exceptions.py index 5df5895c80..f7768bae7f 100644 --- a/server/portal/libs/exceptions.py +++ b/server/portal/libs/exceptions.py @@ -7,7 +7,7 @@ # pylint: disable=invalid-name logger = logging.getLogger(__name__) -METRICS = logging.getLogger("metrics.{}".format(__name__)) +METRICS = logging.getLogger(f"metrics.{__name__}") # pylint: enable=invalid-name diff --git a/server/portal/libs/files/file_processing.py b/server/portal/libs/files/file_processing.py index d2635f30a8..4927797a6a 100644 --- a/server/portal/libs/files/file_processing.py +++ b/server/portal/libs/files/file_processing.py @@ -1,12 +1,13 @@ -import numpy as np +import csv import io -import os import logging -from matplotlib import pyplot as plt -import csv -import matplotlib.animation as anim +import os import tempfile + +import matplotlib.animation as anim +import numpy as np import tifffile as tiff +from matplotlib import pyplot as plt from PIL import Image logger = logging.getLogger(__name__) diff --git a/server/portal/libs/googledrive/files.py b/server/portal/libs/googledrive/files.py index e8d324b3c8..8d3a978859 100644 --- a/server/portal/libs/googledrive/files.py +++ b/server/portal/libs/googledrive/files.py @@ -3,7 +3,7 @@ logger = logging.getLogger(__name__) -class GoogleDriveFile(object): +class GoogleDriveFile: """Represents a google drive file""" SUPPORTED_IMAGE_PREVIEW_EXTS = [ @@ -147,12 +147,12 @@ def name(self): def path(self): if self._parent: if self._parent.name == "My Drive": - path = "/{}".format(self.name) + path = f"/{self.name}" else: path = "/".join([self._parent.path, self.name]) elif "parents" in self._item: parent = self - path = "{}".format(self.name) + path = f"{self.name}" while True: try: self._path_collection.insert( @@ -164,7 +164,7 @@ def path(self): .execute() ) parent_name = "" if parent.name == "My Drive" else parent.name - path = "{}/{}".format(parent_name, path) + path = f"{parent_name}/{path}" except (AttributeError, KeyError): break else: diff --git a/server/portal/libs/googledrive/operations.py b/server/portal/libs/googledrive/operations.py index 575b5dad85..67edfd20cc 100644 --- a/server/portal/libs/googledrive/operations.py +++ b/server/portal/libs/googledrive/operations.py @@ -1,9 +1,9 @@ -import os import io -import magic import logging -from googleapiclient.http import MediaIoBaseUpload, MediaIoBaseDownload +import os +import magic +from googleapiclient.http import MediaIoBaseDownload, MediaIoBaseUpload logger = logging.getLogger(__name__) @@ -15,8 +15,8 @@ def listing(client, system, path, offset=None, limit=100, nextPageToken=None, *a listing_call = ( client.files() .list( - q="'{}' in parents and trashed=False".format(path), - fields="files({}), nextPageToken".format(fields), + q=f"'{path}' in parents and trashed=False", + fields=f"files({fields}), nextPageToken", pageSize=limit, pageToken=nextPageToken, ) @@ -53,10 +53,8 @@ def search(client, system, path, offset=None, limit=100, nextPageToken=None, que listing_call = ( client.files() .list( - q=("'{path}' in parents and trashed=False and name contains '{query_string}'").format( - path=path, query_string=query_string - ), - fields="files({}), nextPageToken".format(fields), + q=(f"'{path}' in parents and trashed=False and name contains '{query_string}'"), + fields=f"files({fields}), nextPageToken", pageSize=limit, pageToken=nextPageToken, ) diff --git a/server/portal/libs/googledrive/operations_unit_test.py b/server/portal/libs/googledrive/operations_unit_test.py index db1979ada1..be8d37b3a6 100644 --- a/server/portal/libs/googledrive/operations_unit_test.py +++ b/server/portal/libs/googledrive/operations_unit_test.py @@ -1,6 +1,7 @@ -import pytest import io -from mock import MagicMock +from unittest.mock import MagicMock + +import pytest @pytest.fixture diff --git a/server/portal/libs/transfer/operations_unit_test.py b/server/portal/libs/transfer/operations_unit_test.py index b4cd6f36b3..03f3f1699c 100644 --- a/server/portal/libs/transfer/operations_unit_test.py +++ b/server/portal/libs/transfer/operations_unit_test.py @@ -1,6 +1,7 @@ -import pytest import io -from mock import MagicMock, call +from unittest.mock import MagicMock, call + +import pytest @pytest.fixture diff --git a/server/portal/middleware.py b/server/portal/middleware.py index eb3e2864e0..5c342ba503 100644 --- a/server/portal/middleware.py +++ b/server/portal/middleware.py @@ -1,8 +1,9 @@ -from django.contrib import messages +import logging + from django.conf import settings -from termsandconditions.models import TermsAndConditions +from django.contrib import messages from termsandconditions.middleware import TermsAndConditionsRedirectMiddleware, is_path_protected -import logging +from termsandconditions.models import TermsAndConditions logger = logging.getLogger(__name__) @@ -28,9 +29,9 @@ def process_request(self, request): request, "

Please Accept the Terms of Use

" "You have not yet agreed to the current Terms of Use. " - 'Please CLICK HERE to review and ' + f'Please CLICK HERE to review and ' "accept the Terms of Use.
Acceptance of the Terms of " "Use is required for continued use of the portal " - "resources." % accept_url, + "resources.", ) return None diff --git a/server/portal/settings/settings.py b/server/portal/settings/settings.py index c607f6919e..5d0b90c498 100644 --- a/server/portal/settings/settings.py +++ b/server/portal/settings/settings.py @@ -10,11 +10,13 @@ https://docs.djangoproject.com/en/2.2/ref/settings/ """ +import logging import os import uuid -import logging from hashlib import sha256 + from kombu import Exchange, Queue + from portal.settings import settings_secret logger = logging.getLogger(__file__) @@ -318,7 +320,8 @@ def portal_filter(record): "metrics": { "format": "[METRICS] %(levelname)s %(module)s %(name)s.%(funcName)s:%(lineno)s:" " %(message)s user=%(user)s ip=%(ip)s agent=%(agent)s sessionId=%(sessionId)s op=%(operation)s" - " info=%(info)s timestamp=%(asctime)s trackingId=portals.%(sessionId)s guid=%(logGuid)s portal=%(portal)s tenant=%(tenant)s" + " info=%(info)s timestamp=%(asctime)s trackingId=portals.%(sessionId)s guid=%(logGuid)s" + " portal=%(portal)s tenant=%(tenant)s" }, }, "handlers": { diff --git a/server/portal/settings/unit_test_settings.py b/server/portal/settings/unit_test_settings.py index 3e01e885ee..5910b5f300 100644 --- a/server/portal/settings/unit_test_settings.py +++ b/server/portal/settings/unit_test_settings.py @@ -12,7 +12,6 @@ import os - # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) diff --git a/server/portal/urls.py b/server/portal/urls.py index cf0d26d509..0c2473b924 100644 --- a/server/portal/urls.py +++ b/server/portal/urls.py @@ -18,17 +18,17 @@ """ from django.conf import settings -from django.contrib import admin from django.conf.urls.static import static +from django.contrib import admin from django.contrib.staticfiles.urls import staticfiles_urlpatterns -from portal.apps.auth.views import tapis_oauth as login -from portal.views.views import project_version as portal_version +from django.urls import include, path, re_path from django.views.generic import RedirectView from django.views.generic.base import TemplateView -from django.urls import path, re_path, include from impersonate import views as impersonate_views -from portal.views.views import health_check -from portal.views.views import serve_docs + +from portal.apps.auth.views import tapis_oauth as login +from portal.views.views import health_check, serve_docs +from portal.views.views import project_version as portal_version admin.autodiscover() diff --git a/server/portal/utils/decorators.py b/server/portal/utils/decorators.py index f5246c1d51..d071b280b3 100644 --- a/server/portal/utils/decorators.py +++ b/server/portal/utils/decorators.py @@ -6,9 +6,10 @@ import logging import time from functools import wraps + from django.http import JsonResponse -from portal.utils.jwt_auth import login_user_agave_jwt +from portal.utils.jwt_auth import login_user_agave_jwt logger = logging.getLogger(__name__) diff --git a/server/portal/utils/encryption.py b/server/portal/utils/encryption.py index 6e61b5f70e..f7c51286e0 100644 --- a/server/portal/utils/encryption.py +++ b/server/portal/utils/encryption.py @@ -3,12 +3,13 @@ :synopsis: Utilities to handle encryption and ssh keys """ -import logging import base64 -from Crypto.PublicKey import RSA +import logging + +from Crypto import Random from Crypto.Cipher import AES from Crypto.Hash import SHA256 -from Crypto import Random +from Crypto.PublicKey import RSA from django.conf import settings # pylint: disable=invalid-name diff --git a/server/portal/utils/exceptions.py b/server/portal/utils/exceptions.py index fb73a3b0a9..1c840dca2a 100644 --- a/server/portal/utils/exceptions.py +++ b/server/portal/utils/exceptions.py @@ -33,7 +33,7 @@ class PortalException(RequestException): """ def __init__(self, message=None, status=None, extra=None, *args, **kwargs): - super(PortalException, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) response = self.response or Response() response.status_code = status or response.status_code response.reason = message or response.reason @@ -46,4 +46,4 @@ class ApiMethodNotAllowed(PortalException): """Custom 405 Method Not Allowed Exception""" def __init__(self, extra=None, *args, **kwargs): - super(ApiMethodNotAllowed, self).__init__(message="Method Not Allowed", status=405, extra=extra) + super().__init__(message="Method Not Allowed", status=405, extra=extra) diff --git a/server/portal/utils/fields.py b/server/portal/utils/fields.py index 4645da92c3..8cf9bb130e 100644 --- a/server/portal/utils/fields.py +++ b/server/portal/utils/fields.py @@ -4,7 +4,6 @@ from django.contrib.postgres.fields import JSONField as DjangoJSONField from django.db.models import Field - # NOTE: TODO_django: Django v3 introduces a native JSONField, so this file will be obsolete. # https://docs.djangoproject.com/en/3.2/ref/models/fields/#django.db.models.JSONField diff --git a/server/portal/utils/jwt_auth.py b/server/portal/utils/jwt_auth.py index 32b35802e0..b454ff7a68 100644 --- a/server/portal/utils/jwt_auth.py +++ b/server/portal/utils/jwt_auth.py @@ -5,15 +5,14 @@ import logging from base64 import b64decode -from six import text_type -from django.conf import settings -from django.contrib.auth import get_user_model -from django.contrib.auth import login -from django.core.exceptions import ObjectDoesNotExist + import jwt as pyjwt -from cryptography.hazmat.primitives.serialization import load_der_public_key from cryptography.exceptions import UnsupportedAlgorithm - +from cryptography.hazmat.primitives.serialization import load_der_public_key +from django.conf import settings +from django.contrib.auth import get_user_model, login +from django.core.exceptions import ObjectDoesNotExist +from six import text_type LOGGER = logging.getLogger(__name__) diff --git a/server/portal/utils/translations.py b/server/portal/utils/translations.py index 57f8be959a..0a649b4dae 100644 --- a/server/portal/utils/translations.py +++ b/server/portal/utils/translations.py @@ -1,10 +1,9 @@ +import copy import logging -import urllib.request -import urllib.parse import urllib.error +import urllib.parse +import urllib.request from urllib.parse import urlparse -import copy - LOGGER = logging.getLogger(__name__) @@ -19,7 +18,7 @@ def url_parse_inputs(job): if isinstance(value, str): parsed = urlparse(value) if parsed.scheme: - job["inputs"][key] = "{}://{}{}".format(parsed.scheme, parsed.netloc, urllib.parse.quote(parsed.path)) + job["inputs"][key] = f"{parsed.scheme}://{parsed.netloc}{urllib.parse.quote(parsed.path)}" else: job["inputs"][key] = urllib.parse.quote(parsed.path) else: @@ -27,7 +26,7 @@ def url_parse_inputs(job): parsed_values = [] for input in value: parsed = urlparse(input) - input = "{}://{}{}".format(parsed.scheme, parsed.netloc, urllib.parse.quote(parsed.path)) + input = f"{parsed.scheme}://{parsed.netloc}{urllib.parse.quote(parsed.path)}" parsed_values.append(input) job["inputs"][key] = parsed_values return job diff --git a/server/portal/utils/unit_test.py b/server/portal/utils/unit_test.py index 5747537030..c7aa4c0dc2 100644 --- a/server/portal/utils/unit_test.py +++ b/server/portal/utils/unit_test.py @@ -1,11 +1,13 @@ -from django.test import TestCase, override_settings -from mock import patch, Mock -from django.contrib.auth.models import Group +from datetime import timedelta +from unittest.mock import Mock, patch + from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group +from django.test import TestCase, override_settings + from portal.utils import check_group_membership -from portal.utils.translations import url_parse_inputs from portal.utils.jwt_auth import login_user_agave_jwt -from datetime import timedelta +from portal.utils.translations import url_parse_inputs class TestTranslations(TestCase): @@ -15,7 +17,7 @@ class TestTranslations(TestCase): def setUp(self): """Setup.""" - super(TestTranslations, self).setUp() + super().setUp() self.user = get_user_model().objects.get(username="username") self.job = { "inputs": { diff --git a/server/portal/views/base.py b/server/portal/views/base.py index 30d849c3e1..aecac74c0c 100644 --- a/server/portal/views/base.py +++ b/server/portal/views/base.py @@ -4,13 +4,15 @@ """ import logging -from requests.exceptions import ConnectionError, HTTPError -from django.views.generic import View -from django.http import JsonResponse, Http404 + from django.core.exceptions import PermissionDenied -from portal.exceptions.api import ApiException +from django.http import Http404, JsonResponse +from django.views.generic import View +from requests.exceptions import ConnectionError, HTTPError from tapipy.errors import BaseTapyException +from portal.exceptions.api import ApiException + logger = logging.getLogger(__name__) @@ -26,7 +28,7 @@ def dispatch(self, request, *args, **kwargs): to be available in the logs and in Opbeat's UI. """ try: - return super(BaseApiView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) except (PermissionDenied, Http404) as e: # log information but re-raise exception to let django handle response logger.error(e, exc_info=True) diff --git a/server/portal/views/unit_test.py b/server/portal/views/unit_test.py index a9b120704a..ef19578d9b 100644 --- a/server/portal/views/unit_test.py +++ b/server/portal/views/unit_test.py @@ -1,11 +1,12 @@ +import json + import pytest -from django.core.exceptions import PermissionDenied, ObjectDoesNotExist +import requests +from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.http import Http404 + from portal.exceptions.api import ApiException from portal.libs.exceptions import PortalLibException -import requests -import json - # route to be used for testing purposes API_ROUTE = "/api/system-monitor/" @@ -83,7 +84,7 @@ def test_connectionerror_httperror_with_response(ExceptionClass, status_code, cl @pytest.mark.parametrize("status_code", [403, 404, NON_403_404]) def test_connectionerror_httperror_non_json_content(ExceptionClass, status_code, client, api_method_mock): test_response = requests.Response() - test_response._content = "Non json error content".encode("utf-8") + test_response._content = b"Non json error content" test_response.status_code = status_code api_method_mock.side_effect = requests.exceptions.HTTPError(response=test_response) diff --git a/server/portal/views/views.py b/server/portal/views/views.py index c5643b08eb..c4d0083a60 100644 --- a/server/portal/views/views.py +++ b/server/portal/views/views.py @@ -1,9 +1,10 @@ import logging import os -from django.views.static import serve + from django.conf import settings from django.contrib.auth.decorators import login_required -from django.http import HttpResponse, JsonResponse, Http404 +from django.http import Http404, HttpResponse, JsonResponse +from django.views.static import serve logger = logging.getLogger(__name__) @@ -16,13 +17,13 @@ def project_version(request): if "ref:" in head: # we're on a branch branch = head.split(":")[1].strip() - with open(".git/{0}".format(branch)) as f: - version = "{}:{}".format(branch, f.readline()) + with open(f".git/{branch}") as f: + version = f"{branch}:{f.readline()}" else: # we're in a detached head, e.g., a tag. would be nice to show tag name... version = head - except IOError: + except OSError: logger.warning("Unable to read project version from git HEAD") version = "UNKNOWN" diff --git a/server/portal/wsgi.py b/server/portal/wsgi.py index 6d1f9a340c..355e780a79 100644 --- a/server/portal/wsgi.py +++ b/server/portal/wsgi.py @@ -8,6 +8,7 @@ """ import os + from django.core.wsgi import get_wsgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "portal.settings.settings") From c1b9355129003535ab41ea23814ceff4063e9d0b Mon Sep 17 00:00:00 2001 From: Sal Tijerina Date: Thu, 3 Sep 2026 08:20:57 -0500 Subject: [PATCH 2/2] fix illegal python filenames with dashes --- README.md | 2 +- .../{reindex-files.py => reindex_files.py} | 2 +- .../search/management/commands/unit_test.py | 34 +++++++++---------- .../{import-apps.py => import_apps.py} | 0 .../{import-jobs.py => import_jobs.py} | 0 .../management/commands/unit_test.py | 6 ++-- 6 files changed, 22 insertions(+), 22 deletions(-) rename server/portal/apps/search/management/commands/{reindex-files.py => reindex_files.py} (98%) rename server/portal/apps/workspace/management/commands/{import-apps.py => import_apps.py} (100%) rename server/portal/apps/workspace/management/commands/{import-jobs.py => import_jobs.py} (100%) diff --git a/README.md b/README.md index c39e83f2a6..42ee655391 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ pre-commit run --all-files python3 manage.py migrate python3 manage.py collectstatic --noinput python3 manage.py createsuperuser # Unless you will only login with your TACC account - python3 manage.py import-apps # Add set of example apps used in Frontera portal (optional) + python3 manage.py import_apps # Add set of example apps used in Frontera portal (optional) #### Initialize the CMS in the `core_portal_cms` container: diff --git a/server/portal/apps/search/management/commands/reindex-files.py b/server/portal/apps/search/management/commands/reindex_files.py similarity index 98% rename from server/portal/apps/search/management/commands/reindex-files.py rename to server/portal/apps/search/management/commands/reindex_files.py index 590b298595..1cb81d5cb5 100644 --- a/server/portal/apps/search/management/commands/reindex-files.py +++ b/server/portal/apps/search/management/commands/reindex_files.py @@ -12,7 +12,7 @@ class Command(BaseCommand): This command reindexes all documents in the default files index in order to apply new mappings/analyzers. It does NOT crawl Agave for file metadata, it only uses data that already exists in the file index. Usage is as simple as - running `./manage.py reindex-files`. + running `./manage.py reindex_files`. This works by resetting the index aliased as settings.ES_REINDEX_INDEX_ALIAS (applying any new mappings/analyzers defined in the portal.libs.elasticsearch.docs.base.IndexedFile diff --git a/server/portal/apps/search/management/commands/unit_test.py b/server/portal/apps/search/management/commands/unit_test.py index e04d696a9f..40126e6e21 100644 --- a/server/portal/apps/search/management/commands/unit_test.py +++ b/server/portal/apps/search/management/commands/unit_test.py @@ -6,9 +6,9 @@ class TestSwapReindex(TestCase): def setUp(self): - self.patch_setup = patch("portal.apps.search.management.commands.reindex-files.setup_files_index") - self.patch_connections = patch("portal.apps.search.management.commands.reindex-files.connections") - self.patch_elasticsearch = patch("portal.apps.search.management.commands.reindex-files.elasticsearch") + self.patch_setup = patch("portal.apps.search.management.commands.reindex_files.setup_files_index") + self.patch_connections = patch("portal.apps.search.management.commands.reindex_files.connections") + self.patch_elasticsearch = patch("portal.apps.search.management.commands.reindex_files.elasticsearch") self.mock_setup = self.patch_setup.start() self.mock_connections = self.patch_connections.start() @@ -18,25 +18,25 @@ def setUp(self): self.addCleanup(self.patch_connections.stop) self.addCleanup(self.patch_elasticsearch.stop) - @patch("portal.apps.search.management.commands.reindex-files.input") + @patch("portal.apps.search.management.commands.reindex_files.input") def test_raises_when_user_does_not_proceed(self, mock_input): mock_input.return_value = "n" with self.assertRaises(SystemExit): - call_command("reindex-files") + call_command("reindex_files") - @patch("portal.apps.search.management.commands.reindex-files.Index") - @patch("portal.apps.search.management.commands.reindex-files.input") + @patch("portal.apps.search.management.commands.reindex_files.Index") + @patch("portal.apps.search.management.commands.reindex_files.input") def test_raises_exception_when_no_index(self, mock_input, mock_index): mock_input.return_value = "Y" mock_index.return_value.get_alias.return_value.keys.side_effect = Exception with self.assertRaises(SystemExit): - call_command("reindex-files") + call_command("reindex_files") - @patch("portal.apps.search.management.commands.reindex-files.Index") - @patch("portal.apps.search.management.commands.reindex-files.input") + @patch("portal.apps.search.management.commands.reindex_files.Index") + @patch("portal.apps.search.management.commands.reindex_files.input") def test_performs_reindex_from_default_to_reindex(self, mock_input, mock_index): mock_input.return_value = "Y" @@ -45,18 +45,18 @@ def test_performs_reindex_from_default_to_reindex(self, mock_input, mock_index): mock_client = MagicMock() self.mock_elasticsearch.Elasticsearch.return_value = mock_client - call_command("reindex-files") + call_command("reindex_files") self.mock_elasticsearch.helpers.reindex.assert_called_with(mock_client, "DEFAULT_NAME", "REINDEX_NAME") - @patch("portal.apps.search.management.commands.reindex-files.Index") - @patch("portal.apps.search.management.commands.reindex-files.input") + @patch("portal.apps.search.management.commands.reindex_files.Index") + @patch("portal.apps.search.management.commands.reindex_files.input") def test_performs_swap_with_correct_args(self, mock_input, mock_index): mock_input.return_value = "Y" mock_index.return_value.get_alias.return_value.keys.side_effect = [["DEFAULT_NAME"], ["REINDEX_NAME"]] - call_command("reindex-files") + call_command("reindex_files") mock_alias = { "actions": [ @@ -68,8 +68,8 @@ def test_performs_swap_with_correct_args(self, mock_input, mock_index): } self.mock_elasticsearch.Elasticsearch().indices.update_aliases.assert_called_with(mock_alias) - @patch("portal.apps.search.management.commands.reindex-files.Index") - @patch("portal.apps.search.management.commands.reindex-files.input") + @patch("portal.apps.search.management.commands.reindex_files.Index") + @patch("portal.apps.search.management.commands.reindex_files.input") def test_cleanup(self, mock_input, mock_index): mock_input.return_value = "Y" @@ -80,6 +80,6 @@ def test_cleanup(self, mock_input, mock_index): ] opts = {"cleanup": True} - call_command("reindex-files", **opts) + call_command("reindex_files", **opts) self.assertEqual(mock_index.return_value.delete.call_count, 1) diff --git a/server/portal/apps/workspace/management/commands/import-apps.py b/server/portal/apps/workspace/management/commands/import_apps.py similarity index 100% rename from server/portal/apps/workspace/management/commands/import-apps.py rename to server/portal/apps/workspace/management/commands/import_apps.py diff --git a/server/portal/apps/workspace/management/commands/import-jobs.py b/server/portal/apps/workspace/management/commands/import_jobs.py similarity index 100% rename from server/portal/apps/workspace/management/commands/import-jobs.py rename to server/portal/apps/workspace/management/commands/import_jobs.py diff --git a/server/portal/apps/workspace/management/commands/unit_test.py b/server/portal/apps/workspace/management/commands/unit_test.py index 54a441f7a7..ab1d72f040 100644 --- a/server/portal/apps/workspace/management/commands/unit_test.py +++ b/server/portal/apps/workspace/management/commands/unit_test.py @@ -13,14 +13,14 @@ class TestImportJobs(TransactionTestCase): fixtures = ["users"] def setUp(self): - self.mock_client_patcher = patch("portal.apps.workspace.management.commands.import-jobs.service_account") + self.mock_client_patcher = patch("portal.apps.workspace.management.commands.import_jobs.service_account") self.mock_client = self.mock_client_patcher.start() self.user = get_user_model().objects.get(username="username") def tearDown(self): self.mock_client_patcher.stop() - @patch("portal.apps.workspace.management.commands.import-jobs.get_user_model") + @patch("portal.apps.workspace.management.commands.import_jobs.get_user_model") def test_import(self, mock_user_model): mock_user_model.return_value.objects.all.return_value = [self.user] JobSubmission.objects.create(jobId="1234", user=self.user) @@ -29,7 +29,7 @@ def test_import(self, mock_user_model): {"id": "5678", "created": "2019-10-29T19:30:13Z"}, ] - call_command("import-jobs") + call_command("import_jobs") result = JobSubmission.objects.all().filter(user=self.user) self.assertEqual(len(result), 2)