From d65dd684565099d53d9998ace8d68f7b26529024 Mon Sep 17 00:00:00 2001 From: ckrew <153777116+ckrew@users.noreply.github.com> Date: Tue, 12 Mar 2024 12:51:02 -0500 Subject: [PATCH] Tethys Async Websocket Consumer with Permission Checks (#1012) * initial consumerbase class * new TethysAsyncWebsocketConsumer class with stubbed methods for implementation also stubbed out other custom methods just in case they are needed in the future * added tests and stubbed out methods for future development * updated docs to use new method * linted code and ran black formatter * initial restructure of authenticated websocket consumer not uses the decorator for permissions added additional args for login_required and permissions_use_or * cleaned up code and moved functions * cleaned up manual copying of class methods * more small code changes * updated docs * removed old permissions property for the docs example * black formatted and linted code * updated tests * added another permission example to the consumer decorator * updated examples in the controller decorator docstring * mocked channels db for docs * cleaned and simplified update_decorated_websocket_consumer_class code * removed unused import --- docs/conf.py | 1 + docs/tutorials/websockets.rst | 12 +- tests/unit_tests/__init__.py | 1 + .../test_tethys_apps/test_base/test_mixins.py | 256 ++++++++++++++++++ .../test_base/test_permissions.py | 32 +++ .../test_models/test_TethysApp.py | 1 + .../test_tethys_apps/test_utilities.py | 49 ++++ .../test_models/test_CondorScheduler.py | 1 + .../test_dask/test_DaskJobResult.py | 1 + .../test_dask/test_DaskScheduler.py | 1 + .../test_gizmo_options/test_base.py | 1 + tethys_apps/admin.py | 1 + tethys_apps/app_installation.py | 1 + tethys_apps/apps.py | 1 + tethys_apps/base/__init__.py | 6 +- tethys_apps/base/bokeh_handler.py | 1 + tethys_apps/base/controller.py | 29 +- tethys_apps/base/handoff.py | 9 +- tethys_apps/base/mixins.py | 161 +++++++++++ tethys_apps/base/permissions.py | 24 ++ tethys_apps/base/url_map.py | 1 + tethys_apps/context_processors.py | 1 + tethys_apps/decorators.py | 1 + tethys_apps/harvester.py | 7 +- .../management/commands/collectworkspaces.py | 1 + .../management/commands/pre_collectstatic.py | 1 + tethys_apps/management/commands/syncstores.py | 1 + .../commands/tethys_app_uninstall.py | 1 + tethys_apps/models.py | 1 + tethys_apps/static_finders.py | 1 + tethys_apps/template_loaders.py | 1 + tethys_apps/urls.py | 1 + tethys_apps/utilities.py | 36 +++ tethys_apps/views.py | 1 + tethys_cli/__init__.py | 1 + tethys_cli/db_commands.py | 1 + tethys_cli/docker_commands.py | 1 + tethys_cli/gen_commands.py | 1 + tethys_cli/settings_commands.py | 1 + tethys_compute/__init__.py | 1 + tethys_compute/admin.py | 1 + tethys_compute/apps.py | 1 + tethys_compute/job_manager.py | 1 + tethys_compute/models/__init__.py | 1 + tethys_compute/models/basic_job.py | 1 + tethys_compute/models/condor/condor_base.py | 1 + tethys_compute/models/condor/condor_job.py | 1 + tethys_compute/models/condor/condor_py_job.py | 1 + .../models/condor/condor_py_workflow.py | 1 + .../models/condor/condor_scheduler.py | 1 + .../models/condor/condor_workflow.py | 1 + .../models/condor/condor_workflow_job_node.py | 1 + .../models/condor/condor_workflow_node.py | 1 + tethys_compute/models/dask/dask_job.py | 1 + tethys_compute/models/dask/dask_scheduler.py | 1 + tethys_compute/models/scheduler.py | 1 + tethys_compute/models/tethys_job.py | 1 + tethys_compute/scheduler_manager.py | 1 + tethys_config/__init__.py | 1 + tethys_config/admin.py | 1 + tethys_config/apps.py | 1 + tethys_config/context_processors.py | 1 + tethys_config/models.py | 1 + tethys_gizmos/admin.py | 1 + tethys_gizmos/gizmo_options/__init__.py | 1 + tethys_gizmos/gizmo_options/base.py | 1 + tethys_gizmos/gizmo_options/button.py | 1 + tethys_gizmos/gizmo_options/datatable_view.py | 1 + tethys_gizmos/gizmo_options/date_picker.py | 1 + tethys_gizmos/gizmo_options/map_view.py | 1 + tethys_gizmos/gizmo_options/message_box.py | 1 + tethys_gizmos/gizmo_options/range_slider.py | 1 + tethys_gizmos/gizmo_options/select_input.py | 1 + tethys_gizmos/gizmo_options/table_view.py | 1 + tethys_gizmos/gizmo_options/text_input.py | 1 + tethys_gizmos/gizmo_options/toggle_switch.py | 1 + tethys_gizmos/templatetags/tethys_gizmos.py | 1 + tethys_gizmos/urls.py | 1 + tethys_layouts/mixins/map_layout.py | 12 +- tethys_layouts/views/map_layout.py | 1 + tethys_layouts/views/tethys_layout.py | 1 + tethys_portal/__init__.py | 1 + tethys_portal/asgi.py | 1 + tethys_portal/forms.py | 1 + tethys_portal/middleware.py | 1 + tethys_portal/urls.py | 1 + tethys_portal/utilities.py | 1 + tethys_portal/views/accounts.py | 1 + tethys_portal/views/api.py | 10 +- tethys_portal/views/error.py | 1 + tethys_portal/views/home.py | 1 + tethys_portal/views/user.py | 1 + tethys_quotas/__init__.py | 1 + tethys_quotas/admin.py | 1 + tethys_quotas/apps.py | 1 + tethys_quotas/decorators.py | 1 + tethys_quotas/handlers/base.py | 1 + tethys_quotas/handlers/workspace.py | 1 + tethys_quotas/models/__init__.py | 1 + tethys_quotas/models/entity_quota.py | 1 + tethys_quotas/models/resource_quota.py | 1 + tethys_quotas/models/tethys_app_quota.py | 1 + tethys_quotas/models/user_quota.py | 1 + tethys_quotas/utilities.py | 1 + tethys_sdk/__init__.py | 1 + tethys_sdk/app_settings.py | 1 + tethys_sdk/base.py | 5 +- tethys_sdk/compute.py | 1 + tethys_sdk/gizmos.py | 1 + tethys_sdk/handoff.py | 1 + tethys_sdk/jobs.py | 1 + tethys_sdk/layouts.py | 1 + tethys_sdk/permissions.py | 1 + tethys_sdk/services.py | 22 +- tethys_sdk/testing.py | 1 + tethys_sdk/workspaces.py | 1 + tethys_services/__init__.py | 1 + tethys_services/admin.py | 1 + tethys_services/apps.py | 1 + tethys_services/backends/arcgis_portal.py | 1 + tethys_services/backends/hydroshare.py | 1 + tethys_services/backends/hydroshare_beta.py | 1 + .../backends/hydroshare_playground.py | 1 + tethys_services/models.py | 1 + tethys_services/urls.py | 1 + tethys_services/utilities.py | 1 + tethys_services/views.py | 1 + 127 files changed, 754 insertions(+), 28 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 921e125d5..8c4738a2d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -40,6 +40,7 @@ "bokeh.server.django.consumers", "bokeh.util.compiler", "channels", + "channels.db", "channels.consumer", "conda", "conda.cli", diff --git a/docs/tutorials/websockets.rst b/docs/tutorials/websockets.rst index 1e95dcea8..bcf6ecfd8 100644 --- a/docs/tutorials/websockets.rst +++ b/docs/tutorials/websockets.rst @@ -43,11 +43,11 @@ a. Create a new file called ``consumers.py`` and add the following code: @consumer(name='dam_notification', url='dams/notifications') class NotificationsConsumer(AsyncWebsocketConsumer): - async def connect(self): - await self.accept() + + async def authorized_connect(self): print("-----------WebSocket Connected-----------") - async def disconnect(self, close_code): + async def authorized_disconnect(self, close_code): pass .. note:: @@ -94,12 +94,12 @@ a. Update the ``consumer class`` to look like this. @consumer(name='dam_notification', url='dams/notifications') class NotificationsConsumer(AsyncWebsocketConsumer): - async def connect(self): - await self.accept() + + async def authorized_connect(self): await self.channel_layer.group_add("notifications", self.channel_name) print(f"Added {self.channel_name} channel to notifications") - async def disconnect(self, close_code): + async def authorized_disconnect(self, close_code): await self.channel_layer.group_discard("notifications", self.channel_name) print(f"Removed {self.channel_name} channel from notifications") diff --git a/tests/unit_tests/__init__.py b/tests/unit_tests/__init__.py index d3ed76d4f..ea66f1d5d 100644 --- a/tests/unit_tests/__init__.py +++ b/tests/unit_tests/__init__.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + import uuid import factory from unittest import mock diff --git a/tests/unit_tests/test_tethys_apps/test_base/test_mixins.py b/tests/unit_tests/test_tethys_apps/test_base/test_mixins.py index 4bcc349a1..024d9fb5f 100644 --- a/tests/unit_tests/test_tethys_apps/test_base/test_mixins.py +++ b/tests/unit_tests/test_tethys_apps/test_base/test_mixins.py @@ -1,5 +1,7 @@ import unittest +from unittest import mock import tethys_apps.base.mixins as tethys_mixins +from ... import UserFactory class TestTethysBaseMixin(unittest.TestCase): @@ -13,3 +15,257 @@ def test_TethysBaseMixin(self): result = tethys_mixins.TethysBaseMixin() result.root_url = "test-url" self.assertEqual("test_url", result.url_namespace) + + +class TestTethysAsyncWebsocketConsumer(unittest.IsolatedAsyncioTestCase): + def setUp(self): + self.consumer = tethys_mixins.TethysAsyncWebsocketConsumerMixin() + self.consumer.accept = mock.AsyncMock() + self.consumer.permissions = ["test_permission"] + self.consumer.scope = {"user": UserFactory(), "path": "path/to/app"} + + def tearDown(self): + pass + + def test_perms_list(self): + self.assertTrue(self.consumer.perms == ["test_permission"]) + + def test_perms_none(self): + self.consumer.permissions = None + self.assertTrue(self.consumer.perms == []) + + def test_perms_str(self): + self.consumer.permissions = "test_permission,test_permission1" + self.assertTrue(self.consumer.perms == ["test_permission", "test_permission1"]) + + def test_perms_exception(self): + self.consumer.permissions = {"test": "test_permsision"} + with self.assertRaises(TypeError) as context: + self.consumer.perms + + self.assertTrue( + context.exception.args[0] + == "permissions must be a list, tuple, or comma separated string" + ) + + async def test_authorized_login_required_success(self): + self.consumer.permissions = [] + self.consumer.login_required = True + self.assertTrue(await self.consumer.authorized) + + async def test_authorized_login_required_failure(self): + self.consumer.permissions = [] + self.consumer.login_required = True + self.consumer.scope = { + "user": mock.MagicMock(is_authenticated=False), + "path": "path/to/app", + } + self.assertFalse(await self.consumer.authorized) + + @mock.patch("tethys_apps.base.mixins.scoped_user_has_permission") + async def test_authorized_permissions_and(self, mock_suhp): + self.consumer.permissions = ["test_permission", "test_permission1"] + mock_suhp.side_effect = [True, True] + self.assertTrue(await self.consumer.authorized) + + @mock.patch("tethys_apps.base.mixins.scoped_user_has_permission") + async def test_authorized_inadequate_permissions_and(self, mock_suhp): + self.consumer.permissions = ["test_permission", "test_permission1"] + mock_suhp.side_effect = [True, False] + self.assertFalse(await self.consumer.authorized) + + @mock.patch("tethys_apps.base.mixins.scoped_user_has_permission") + async def test_authorized_permissions_or(self, mock_suhp): + self.consumer.permissions = ["test_permission", "test_permission1"] + self.consumer.permissions_use_or = True + mock_suhp.side_effect = [True, False] + self.assertTrue(await self.consumer.authorized) + + @mock.patch("tethys_apps.base.mixins.scoped_user_has_permission") + async def test_authorized_inadequate_permissions_or(self, mock_suhp): + self.consumer.permissions = ["test_permission", "test_permission1"] + self.consumer.permissions_use_or = True + mock_suhp.side_effect = [False, False] + self.assertFalse(await self.consumer.authorized) + + async def test_authorized_connect(self): + await self.consumer.authorized_connect() + + async def test_unauthorized_connect(self): + await self.consumer.unauthorized_connect() + + async def test_authorized_disconnect(self): + event = {} + await self.consumer.authorized_disconnect(event) + + async def test_unauthorized_disconnect(self): + event = {} + await self.consumer.unauthorized_disconnect(event) + + @mock.patch( + "tethys_apps.base.mixins.TethysAsyncWebsocketConsumerMixin.authorized_connect" + ) + async def test_connect(self, mock_authorized_connect): + self.consumer._authorized = True + await self.consumer.connect() + self.consumer.accept.assert_called_once() + mock_authorized_connect.assert_called_once() + + @mock.patch( + "tethys_apps.base.mixins.TethysAsyncWebsocketConsumerMixin.unauthorized_connect" + ) + async def test_connect_not_authorized(self, mock_unauthorized_connect): + self.consumer._authorized = False + await self.consumer.connect() + self.consumer.accept.assert_not_called() + mock_unauthorized_connect.assert_called_once() + + @mock.patch( + "tethys_apps.base.mixins.TethysAsyncWebsocketConsumerMixin.authorized_disconnect" + ) + async def test_disconnect(self, mock_authorized_disconnect): + self.consumer._authorized = True + event = "event" + await self.consumer.disconnect(event) + mock_authorized_disconnect.assert_called_with(event) + + @mock.patch( + "tethys_apps.base.mixins.TethysAsyncWebsocketConsumerMixin.unauthorized_disconnect" + ) + async def test_disconnect_not_authorized(self, mock_unauthorized_disconnect): + self.consumer._authorized = False + event = "event" + await self.consumer.disconnect(event) + mock_unauthorized_disconnect.assert_called_once() + + +class TestTethysWebsocketConsumer(unittest.TestCase): + def setUp(self): + self.consumer = tethys_mixins.TethysWebsocketConsumerMixin() + self.consumer.accept = mock.MagicMock() + self.consumer.permissions = ["test_permission"] + self.consumer.scope = {"user": UserFactory(), "path": "path/to/app"} + + def tearDown(self): + pass + + def test_perms_list(self): + self.assertTrue(self.consumer.perms == ["test_permission"]) + + def test_perms_none(self): + self.consumer.permissions = None + self.assertTrue(self.consumer.perms == []) + + def test_perms_str(self): + self.consumer.permissions = "test_permission,test_permission1" + self.assertTrue(self.consumer.perms == ["test_permission", "test_permission1"]) + + def test_perms_exception(self): + self.consumer.permissions = {"test": "test_permsision"} + with self.assertRaises(TypeError) as context: + self.consumer.perms + + self.assertTrue( + context.exception.args[0] + == "permissions must be a list, tuple, or comma separated string" + ) + + def test_authorized_login_required_success(self): + self.consumer.permissions = [] + self.consumer.login_required = True + self.assertTrue(self.consumer.authorized) + + def test_authorized_login_required_failure(self): + self.consumer.permissions = [] + self.consumer.login_required = True + self.consumer.scope = { + "user": mock.MagicMock(is_authenticated=False), + "path": "path/to/app", + } + self.assertFalse(self.consumer.authorized) + + def test_authorized_permissions_and(self): + self.consumer.permissions = ["test_permission"] + with mock.patch( + "tethys_apps.base.mixins.scoped_user_has_permission", user_has_perms + ): + self.assertTrue(self.consumer.authorized) + + def test_authorized_inadequate_permissions_and(self): + self.consumer.permissions = ["test_permission", "test_permission1"] + with mock.patch( + "tethys_apps.base.mixins.scoped_user_has_permission", user_has_perms + ): + self.assertFalse(self.consumer.authorized) + + def test_authorized_permissions_or(self): + self.consumer.permissions = ["test_permission", "test_permission1"] + self.consumer.permissions_use_or = True + with mock.patch( + "tethys_apps.base.mixins.scoped_user_has_permission", user_has_perms + ): + self.assertTrue(self.consumer.authorized) + + def test_authorized_inadequate_permissions_or(self): + self.consumer.permissions = ["test_permission1"] + self.consumer.permissions_use_or = True + with mock.patch( + "tethys_apps.base.mixins.scoped_user_has_permission", user_has_perms + ): + self.assertFalse(self.consumer.authorized) + + def test_authorized_connect(self): + self.consumer.authorized_connect() + + def test_unauthorized_connect(self): + self.consumer.unauthorized_connect() + + def test_authorized_disconnect(self): + event = {} + self.consumer.authorized_disconnect(event) + + def test_unauthorized_disconnect(self): + event = {} + self.consumer.unauthorized_disconnect(event) + + @mock.patch( + "tethys_apps.base.mixins.TethysWebsocketConsumerMixin.authorized_connect" + ) + def test_connect(self, mock_authorized_connect): + self.consumer._authorized = True + self.consumer.connect() + self.consumer.accept.assert_called_once() + mock_authorized_connect.assert_called_once() + + @mock.patch( + "tethys_apps.base.mixins.TethysWebsocketConsumerMixin.unauthorized_connect" + ) + def test_connect_not_authorized(self, mock_unauthorized_connect): + self.consumer._authorized = False + self.consumer.connect() + self.consumer.accept.assert_not_called() + mock_unauthorized_connect.assert_called_once() + + @mock.patch( + "tethys_apps.base.mixins.TethysWebsocketConsumerMixin.authorized_disconnect" + ) + def test_disconnect(self, mock_authorized_disconnect): + self.consumer._authorized = True + event = "event" + self.consumer.disconnect(event) + mock_authorized_disconnect.assert_called_with(event) + + @mock.patch( + "tethys_apps.base.mixins.TethysWebsocketConsumerMixin.unauthorized_disconnect" + ) + def test_disconnect_not_authorized(self, mock_unauthorized_disconnect): + self.consumer._authorized = False + event = "event" + self.consumer.disconnect(event) + mock_unauthorized_disconnect.assert_called_once() + + +def user_has_perms(_, perm): + if perm == "test_permission": + return True + return False diff --git a/tests/unit_tests/test_tethys_apps/test_base/test_permissions.py b/tests/unit_tests/test_tethys_apps/test_base/test_permissions.py index 37533a6df..bf812fc80 100644 --- a/tests/unit_tests/test_tethys_apps/test_base/test_permissions.py +++ b/tests/unit_tests/test_tethys_apps/test_base/test_permissions.py @@ -103,3 +103,35 @@ def test_has_permission_no(self, mock_app): mock_app.return_value = mock.MagicMock(package="test_package") result = tethys_permission.has_permission(request=request, perm="test_perm") self.assertFalse(result) + + +class TestAsyncPermissionGroup(unittest.IsolatedAsyncioTestCase): + def setUp(self): + self.user = UserFactory() + self.request_factory = RequestFactory() + self.name = "test_name" + self.permissions = ["foo", "bar"] + self.check_string = ''.format(self.name) + + def tearDown(self): + pass + + @mock.patch("tethys_apps.utilities.get_active_app") + async def test_scoped_user_has_permission(self, mock_app): + self.user.has_perm = mock.MagicMock(return_value=True) + scope = {"user": self.user, "path": "some/url/path"} + mock_app.return_value = mock.MagicMock(package="test_package") + result = await tethys_permission.scoped_user_has_permission( + scope=scope, perm="test_perm" + ) + self.assertTrue(result) + + @mock.patch("tethys_apps.utilities.get_active_app") + async def test_scoped_user_has_permission_no(self, mock_app): + self.user.has_perm = mock.MagicMock(return_value=False) + scope = {"user": self.user, "path": "some/url/path"} + mock_app.return_value = mock.MagicMock(package="test_package") + result = await tethys_permission.scoped_user_has_permission( + scope=scope, perm="test_perm" + ) + self.assertFalse(result) diff --git a/tests/unit_tests/test_tethys_apps/test_models/test_TethysApp.py b/tests/unit_tests/test_tethys_apps/test_models/test_TethysApp.py index a94c665a2..078bdf270 100644 --- a/tests/unit_tests/test_tethys_apps/test_models/test_TethysApp.py +++ b/tests/unit_tests/test_tethys_apps/test_models/test_TethysApp.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + from tethys_sdk.testing import TethysTestCase from tethys_apps.models import ( TethysApp, diff --git a/tests/unit_tests/test_tethys_apps/test_utilities.py b/tests/unit_tests/test_tethys_apps/test_utilities.py index 4fa33b160..9a5152b7a 100644 --- a/tests/unit_tests/test_tethys_apps/test_utilities.py +++ b/tests/unit_tests/test_tethys_apps/test_utilities.py @@ -4,6 +4,7 @@ from tethys_sdk.testing import TethysTestCase from tethys_apps import utilities from django.core.signing import Signer +from channels.generic.websocket import AsyncWebsocketConsumer, WebsocketConsumer class TethysAppsUtilitiesTests(unittest.TestCase): @@ -1025,3 +1026,51 @@ def test_secrets_signed_unsigned_value_with_secrets( custom_secret_setting.name(), secret_signed_mock, app_target_name, False ) self.assertEqual(unsigned_secret, mock_val) + + def test_update_decorated_websocket_consumer_class(self): + class TestConsumer(WebsocketConsumer): + def authorized_connect(self): + """Connects to the websocket consumer and adds a notifications group to the channel""" + return "authorized_connect_run" + + permissions_required = ["test_permission"] + permissions_use_or = True + login_required = False + updated_class = utilities.update_decorated_websocket_consumer_class( + TestConsumer, permissions_required, permissions_use_or, login_required + ) + + self.assertTrue(updated_class.permissions == permissions_required) + self.assertTrue(updated_class.permissions_use_or == permissions_use_or) + self.assertTrue(updated_class.login_required == login_required) + self.assertTrue( + updated_class().authorized_connect() == "authorized_connect_run" + ) + + +class TestAsyncUtilities(unittest.IsolatedAsyncioTestCase): + def set_up(self): + pass + + def tear_down(self): + pass + + async def test_update_decorated_websocket_consumer_class_async(self): + class TestConsumer(AsyncWebsocketConsumer): + async def authorized_connect(self): + """Connects to the websocket consumer and adds a notifications group to the channel""" + return "authorized_connect_run" + + permissions_required = ["test_permission"] + permissions_use_or = True + login_required = False + updated_class = utilities.update_decorated_websocket_consumer_class( + TestConsumer, permissions_required, permissions_use_or, login_required + ) + + self.assertTrue(updated_class.permissions == permissions_required) + self.assertTrue(updated_class.permissions_use_or == permissions_use_or) + self.assertTrue(updated_class.login_required == login_required) + self.assertTrue( + await updated_class().authorized_connect() == "authorized_connect_run" + ) diff --git a/tests/unit_tests/test_tethys_compute/test_models/test_CondorScheduler.py b/tests/unit_tests/test_tethys_compute/test_models/test_CondorScheduler.py index da34163c5..ec3893103 100644 --- a/tests/unit_tests/test_tethys_compute/test_models/test_CondorScheduler.py +++ b/tests/unit_tests/test_tethys_compute/test_models/test_CondorScheduler.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + from tethys_apps.base.testing.testing import TethysTestCase from tethys_compute.models import Scheduler, CondorScheduler diff --git a/tests/unit_tests/test_tethys_compute/test_models/test_dask/test_DaskJobResult.py b/tests/unit_tests/test_tethys_compute/test_models/test_dask/test_DaskJobResult.py index c14bea3fb..8b32e024a 100644 --- a/tests/unit_tests/test_tethys_compute/test_models/test_dask/test_DaskJobResult.py +++ b/tests/unit_tests/test_tethys_compute/test_models/test_dask/test_DaskJobResult.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + from tethys_sdk.testing import TethysTestCase from tethys_compute.models.dask.dask_scheduler import DaskScheduler from tethys_compute.models.dask.dask_job import DaskJob diff --git a/tests/unit_tests/test_tethys_compute/test_models/test_dask/test_DaskScheduler.py b/tests/unit_tests/test_tethys_compute/test_models/test_dask/test_DaskScheduler.py index acaa5cbf3..d2285235a 100644 --- a/tests/unit_tests/test_tethys_compute/test_models/test_dask/test_DaskScheduler.py +++ b/tests/unit_tests/test_tethys_compute/test_models/test_dask/test_DaskScheduler.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + from tethys_apps.base.testing.testing import TethysTestCase from tethys_compute.models import Scheduler, DaskScheduler from unittest import mock diff --git a/tests/unit_tests/test_tethys_gizmos/test_gizmo_options/test_base.py b/tests/unit_tests/test_tethys_gizmos/test_gizmo_options/test_base.py index 58758127c..3d680b92a 100644 --- a/tests/unit_tests/test_tethys_gizmos/test_gizmo_options/test_base.py +++ b/tests/unit_tests/test_tethys_gizmos/test_gizmo_options/test_base.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + import unittest import tethys_gizmos.gizmo_options.base as basetest diff --git a/tethys_apps/admin.py b/tethys_apps/admin.py index e8b7ad865..a9ddb27b4 100644 --- a/tethys_apps/admin.py +++ b/tethys_apps/admin.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import json import logging from django import forms diff --git a/tethys_apps/app_installation.py b/tethys_apps/app_installation.py index 4c52eb2e1..75cef1352 100644 --- a/tethys_apps/app_installation.py +++ b/tethys_apps/app_installation.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import os diff --git a/tethys_apps/apps.py b/tethys_apps/apps.py index 108cae352..3da1fd33a 100644 --- a/tethys_apps/apps.py +++ b/tethys_apps/apps.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import sys from django.apps import AppConfig diff --git a/tethys_apps/base/__init__.py b/tethys_apps/base/__init__.py index 377f89e55..f6becc4a2 100644 --- a/tethys_apps/base/__init__.py +++ b/tethys_apps/base/__init__.py @@ -7,8 +7,12 @@ * License: BSD 2-Clause ******************************************************************************** """ + # DO NOT ERASE -from tethys_apps.base.app_base import TethysAppBase, TethysExtensionBase # noqa: F401 +from tethys_apps.base.app_base import ( # noqa: F401 + TethysAppBase, + TethysExtensionBase, +) from tethys_apps.base.bokeh_handler import with_request, with_workspaces # noqa: F401 from tethys_apps.base.url_map import url_map_maker # noqa: F401 from tethys_apps.base.workspace import TethysWorkspace # noqa: F401 diff --git a/tethys_apps/base/bokeh_handler.py b/tethys_apps/base/bokeh_handler.py index cbe0c4433..c36a8a22f 100644 --- a/tethys_apps/base/bokeh_handler.py +++ b/tethys_apps/base/bokeh_handler.py @@ -6,6 +6,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + # Native Imports from functools import wraps diff --git a/tethys_apps/base/controller.py b/tethys_apps/base/controller.py index c31d1199b..74ae6cc03 100644 --- a/tethys_apps/base/controller.py +++ b/tethys_apps/base/controller.py @@ -31,7 +31,7 @@ user_workspace as user_workspace_decorator, ) from ..decorators import login_required as login_required_decorator, permission_required -from ..utilities import get_all_submodules +from ..utilities import get_all_submodules, update_decorated_websocket_consumer_class # imports for type hinting from typing import Union, Any @@ -58,6 +58,11 @@ def consumer( name: str = None, url: str = None, regex: Union[str, list, tuple] = None, + # login_required kwargs + login_required: bool = True, + # permission_required kwargs + permissions_required: Union[str, list, tuple] = None, + permissions_use_or: bool = False, ) -> Callable: """ Decorator to register a Consumer class as routed consumer endpoint @@ -88,7 +93,23 @@ class MyConsumer(AsyncWebsocketConsumer): url='customized/url', ) class MyConsumer(AsyncWebsocketConsumer): - pass + + def connect(): + pass + + ------------ + + @consumer( + name='custom_name', + url='customized/url', + permissions_required='permission', + login_required=True + ) + class MyConsumer(AsyncWebsocketConsumer): + + def authorized_connect(): + pass + """ # noqa: E501 def wrapped(function_or_class): @@ -100,6 +121,10 @@ def wrapped(function_or_class): regex=regex, ) + function_or_class = update_decorated_websocket_consumer_class( + function_or_class, permissions_required, permissions_use_or, login_required + ) + controller = function_or_class.as_asgi() _process_url_kwargs(controller, url_map_kwargs_list) return function_or_class diff --git a/tethys_apps/base/handoff.py b/tethys_apps/base/handoff.py index 3dc94403a..944605d51 100644 --- a/tethys_apps/base/handoff.py +++ b/tethys_apps/base/handoff.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import inspect import json from django.shortcuts import redirect @@ -124,10 +125,10 @@ def handoff( json.dumps(error), content_type="application/javascript" ) - error[ - "message" - ] = "HTTP 400 Bad Request: No handoff handler '{0}' for app '{1}' found.".format( - manager.app.name, handler_name + error["message"] = ( + "HTTP 400 Bad Request: No handoff handler '{0}' for app '{1}' found.".format( + manager.app.name, handler_name + ) ) return HttpResponseBadRequest( json.dumps(error), content_type="application/javascript" diff --git a/tethys_apps/base/mixins.py b/tethys_apps/base/mixins.py index 512e1d8f1..7e73f9337 100644 --- a/tethys_apps/base/mixins.py +++ b/tethys_apps/base/mixins.py @@ -1,3 +1,6 @@ +from .permissions import scoped_user_has_permission + + class TethysBaseMixin: """ Provides methods and properties common to the TethysBase and model classes. @@ -16,3 +19,161 @@ def url_namespace(self): @property def index_url(self): return f"{self.url_namespace}:{self.index}" + + +class TethysAsyncWebsocketConsumerMixin: + """ + Provides methods and properties common to Tethys async websocket consumers. + """ + + permissions = [] + permissions_use_or = False + login_required = True + _authorized = None + _perms = None + + @property + def perms(self): + if self._perms is None: + if self.permissions is None: + self._perms = [] + elif type(self.permissions) in [list, tuple]: + self._perms = self.permissions + elif isinstance(self.permissions, str): + self._perms = self.permissions.split(",") + else: + raise TypeError( + "permissions must be a list, tuple, or comma separated string" + ) + return self._perms + + @property + async def authorized(self): + if self._authorized is None: + if self.login_required and not self.scope["user"].is_authenticated: + self._authorized = False + return self._authorized + + if self.permissions_use_or: + self._authorized = False + for perm in self.perms: + if await scoped_user_has_permission(self.scope, perm): + self._authorized = True + else: + self._authorized = True + for perm in self.perms: + if not await scoped_user_has_permission(self.scope, perm): + self._authorized = False + return self._authorized + + async def authorized_connect(self): + """Custom class method to run custom code when an authorized user connects to the websocket""" + pass + + async def unauthorized_connect(self): + """Custom class method to run custom code when an unauthorized user connects to the websocket""" + pass + + async def authorized_disconnect(self, close_code): + """Custom class method to run custom code when an authorized user connects to the websocket""" + pass + + async def unauthorized_disconnect(self, close_code): + """Custom class method to run custom code when an unauthorized user connects to the websocket""" + pass + + async def connect(self): + """Class method to handle when user connects to the websocket""" + if await self.authorized: + await self.accept() + await self.authorized_connect() + else: + # User not authorized for websocket access + await self.unauthorized_connect() + + async def disconnect(self, close_code): + """Class method to handle when user disconnects to the websocket""" + if await self.authorized: + await self.authorized_disconnect(close_code) + else: + # User not authorized for websocket access + await self.unauthorized_disconnect(close_code) + + +class TethysWebsocketConsumerMixin: + """ + Provides methods and properties common to Tethys websocket consumers. + """ + + permissions = [] + permissions_use_or = False + login_required = True + _authorized = None + _perms = None + + @property + def perms(self): + if self._perms is None: + if self.permissions is None: + self._perms = [] + elif type(self.permissions) in [list, tuple]: + self._perms = self.permissions + elif isinstance(self.permissions, str): + self._perms = self.permissions.split(",") + else: + raise TypeError( + "permissions must be a list, tuple, or comma separated string" + ) + return self._perms + + @property + def authorized(self): + if self._authorized is None: + if self.login_required and not self.scope["user"].is_authenticated: + self._authorized = False + return self._authorized + + if self.permissions_use_or: + self._authorized = False + for perm in self.perms: + if scoped_user_has_permission(self.scope, perm): + self._authorized = True + else: + self._authorized = True + for perm in self.perms: + if not scoped_user_has_permission(self.scope, perm): + self._authorized = False + return self._authorized + + def authorized_connect(self): + """Custom class method to run custom code when an authorized user connects to the websocket""" + pass + + def unauthorized_connect(self): + """Custom class method to run custom code when an unauthorized user connects to the websocket""" + pass + + def authorized_disconnect(self, close_code): + """Custom class method to run custom code when an authorized user connects to the websocket""" + pass + + def unauthorized_disconnect(self, close_code): + """Custom class method to run custom code when an unauthorized user connects to the websocket""" + pass + + def connect(self): + """Class method to handle when user connects to the websocket""" + if self.authorized: + self.accept() + self.authorized_connect() + else: + # User not authorized for websocket access + self.unauthorized_connect() + + def disconnect(self, close_code): + """Class method to handle when user disconnects to the websocket""" + if self.authorized: + self.authorized_disconnect(close_code) + else: + # User not authorized for websocket access + self.unauthorized_disconnect(close_code) diff --git a/tethys_apps/base/permissions.py b/tethys_apps/base/permissions.py index d7d01a126..e85c87c63 100644 --- a/tethys_apps/base/permissions.py +++ b/tethys_apps/base/permissions.py @@ -8,6 +8,8 @@ ******************************************************************************** """ +from channels.db import database_sync_to_async + class Permission: """ @@ -139,3 +141,25 @@ def my_controller(request): if user.has_perm(namespaced_perm, app): return True return False + + +@database_sync_to_async +def scoped_user_has_permission(scope, perm): + """_summary_ + + Args: + user_id (_type_): _description_ + + Returns: + _type_: _description_ + """ + from tethys_apps.utilities import get_active_app + + request_url = scope["path"] + user = scope["user"] + + app = get_active_app(url=request_url) + + namespaced_perm = "tethys_apps." + app.package + ":" + perm + + return user.has_perm(namespaced_perm, app) diff --git a/tethys_apps/base/url_map.py b/tethys_apps/base/url_map.py index 37f9339f8..fd89546fe 100644 --- a/tethys_apps/base/url_map.py +++ b/tethys_apps/base/url_map.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + DEFAULT_EXPRESSION = r"[0-9A-Za-z-_.]+" diff --git a/tethys_apps/context_processors.py b/tethys_apps/context_processors.py index 049d3ce44..eebf6d508 100644 --- a/tethys_apps/context_processors.py +++ b/tethys_apps/context_processors.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from tethys_apps.utilities import get_active_app from tethys_portal.dependencies import vendor_static_dependencies diff --git a/tethys_apps/decorators.py b/tethys_apps/decorators.py index 12c837bb4..591ba11c9 100644 --- a/tethys_apps/decorators.py +++ b/tethys_apps/decorators.py @@ -7,6 +7,7 @@ * License: ******************************************************************************** """ + from functools import wraps from urllib.parse import urlparse diff --git a/tethys_apps/harvester.py b/tethys_apps/harvester.py index cc0ec3830..7e8d07030 100644 --- a/tethys_apps/harvester.py +++ b/tethys_apps/harvester.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import inspect import logging import pkgutil @@ -208,9 +209,9 @@ def _harvest_extension_instances(self, extension_packages): # compile valid apps if validated_ext_instance: valid_ext_instances.append(validated_ext_instance) - valid_extension_modules[ - extension_name - ] = extension_package + valid_extension_modules[extension_name] = ( + extension_package + ) # Notify user that the app has been loaded loaded_extensions.append(extension_name) diff --git a/tethys_apps/management/commands/collectworkspaces.py b/tethys_apps/management/commands/collectworkspaces.py index a673ff0be..d38881a4b 100644 --- a/tethys_apps/management/commands/collectworkspaces.py +++ b/tethys_apps/management/commands/collectworkspaces.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import os import shutil diff --git a/tethys_apps/management/commands/pre_collectstatic.py b/tethys_apps/management/commands/pre_collectstatic.py index e0cb0075e..e276a015e 100644 --- a/tethys_apps/management/commands/pre_collectstatic.py +++ b/tethys_apps/management/commands/pre_collectstatic.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import os import shutil diff --git a/tethys_apps/management/commands/syncstores.py b/tethys_apps/management/commands/syncstores.py index 6c9a5ee5f..b083e9bbb 100644 --- a/tethys_apps/management/commands/syncstores.py +++ b/tethys_apps/management/commands/syncstores.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.core.management.base import BaseCommand from tethys_cli.cli_colors import TC_BLUE, TC_WARNING, TC_ENDC diff --git a/tethys_apps/management/commands/tethys_app_uninstall.py b/tethys_apps/management/commands/tethys_app_uninstall.py index fab965052..0783ee99b 100644 --- a/tethys_apps/management/commands/tethys_app_uninstall.py +++ b/tethys_apps/management/commands/tethys_app_uninstall.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import os import site import subprocess diff --git a/tethys_apps/models.py b/tethys_apps/models.py index ae8b01df0..ad1b45610 100644 --- a/tethys_apps/models.py +++ b/tethys_apps/models.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.dispatch import receiver import logging import uuid diff --git a/tethys_apps/static_finders.py b/tethys_apps/static_finders.py index a2adcc7b3..4ea35bae1 100644 --- a/tethys_apps/static_finders.py +++ b/tethys_apps/static_finders.py @@ -7,6 +7,7 @@ * License: ******************************************************************************** """ + import os from collections import OrderedDict as SortedDict from django.contrib.staticfiles import utils diff --git a/tethys_apps/template_loaders.py b/tethys_apps/template_loaders.py index b9a962d79..5b3226b32 100644 --- a/tethys_apps/template_loaders.py +++ b/tethys_apps/template_loaders.py @@ -7,6 +7,7 @@ * License: ******************************************************************************** """ + import io import errno from django.core.exceptions import SuspiciousFileOperation diff --git a/tethys_apps/urls.py b/tethys_apps/urls.py index 64512a808..442df2ef0 100644 --- a/tethys_apps/urls.py +++ b/tethys_apps/urls.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import logging from django.urls import include, re_path from channels.routing import URLRouter diff --git a/tethys_apps/utilities.py b/tethys_apps/utilities.py index dc3c14c27..f3c4f81ac 100644 --- a/tethys_apps/utilities.py +++ b/tethys_apps/utilities.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import importlib import logging import os @@ -19,7 +20,12 @@ from django.core import signing from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from django.utils._os import safe_join +from channels.consumer import SyncConsumer +from tethys_apps.base.mixins import ( + TethysAsyncWebsocketConsumerMixin, + TethysWebsocketConsumerMixin, +) from tethys_apps.exceptions import TethysAppSettingNotAssigned from .harvester import SingletonHarvester @@ -701,3 +707,33 @@ def sign_and_unsign_secret_string(signer, value, is_signing): else: secret_unsigned = signer.unsign_object(f"{value}") return secret_unsigned + + +def update_decorated_websocket_consumer_class( + function_or_class, permissions_required, permissions_use_or, login_required +): + """Updates a given consumer class and adds the necessary properties and function for authorizing user access + depending on the other args given. + + Args: + function_or_class (class): class of the websocket consumer + permissions_required (str, list, tuple): the permissions required for user access + permissions_use_or (bool): Determines if all permissions need to be met or just one of them + login_required (bool): Determines if the user needs to be logged in to use + + Returns: + class: updated class with necessary properties and function for authorizing user access + """ + if issubclass(function_or_class, SyncConsumer): + consumer_mixin = TethysWebsocketConsumerMixin + else: + consumer_mixin = TethysAsyncWebsocketConsumerMixin + + class_bases = list(function_or_class.__bases__) + class_bases.insert(0, consumer_mixin) + function_or_class.__bases__ = tuple(class_bases) + function_or_class.permissions = permissions_required + function_or_class.permissions_use_or = permissions_use_or + function_or_class.login_required = login_required + + return function_or_class diff --git a/tethys_apps/views.py b/tethys_apps/views.py index ccbed923e..29938d139 100644 --- a/tethys_apps/views.py +++ b/tethys_apps/views.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import logging from django.shortcuts import render from django.http import HttpResponse, JsonResponse diff --git a/tethys_cli/__init__.py b/tethys_cli/__init__.py index 9dbad7c7a..397853a24 100644 --- a/tethys_cli/__init__.py +++ b/tethys_cli/__init__.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + # Commandline interface for Tethys import argparse diff --git a/tethys_cli/db_commands.py b/tethys_cli/db_commands.py index fee76b98a..dd6c424aa 100644 --- a/tethys_cli/db_commands.py +++ b/tethys_cli/db_commands.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from pathlib import Path import shutil import argparse diff --git a/tethys_cli/docker_commands.py b/tethys_cli/docker_commands.py index c78aa5b9e..b564f2b10 100644 --- a/tethys_cli/docker_commands.py +++ b/tethys_cli/docker_commands.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import os import json from abc import ABC, abstractmethod diff --git a/tethys_cli/gen_commands.py b/tethys_cli/gen_commands.py index 3d2745071..049a94006 100644 --- a/tethys_cli/gen_commands.py +++ b/tethys_cli/gen_commands.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import json import os import string diff --git a/tethys_cli/settings_commands.py b/tethys_cli/settings_commands.py index 753cfe23c..4be4c3181 100644 --- a/tethys_cli/settings_commands.py +++ b/tethys_cli/settings_commands.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from pathlib import Path from pprint import pformat from argparse import Namespace diff --git a/tethys_compute/__init__.py b/tethys_compute/__init__.py index 9918d7901..f74c0ced3 100644 --- a/tethys_compute/__init__.py +++ b/tethys_compute/__init__.py @@ -7,4 +7,5 @@ * License: BSD 2-Clause ******************************************************************************** """ + default_app_config = "tethys_compute.apps.TethysComputeConfig" diff --git a/tethys_compute/admin.py b/tethys_compute/admin.py index cd078d20f..ce4e91d25 100644 --- a/tethys_compute/admin.py +++ b/tethys_compute/admin.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.contrib import admin from django.utils.html import format_html from django import forms diff --git a/tethys_compute/apps.py b/tethys_compute/apps.py index 832ac2034..aaba8606e 100644 --- a/tethys_compute/apps.py +++ b/tethys_compute/apps.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.apps import AppConfig diff --git a/tethys_compute/job_manager.py b/tethys_compute/job_manager.py index 9c022f842..ea3aa10a3 100644 --- a/tethys_compute/job_manager.py +++ b/tethys_compute/job_manager.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import logging diff --git a/tethys_compute/models/__init__.py b/tethys_compute/models/__init__.py index 371bd881e..7865ed1ba 100644 --- a/tethys_compute/models/__init__.py +++ b/tethys_compute/models/__init__.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.dispatch import receiver from django.db.models.signals import post_save diff --git a/tethys_compute/models/basic_job.py b/tethys_compute/models/basic_job.py index a2d295f3c..8dce775b0 100644 --- a/tethys_compute/models/basic_job.py +++ b/tethys_compute/models/basic_job.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + from tethys_compute.models.tethys_job import TethysJob diff --git a/tethys_compute/models/condor/condor_base.py b/tethys_compute/models/condor/condor_base.py index 9f299b3b4..1cc5c5b3d 100644 --- a/tethys_compute/models/condor/condor_base.py +++ b/tethys_compute/models/condor/condor_base.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + import os from abc import abstractmethod from pathlib import Path diff --git a/tethys_compute/models/condor/condor_job.py b/tethys_compute/models/condor/condor_job.py index 694c44fc5..6cbf5cf97 100644 --- a/tethys_compute/models/condor/condor_job.py +++ b/tethys_compute/models/condor/condor_job.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + import shutil import logging diff --git a/tethys_compute/models/condor/condor_py_job.py b/tethys_compute/models/condor/condor_py_job.py index 6baf2e2e3..434f858fe 100644 --- a/tethys_compute/models/condor/condor_py_job.py +++ b/tethys_compute/models/condor/condor_py_job.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + from tethys_portal.optional_dependencies import optional_import import os diff --git a/tethys_compute/models/condor/condor_py_workflow.py b/tethys_compute/models/condor/condor_py_workflow.py index 4cd4769c2..4f1f7b09c 100644 --- a/tethys_compute/models/condor/condor_py_workflow.py +++ b/tethys_compute/models/condor/condor_py_workflow.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + from tethys_portal.optional_dependencies import optional_import from django.db import models diff --git a/tethys_compute/models/condor/condor_scheduler.py b/tethys_compute/models/condor/condor_scheduler.py index ef38db763..a3b5fb824 100644 --- a/tethys_compute/models/condor/condor_scheduler.py +++ b/tethys_compute/models/condor/condor_scheduler.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + from django.db import models from tethys_compute.models.scheduler import Scheduler diff --git a/tethys_compute/models/condor/condor_workflow.py b/tethys_compute/models/condor/condor_workflow.py index 68ab94020..2f55efb24 100644 --- a/tethys_compute/models/condor/condor_workflow.py +++ b/tethys_compute/models/condor/condor_workflow.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + import shutil import logging import os diff --git a/tethys_compute/models/condor/condor_workflow_job_node.py b/tethys_compute/models/condor/condor_workflow_job_node.py index 9700d9a42..d0e0ba033 100644 --- a/tethys_compute/models/condor/condor_workflow_job_node.py +++ b/tethys_compute/models/condor/condor_workflow_job_node.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + from django.db.models.signals import pre_save from django.dispatch import receiver diff --git a/tethys_compute/models/condor/condor_workflow_node.py b/tethys_compute/models/condor/condor_workflow_node.py index fef7c190c..da2560f17 100644 --- a/tethys_compute/models/condor/condor_workflow_node.py +++ b/tethys_compute/models/condor/condor_workflow_node.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + from abc import abstractmethod from django.db import models diff --git a/tethys_compute/models/dask/dask_job.py b/tethys_compute/models/dask/dask_job.py index 4f0be22a9..d08dac141 100644 --- a/tethys_compute/models/dask/dask_job.py +++ b/tethys_compute/models/dask/dask_job.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + import logging import datetime import json diff --git a/tethys_compute/models/dask/dask_scheduler.py b/tethys_compute/models/dask/dask_scheduler.py index ea751b6b5..441df0471 100644 --- a/tethys_compute/models/dask/dask_scheduler.py +++ b/tethys_compute/models/dask/dask_scheduler.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + import logging from django.db import models from tethys_compute.models.scheduler import Scheduler diff --git a/tethys_compute/models/scheduler.py b/tethys_compute/models/scheduler.py index 480bd34d8..7d8144d7b 100644 --- a/tethys_compute/models/scheduler.py +++ b/tethys_compute/models/scheduler.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + from django.db import models from model_utils.managers import InheritanceManager diff --git a/tethys_compute/models/tethys_job.py b/tethys_compute/models/tethys_job.py index 20676c75e..b71ddc106 100644 --- a/tethys_compute/models/tethys_job.py +++ b/tethys_compute/models/tethys_job.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + import logging import datetime import inspect diff --git a/tethys_compute/scheduler_manager.py b/tethys_compute/scheduler_manager.py index 33874657a..75f1e5aa4 100644 --- a/tethys_compute/scheduler_manager.py +++ b/tethys_compute/scheduler_manager.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from tethys_compute.models import Scheduler from tethys_compute.models.condor.condor_scheduler import CondorScheduler from tethys_compute.models.dask.dask_scheduler import DaskScheduler diff --git a/tethys_config/__init__.py b/tethys_config/__init__.py index d7f518841..0771ad4cd 100644 --- a/tethys_config/__init__.py +++ b/tethys_config/__init__.py @@ -7,5 +7,6 @@ * License: BSD 2-Clause ******************************************************************************** """ + # Load the custom app config default_app_config = "tethys_config.apps.TethysPortalConfig" diff --git a/tethys_config/admin.py b/tethys_config/admin.py index eb93ff469..84e587907 100644 --- a/tethys_config/admin.py +++ b/tethys_config/admin.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.contrib import admin from django.forms import Textarea from django.db import models diff --git a/tethys_config/apps.py b/tethys_config/apps.py index fd1195f62..7e5ce3a2a 100644 --- a/tethys_config/apps.py +++ b/tethys_config/apps.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.apps import AppConfig diff --git a/tethys_config/context_processors.py b/tethys_config/context_processors.py index 5b34caff8..453074a84 100644 --- a/tethys_config/context_processors.py +++ b/tethys_config/context_processors.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import datetime as dt from tethys_portal.optional_dependencies import optional_import, has_module diff --git a/tethys_config/models.py b/tethys_config/models.py index 008fa4031..30328efc6 100644 --- a/tethys_config/models.py +++ b/tethys_config/models.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.db import models diff --git a/tethys_gizmos/admin.py b/tethys_gizmos/admin.py index bd1fee0b0..41ed3b83b 100644 --- a/tethys_gizmos/admin.py +++ b/tethys_gizmos/admin.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + # from django.contrib import admin # Register your models here. diff --git a/tethys_gizmos/gizmo_options/__init__.py b/tethys_gizmos/gizmo_options/__init__.py index d8e78b1f8..3290a5b64 100644 --- a/tethys_gizmos/gizmo_options/__init__.py +++ b/tethys_gizmos/gizmo_options/__init__.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + # flake8: noqa from .date_picker import * from .button import * diff --git a/tethys_gizmos/gizmo_options/base.py b/tethys_gizmos/gizmo_options/base.py index ee0975b8f..a7989671c 100644 --- a/tethys_gizmos/gizmo_options/base.py +++ b/tethys_gizmos/gizmo_options/base.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import re diff --git a/tethys_gizmos/gizmo_options/button.py b/tethys_gizmos/gizmo_options/button.py index 4b5ec74ea..7a219deb4 100644 --- a/tethys_gizmos/gizmo_options/button.py +++ b/tethys_gizmos/gizmo_options/button.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from .base import TethysGizmoOptions __all__ = ["ButtonGroup", "Button"] diff --git a/tethys_gizmos/gizmo_options/datatable_view.py b/tethys_gizmos/gizmo_options/datatable_view.py index 1fd1a1d3f..0ba09db56 100644 --- a/tethys_gizmos/gizmo_options/datatable_view.py +++ b/tethys_gizmos/gizmo_options/datatable_view.py @@ -6,6 +6,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import re from json import dumps from tethys_portal.dependencies import vendor_static_dependencies diff --git a/tethys_gizmos/gizmo_options/date_picker.py b/tethys_gizmos/gizmo_options/date_picker.py index 1c2ee5d1e..5a8bdf49c 100644 --- a/tethys_gizmos/gizmo_options/date_picker.py +++ b/tethys_gizmos/gizmo_options/date_picker.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from .base import TethysGizmoOptions from tethys_portal.dependencies import vendor_static_dependencies diff --git a/tethys_gizmos/gizmo_options/map_view.py b/tethys_gizmos/gizmo_options/map_view.py index 57d5d1f70..d6306415e 100644 --- a/tethys_gizmos/gizmo_options/map_view.py +++ b/tethys_gizmos/gizmo_options/map_view.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import logging from tethys_portal.dependencies import vendor_static_dependencies from .base import TethysGizmoOptions, SecondaryGizmoOptions diff --git a/tethys_gizmos/gizmo_options/message_box.py b/tethys_gizmos/gizmo_options/message_box.py index a8b9fa7b0..47191e240 100644 --- a/tethys_gizmos/gizmo_options/message_box.py +++ b/tethys_gizmos/gizmo_options/message_box.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from .base import TethysGizmoOptions __all__ = ["MessageBox"] diff --git a/tethys_gizmos/gizmo_options/range_slider.py b/tethys_gizmos/gizmo_options/range_slider.py index 197da8b23..291031421 100644 --- a/tethys_gizmos/gizmo_options/range_slider.py +++ b/tethys_gizmos/gizmo_options/range_slider.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from .base import TethysGizmoOptions __all__ = ["RangeSlider"] diff --git a/tethys_gizmos/gizmo_options/select_input.py b/tethys_gizmos/gizmo_options/select_input.py index f5260cfa4..2d50127a5 100644 --- a/tethys_gizmos/gizmo_options/select_input.py +++ b/tethys_gizmos/gizmo_options/select_input.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import json from tethys_portal.dependencies import vendor_static_dependencies from .base import TethysGizmoOptions diff --git a/tethys_gizmos/gizmo_options/table_view.py b/tethys_gizmos/gizmo_options/table_view.py index d98202e97..245a26b69 100644 --- a/tethys_gizmos/gizmo_options/table_view.py +++ b/tethys_gizmos/gizmo_options/table_view.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from .base import TethysGizmoOptions __all__ = ["TableView"] diff --git a/tethys_gizmos/gizmo_options/text_input.py b/tethys_gizmos/gizmo_options/text_input.py index a4d74ef89..2c43c3634 100644 --- a/tethys_gizmos/gizmo_options/text_input.py +++ b/tethys_gizmos/gizmo_options/text_input.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from .base import TethysGizmoOptions __all__ = ["TextInput"] diff --git a/tethys_gizmos/gizmo_options/toggle_switch.py b/tethys_gizmos/gizmo_options/toggle_switch.py index ebd978dd8..a449f2b0a 100644 --- a/tethys_gizmos/gizmo_options/toggle_switch.py +++ b/tethys_gizmos/gizmo_options/toggle_switch.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from tethys_portal.dependencies import vendor_static_dependencies from .base import TethysGizmoOptions diff --git a/tethys_gizmos/templatetags/tethys_gizmos.py b/tethys_gizmos/templatetags/tethys_gizmos.py index 06eebd732..9032b40e6 100644 --- a/tethys_gizmos/templatetags/tethys_gizmos.py +++ b/tethys_gizmos/templatetags/tethys_gizmos.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import os import json import time diff --git a/tethys_gizmos/urls.py b/tethys_gizmos/urls.py index 78dc432dd..dc2c97dca 100644 --- a/tethys_gizmos/urls.py +++ b/tethys_gizmos/urls.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.urls import re_path, include from tethys_gizmos.views.gizmos import jobs_table as jobs_table_views diff --git a/tethys_layouts/mixins/map_layout.py b/tethys_layouts/mixins/map_layout.py index 4100c855c..22d20bc26 100644 --- a/tethys_layouts/mixins/map_layout.py +++ b/tethys_layouts/mixins/map_layout.py @@ -998,14 +998,14 @@ def generate_custom_color_ramp_divisions( divisions[f"{prefix}{i}"] = f"{(m * i + b):.{value_precision}f}" if color_ramp in cls.COLOR_RAMPS.keys(): - divisions[ - f"{color_prefix}{i}" - ] = f"{cls.COLOR_RAMPS[color_ramp][(i - first_division) % len(cls.COLOR_RAMPS[color_ramp])]}" + divisions[f"{color_prefix}{i}"] = ( + f"{cls.COLOR_RAMPS[color_ramp][(i - first_division) % len(cls.COLOR_RAMPS[color_ramp])]}" + ) else: # use default color ramp - divisions[ - f"{color_prefix}{i}" - ] = f"{cls.COLOR_RAMPS['Default'][(i - first_division) % len(cls.COLOR_RAMPS['Default'])]}" + divisions[f"{color_prefix}{i}"] = ( + f"{cls.COLOR_RAMPS['Default'][(i - first_division) % len(cls.COLOR_RAMPS['Default'])]}" + ) if no_data_value is not None: divisions["val_no_data"] = no_data_value return divisions diff --git a/tethys_layouts/views/map_layout.py b/tethys_layouts/views/map_layout.py index a9b18dfe4..ba0a89113 100644 --- a/tethys_layouts/views/map_layout.py +++ b/tethys_layouts/views/map_layout.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2021 ******************************************************************************** """ + from tethys_portal.optional_dependencies import optional_import from abc import ABCMeta import collections diff --git a/tethys_layouts/views/tethys_layout.py b/tethys_layouts/views/tethys_layout.py index a72213a16..b890ca93b 100644 --- a/tethys_layouts/views/tethys_layout.py +++ b/tethys_layouts/views/tethys_layout.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2021 ******************************************************************************** """ + import logging from django.http import HttpResponseNotFound, HttpResponse diff --git a/tethys_portal/__init__.py b/tethys_portal/__init__.py index 4eec5d35a..b7ef34eb3 100644 --- a/tethys_portal/__init__.py +++ b/tethys_portal/__init__.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + try: from ._version import version as __version__ from ._version import version_tuple # noqa: F401 diff --git a/tethys_portal/asgi.py b/tethys_portal/asgi.py index 153c0938c..334a2cb8e 100644 --- a/tethys_portal/asgi.py +++ b/tethys_portal/asgi.py @@ -2,6 +2,7 @@ ASGI entrypoint. Configures Django and then runs the application defined in the ASGI_APPLICATION setting. """ + import os from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter diff --git a/tethys_portal/forms.py b/tethys_portal/forms.py index 25a0e7538..cd250bd6d 100644 --- a/tethys_portal/forms.py +++ b/tethys_portal/forms.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django import forms from django.contrib.auth.models import User from django.contrib.auth.password_validation import validate_password diff --git a/tethys_portal/middleware.py b/tethys_portal/middleware.py index 7c0bf5177..52fce23d8 100644 --- a/tethys_portal/middleware.py +++ b/tethys_portal/middleware.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.conf import settings from django.contrib import messages from django.core.exceptions import PermissionDenied diff --git a/tethys_portal/urls.py b/tethys_portal/urls.py index 6beea748b..1feaa07c7 100644 --- a/tethys_portal/urls.py +++ b/tethys_portal/urls.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import logging from importlib import import_module diff --git a/tethys_portal/utilities.py b/tethys_portal/utilities.py index 16dee3e33..f6ec49d19 100644 --- a/tethys_portal/utilities.py +++ b/tethys_portal/utilities.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import datetime from uuid import UUID from django.contrib.auth import login diff --git a/tethys_portal/views/accounts.py b/tethys_portal/views/accounts.py index 752d35006..620135853 100644 --- a/tethys_portal/views/accounts.py +++ b/tethys_portal/views/accounts.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.conf import settings from django.shortcuts import render, redirect from django.contrib.auth import authenticate, login, logout diff --git a/tethys_portal/views/api.py b/tethys_portal/views/api.py index 9ab155315..27fd12d2d 100644 --- a/tethys_portal/views/api.py +++ b/tethys_portal/views/api.py @@ -57,7 +57,7 @@ def get_app(request, app): "icon": static(app.icon), "exitUrl": reverse("app_library"), "rootUrl": reverse(app.index_url), - "settingsUrl": f'{reverse("admin:index")}tethys_apps/tethysapp/{ app.id }/change/', + "settingsUrl": f'{reverse("admin:index")}tethys_apps/tethysapp/{app.id}/change/', } if request.user.is_authenticated: @@ -73,9 +73,11 @@ def get_app(request, app): pass metadata["customSettings"][s.name] = { - "type": s.type - if s.type_custom_setting == "SIMPLE" - else s.type_custom_setting, + "type": ( + s.type + if s.type_custom_setting == "SIMPLE" + else s.type_custom_setting + ), "value": v, } diff --git a/tethys_portal/views/error.py b/tethys_portal/views/error.py index 6f1a254e5..3346894ca 100644 --- a/tethys_portal/views/error.py +++ b/tethys_portal/views/error.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.shortcuts import render diff --git a/tethys_portal/views/home.py b/tethys_portal/views/home.py index 89669b78d..5e2256781 100644 --- a/tethys_portal/views/home.py +++ b/tethys_portal/views/home.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.shortcuts import render, redirect from django.conf import settings from tethys_config.models import get_custom_template diff --git a/tethys_portal/views/user.py b/tethys_portal/views/user.py index 9a2a3b01c..977bf223c 100644 --- a/tethys_portal/views/user.py +++ b/tethys_portal/views/user.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.conf import settings as django_settings from django.shortcuts import render, redirect from django.contrib.auth import logout diff --git a/tethys_quotas/__init__.py b/tethys_quotas/__init__.py index 5ec3de4b8..8e4d35f1c 100644 --- a/tethys_quotas/__init__.py +++ b/tethys_quotas/__init__.py @@ -6,4 +6,5 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + default_app_config = "tethys_quotas.apps.TethysQuotasConfig" diff --git a/tethys_quotas/admin.py b/tethys_quotas/admin.py index 221ede63a..334feb15f 100644 --- a/tethys_quotas/admin.py +++ b/tethys_quotas/admin.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + from django.urls import reverse from django.contrib.contenttypes.models import ContentType from django.utils.html import format_html diff --git a/tethys_quotas/apps.py b/tethys_quotas/apps.py index b8b78aaea..374f78cf7 100644 --- a/tethys_quotas/apps.py +++ b/tethys_quotas/apps.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + import logging from django.apps import AppConfig from django.db.utils import ProgrammingError, OperationalError diff --git a/tethys_quotas/decorators.py b/tethys_quotas/decorators.py index 56051fdca..844705216 100644 --- a/tethys_quotas/decorators.py +++ b/tethys_quotas/decorators.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + import logging from django.utils.functional import wraps from django.http import HttpRequest diff --git a/tethys_quotas/handlers/base.py b/tethys_quotas/handlers/base.py index 2a2ba97a0..1f2bcf888 100644 --- a/tethys_quotas/handlers/base.py +++ b/tethys_quotas/handlers/base.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + from abc import abstractmethod from tethys_quotas.utilities import get_resource_available diff --git a/tethys_quotas/handlers/workspace.py b/tethys_quotas/handlers/workspace.py index 9a07b29da..fe137c339 100644 --- a/tethys_quotas/handlers/workspace.py +++ b/tethys_quotas/handlers/workspace.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + from django.contrib.auth.models import User from tethys_apps.models import TethysApp from tethys_apps.base.workspace import _get_user_workspace, _get_app_workspace diff --git a/tethys_quotas/models/__init__.py b/tethys_quotas/models/__init__.py index 800718d98..343d9999a 100644 --- a/tethys_quotas/models/__init__.py +++ b/tethys_quotas/models/__init__.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from tethys_quotas.models.resource_quota import ResourceQuota # noqa: F401 from tethys_quotas.models.entity_quota import EntityQuota # noqa: F401 from tethys_quotas.models.user_quota import UserQuota # noqa: F401 diff --git a/tethys_quotas/models/entity_quota.py b/tethys_quotas/models/entity_quota.py index 7bc053f2b..84d85a331 100644 --- a/tethys_quotas/models/entity_quota.py +++ b/tethys_quotas/models/entity_quota.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + import logging from django.db import models diff --git a/tethys_quotas/models/resource_quota.py b/tethys_quotas/models/resource_quota.py index 76037614c..2bb9ddf78 100644 --- a/tethys_quotas/models/resource_quota.py +++ b/tethys_quotas/models/resource_quota.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + import logging import inspect from django.contrib.auth.models import User diff --git a/tethys_quotas/models/tethys_app_quota.py b/tethys_quotas/models/tethys_app_quota.py index 7ed15e6b4..c19ae8624 100644 --- a/tethys_quotas/models/tethys_app_quota.py +++ b/tethys_quotas/models/tethys_app_quota.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + import logging from django.db import models diff --git a/tethys_quotas/models/user_quota.py b/tethys_quotas/models/user_quota.py index 753292027..37ccbaab7 100644 --- a/tethys_quotas/models/user_quota.py +++ b/tethys_quotas/models/user_quota.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + import logging from django.db import models diff --git a/tethys_quotas/utilities.py b/tethys_quotas/utilities.py index 0cb419505..07ec08187 100644 --- a/tethys_quotas/utilities.py +++ b/tethys_quotas/utilities.py @@ -6,6 +6,7 @@ * Copyright: (c) Aquaveo 2018 ******************************************************************************** """ + import logging from django.conf import settings from django.core.exceptions import PermissionDenied diff --git a/tethys_sdk/__init__.py b/tethys_sdk/__init__.py index 569467060..eaa3077bc 100644 --- a/tethys_sdk/__init__.py +++ b/tethys_sdk/__init__.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + # flake8: noqa # This module provides a centralized location for SDK imports. from tethys_portal import __version__ diff --git a/tethys_sdk/app_settings.py b/tethys_sdk/app_settings.py index cb8583539..8f3139121 100644 --- a/tethys_sdk/app_settings.py +++ b/tethys_sdk/app_settings.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + # flake8: noqa # DO NOT ERASE from tethys_apps.models import ( diff --git a/tethys_sdk/base.py b/tethys_sdk/base.py index 11b0aa363..367f64ec7 100644 --- a/tethys_sdk/base.py +++ b/tethys_sdk/base.py @@ -8,7 +8,10 @@ # flake8: noqa # DO NOT ERASE -from tethys_apps.base import TethysAppBase, TethysExtensionBase +from tethys_apps.base import ( + TethysAppBase, + TethysExtensionBase, +) from tethys_apps.base.url_map import url_map_maker from tethys_apps.base.controller import TethysController from tethys_apps.base.bokeh_handler import with_request, with_workspaces diff --git a/tethys_sdk/compute.py b/tethys_sdk/compute.py index 327a0bf3a..f3e5ddfef 100644 --- a/tethys_sdk/compute.py +++ b/tethys_sdk/compute.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + # flake8: noqa # DO NOT ERASE from tethys_compute.scheduler_manager import ( diff --git a/tethys_sdk/gizmos.py b/tethys_sdk/gizmos.py index 1ce3abd79..582e3154d 100644 --- a/tethys_sdk/gizmos.py +++ b/tethys_sdk/gizmos.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + # flake8: noqa # DO NOT ERASE from tethys_gizmos.gizmo_options import * diff --git a/tethys_sdk/handoff.py b/tethys_sdk/handoff.py index dd4b0f39d..5cf6d0331 100644 --- a/tethys_sdk/handoff.py +++ b/tethys_sdk/handoff.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + # flake8: noqa # DO NOT ERASE from tethys_apps.base.handoff import HandoffHandler diff --git a/tethys_sdk/jobs.py b/tethys_sdk/jobs.py index 42dd8c594..65c6c9a13 100644 --- a/tethys_sdk/jobs.py +++ b/tethys_sdk/jobs.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + # flake8: noqa # DO NOT ERASE from tethys_compute.models import ( diff --git a/tethys_sdk/layouts.py b/tethys_sdk/layouts.py index 609e7abbc..7512c9173 100644 --- a/tethys_sdk/layouts.py +++ b/tethys_sdk/layouts.py @@ -6,6 +6,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + # flake8: noqa # DO NOT ERASE from tethys_layouts.views.map_layout import MapLayout diff --git a/tethys_sdk/permissions.py b/tethys_sdk/permissions.py index 1db34e771..68552b59c 100644 --- a/tethys_sdk/permissions.py +++ b/tethys_sdk/permissions.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + # flake8: noqa # DO NOT ERASE from tethys_apps.base import Permission, PermissionGroup, has_permission diff --git a/tethys_sdk/services.py b/tethys_sdk/services.py index f8dc80bba..77e0303f7 100644 --- a/tethys_sdk/services.py +++ b/tethys_sdk/services.py @@ -1 +1,21 @@ -""" ******************************************************************************** * Name: services.py * Author: Nathan Swain * Created On: 7 August 2015 * Copyright: (c) Brigham Young University 2015 * License: BSD 2-Clause ******************************************************************************** """ # flake8: noqa # DO NOT ERASE from tethys_services.utilities import ( list_dataset_engines, get_dataset_engine, list_spatial_dataset_engines, get_spatial_dataset_engine, list_wps_service_engines, get_wps_service_engine, ensure_oauth2, ) \ No newline at end of file +""" +******************************************************************************** +* Name: services.py +* Author: Nathan Swain +* Created On: 7 August 2015 +* Copyright: (c) Brigham Young University 2015 +* License: BSD 2-Clause +******************************************************************************** +""" + +# flake8: noqa +# DO NOT ERASE +from tethys_services.utilities import ( + list_dataset_engines, + get_dataset_engine, + list_spatial_dataset_engines, + get_spatial_dataset_engine, + list_wps_service_engines, + get_wps_service_engine, + ensure_oauth2, +) diff --git a/tethys_sdk/testing.py b/tethys_sdk/testing.py index 50769cc67..035fd0f5b 100644 --- a/tethys_sdk/testing.py +++ b/tethys_sdk/testing.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + # flake8: noqa # DO NOT ERASE from tethys_apps.base.testing.testing import TethysTestCase diff --git a/tethys_sdk/workspaces.py b/tethys_sdk/workspaces.py index 01afab79e..3bf487de6 100644 --- a/tethys_sdk/workspaces.py +++ b/tethys_sdk/workspaces.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + # flake8: noqa # DO NOT ERASE from tethys_apps.base.workspace import ( diff --git a/tethys_services/__init__.py b/tethys_services/__init__.py index 65d0712c2..727d2dc3b 100644 --- a/tethys_services/__init__.py +++ b/tethys_services/__init__.py @@ -7,5 +7,6 @@ * License: BSD 2-Clause ******************************************************************************** """ + # Load the custom app config default_app_config = "tethys_services.apps.TethysServicesConfig" diff --git a/tethys_services/admin.py b/tethys_services/admin.py index 5dd26a984..b8d88d90a 100644 --- a/tethys_services/admin.py +++ b/tethys_services/admin.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.contrib import admin from django.utils.translation import gettext_lazy as _ from .models import ( diff --git a/tethys_services/apps.py b/tethys_services/apps.py index 9f4d12e79..e6e5dc54c 100644 --- a/tethys_services/apps.py +++ b/tethys_services/apps.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.apps import AppConfig diff --git a/tethys_services/backends/arcgis_portal.py b/tethys_services/backends/arcgis_portal.py index 8e63a0a09..a885d8f68 100644 --- a/tethys_services/backends/arcgis_portal.py +++ b/tethys_services/backends/arcgis_portal.py @@ -6,6 +6,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from social_core.backends.arcgis import ArcGISOAuth2 from django.conf import settings diff --git a/tethys_services/backends/hydroshare.py b/tethys_services/backends/hydroshare.py index 5c09f2cfe..5eb297f24 100644 --- a/tethys_services/backends/hydroshare.py +++ b/tethys_services/backends/hydroshare.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from datetime import datetime import time from social_core.backends.oauth import BaseOAuth2 diff --git a/tethys_services/backends/hydroshare_beta.py b/tethys_services/backends/hydroshare_beta.py index 8e76fbca8..f98c13b02 100644 --- a/tethys_services/backends/hydroshare_beta.py +++ b/tethys_services/backends/hydroshare_beta.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from .hydroshare import HydroShareOAuth2 diff --git a/tethys_services/backends/hydroshare_playground.py b/tethys_services/backends/hydroshare_playground.py index 88d05ae0c..0a85c8241 100644 --- a/tethys_services/backends/hydroshare_playground.py +++ b/tethys_services/backends/hydroshare_playground.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from .hydroshare import HydroShareOAuth2 diff --git a/tethys_services/models.py b/tethys_services/models.py index 47a69863c..d678ddc9c 100644 --- a/tethys_services/models.py +++ b/tethys_services/models.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.db import models from django.core.exceptions import ObjectDoesNotExist, ValidationError from urllib.error import HTTPError, URLError diff --git a/tethys_services/urls.py b/tethys_services/urls.py index 7fd18903a..c13e955ea 100644 --- a/tethys_services/urls.py +++ b/tethys_services/urls.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.urls import re_path, include from tethys_services import views as tethys_services_views diff --git a/tethys_services/utilities.py b/tethys_services/utilities.py index f9a686376..ee4e33627 100644 --- a/tethys_services/utilities.py +++ b/tethys_services/utilities.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + import logging from urllib.error import HTTPError, URLError from functools import wraps diff --git a/tethys_services/views.py b/tethys_services/views.py index 7d572cd69..b96f9647c 100644 --- a/tethys_services/views.py +++ b/tethys_services/views.py @@ -7,6 +7,7 @@ * License: BSD 2-Clause ******************************************************************************** """ + from django.shortcuts import render from tethys_apps.decorators import login_required