diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aefee5a66f0173380754ead83fcbbdd2726e9cb2..eba0186fb867995aeed85aeb3f68b2b8f7a6840b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -70,13 +70,8 @@ lint: - export COMPARISON_SHA=$(git merge-base $CI_COMMIT_REF_NAME origin/$CI_DEFAULT_BRANCH) - source venv/bin/activate script: - # exit early if there are no files to check - - '[[ -z "$(git diff --name-only --diff-filter=d $COMPARISON_SHA..$CI_COMMIT_SHA -- "*.py")" ]] && exit 0' - # runs ruff (linter and formatter) on all changed files, excluding deleted files. - # this will also force changes on offending unchanged lines in changed files, but we accept that in the spirit of - # leaving every file better than we found it. - - 'git diff --name-only -z --diff-filter=d $COMPARISON_SHA..$CI_COMMIT_SHA -- "*.py" | xargs -0 ruff check --force-exclude --output-format=gitlab | tee .gl-lint-report.json' - - 'git diff --name-only -z --diff-filter=d $COMPARISON_SHA..$CI_COMMIT_SHA -- "*.py" | xargs -0 ruff format --force-exclude --diff' + - 'ruff check --output-format=gitlab | tee .gl-lint-report.json' + - 'ruff format --diff' artifacts: reports: codequality: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4f5d837b22e12ba313069b9a6f72aaf3369c67a2..8dfc5beb579bb498a50d0df61d66e4477a3b988e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,6 +17,6 @@ repos: - id: ruff name: ruff language: system - entry: utils/scripts/ruff-changed.sh + entry: utils/scripts/ruff.sh pass_filenames: false always_run: true diff --git a/comments/tests/test_graphql.py b/comments/tests/test_graphql.py index 2d8fb452b609a5967266a956176689e74e7402d1..4a4e9700d43b1900524b8ef6a9dae1bba9893aae 100644 --- a/comments/tests/test_graphql.py +++ b/comments/tests/test_graphql.py @@ -9,8 +9,7 @@ from novu.api import EventApi from comments.models import Comment, CommentReaction from openbook_auth.models import UserBlock -from openbook_common.enums import FeatureToggleName, FeatureToggleType, ReactionType -from openbook_common.models import FeatureToggle as FeatureToggleModel +from openbook_common.enums import ReactionType from openbook_common.tests.helpers import ( make_community_post, make_fake_post_text, @@ -66,14 +65,6 @@ def mock_novu_api(): @pytest.mark.usefixtures("mock_novu_api") @pytest.mark.django_db class TestPostComments(TestCase): - @pytest.fixture(autouse=True) - def setup(self): - notifications_toggle = FeatureToggleModel.objects.create( - name=FeatureToggleName.NOTIFICATIONS.value, status=FeatureToggleType.PRODUCTION - ) - yield - notifications_toggle.delete() - @mock.patch("openbook_notifications.services.NotificationsService.send_comment_mention_notification") @mock.patch("openbook_notifications.services.NotificationsService.send_commentable_author_notification") def test_should_mention_a_user_in_a_post_comment( @@ -281,14 +272,6 @@ class TestPostComments(TestCase): @pytest.mark.usefixtures("mock_novu_api") @pytest.mark.django_db class TestAddingComments(TestCase): - @pytest.fixture(autouse=True) - def setup(self): - notifications_toggle = FeatureToggleModel.objects.create( - name=FeatureToggleName.NOTIFICATIONS.value, status=FeatureToggleType.PRODUCTION - ) - yield - notifications_toggle.delete() - def test_should_add_a_comment(self): post = make_post() commenter = make_user(name="Commenter") @@ -387,14 +370,6 @@ class TestAddingComments(TestCase): @pytest.mark.usefixtures("mock_novu_api") @pytest.mark.django_db class TestDeletingComments(TestCase): - @pytest.fixture(autouse=True) - def setup(self): - notifications_toggle = FeatureToggleModel.objects.create( - name=FeatureToggleName.NOTIFICATIONS.value, status=FeatureToggleType.PRODUCTION - ) - yield - notifications_toggle.delete() - def test_should_delete_own_comment(self): comment = make_space_post_comment() commenter = comment.commenter @@ -447,14 +422,6 @@ class TestDeletingComments(TestCase): @pytest.mark.usefixtures("mock_novu_api") @pytest.mark.django_db class TestReactingToComments(TestCase): - @pytest.fixture(autouse=True) - def setup(self): - notifications_toggle = FeatureToggleModel.objects.create( - name=FeatureToggleName.NOTIFICATIONS.value, status=FeatureToggleType.PRODUCTION - ) - yield - notifications_toggle.delete() - def test_should_be_able_to_react_to_a_comment(self): comment = make_space_post_comment() commenter = comment.commenter @@ -486,14 +453,6 @@ class TestReactingToComments(TestCase): @pytest.mark.usefixtures("mock_novu_api") @pytest.mark.django_db class TestComments(TestCase): - @pytest.fixture(autouse=True) - def setup(self): - notifications_toggle = FeatureToggleModel.objects.create( - name=FeatureToggleName.NOTIFICATIONS.value, status=FeatureToggleType.PRODUCTION - ) - yield - notifications_toggle.delete() - def test_comment_by_id(self): post = make_post() user = make_user(name="User") diff --git a/locale/da/LC_MESSAGES/django.po b/locale/da/LC_MESSAGES/django.po index b9470b4d60f1e5210954cc96d53d6096f5ffce0c..b7e3a9769a1c15dd484beffb70c4defcad8812ce 100644 --- a/locale/da/LC_MESSAGES/django.po +++ b/locale/da/LC_MESSAGES/django.po @@ -1636,14 +1636,6 @@ msgstr "Intet hashtag med det angivne navn." msgid "Hashtags must to be alphanumerical and up to 32 characters." msgstr "Hashtags skal være alfanumeriske og op til 32 tegn." -#: openbook_importer/views.py:44 -msgid "done" -msgstr "udført" - -#: openbook_importer/views.py:92 openbook_importer/views.py:98 -msgid "invalid archive" -msgstr "ugyldigt arkiv" - #: openbook_insights/models.py:31 msgid "header image" msgstr "" diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 53fc25b63d0f4c12be145074ed8f88cad0da969c..45a345e46606dfbcde1b874fa1998905335d63b5 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -1636,14 +1636,6 @@ msgstr "Es existiert kein Hashtag mit dem angegebenen Namen." msgid "Hashtags must to be alphanumerical and up to 32 characters." msgstr "Hashtags müssen alphanumerisch und bis zu 32 Zeichen lang sein." -#: openbook_importer/views.py:44 -msgid "done" -msgstr "fertig" - -#: openbook_importer/views.py:92 openbook_importer/views.py:98 -msgid "invalid archive" -msgstr "ungültiges Archiv" - #: openbook_insights/models.py:31 msgid "header image" msgstr "" diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po index 7809d1bf9d072dccc82e3dd48b9f6417cfd1ab77..7bb77feed983dc585f8ec36634dfb7d983ca9b96 100644 --- a/locale/es/LC_MESSAGES/django.po +++ b/locale/es/LC_MESSAGES/django.po @@ -1636,14 +1636,6 @@ msgstr "No existe ningún hashtag con el nombre proporcionado." msgid "Hashtags must to be alphanumerical and up to 32 characters." msgstr "Los Hashtags deben ser alfanuméricos y de un máximo de 32 caracteres." -#: openbook_importer/views.py:44 -msgid "done" -msgstr "listo" - -#: openbook_importer/views.py:92 openbook_importer/views.py:98 -msgid "invalid archive" -msgstr "archivo inválido" - #: openbook_insights/models.py:31 msgid "header image" msgstr "" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index af83445e917997483d9a343a657a33268f14827a..f427d5d23f429ea2ea576241e951bac876b83baa 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -1634,14 +1634,6 @@ msgstr "" msgid "Hashtags must to be alphanumerical and up to 32 characters." msgstr "" -#: openbook_importer/views.py:44 -msgid "done" -msgstr "terminé" - -#: openbook_importer/views.py:92 openbook_importer/views.py:98 -msgid "invalid archive" -msgstr "archive invalide" - #: openbook_insights/models.py:31 msgid "header image" msgstr "" diff --git a/locale/hu/LC_MESSAGES/django.po b/locale/hu/LC_MESSAGES/django.po index 6c465287bad1ae395b12ba0db0185a01c2c03fdc..4f7b479561a4ef81ef1bddc327b06b8d70634f79 100644 --- a/locale/hu/LC_MESSAGES/django.po +++ b/locale/hu/LC_MESSAGES/django.po @@ -1634,14 +1634,6 @@ msgstr "" msgid "Hashtags must to be alphanumerical and up to 32 characters." msgstr "" -#: openbook_importer/views.py:44 -msgid "done" -msgstr "kész" - -#: openbook_importer/views.py:92 openbook_importer/views.py:98 -msgid "invalid archive" -msgstr "érvénytelen archÃvum" - #: openbook_insights/models.py:31 msgid "header image" msgstr "" diff --git a/locale/it/LC_MESSAGES/django.po b/locale/it/LC_MESSAGES/django.po index e1aaee743dcdb359abe71e36aa8dc2345c510911..33f3a4ceb82c520addd26c36eb9c9e93f1197f6b 100644 --- a/locale/it/LC_MESSAGES/django.po +++ b/locale/it/LC_MESSAGES/django.po @@ -1636,14 +1636,6 @@ msgstr "Non esiste alcun hashtag con il nome fornito." msgid "Hashtags must to be alphanumerical and up to 32 characters." msgstr "Gli hashtag devono essere alfanumerici e fino a 32 caratteri." -#: openbook_importer/views.py:44 -msgid "done" -msgstr "fatto" - -#: openbook_importer/views.py:92 openbook_importer/views.py:98 -msgid "invalid archive" -msgstr "archivio non valido" - #: openbook_insights/models.py:31 msgid "header image" msgstr "" diff --git a/locale/nl/LC_MESSAGES/django.po b/locale/nl/LC_MESSAGES/django.po index 66e07956b605496911efe4eec9c0fe19ffe210e2..6db8bf6c97be851573e3628ad285003b06840110 100644 --- a/locale/nl/LC_MESSAGES/django.po +++ b/locale/nl/LC_MESSAGES/django.po @@ -1634,14 +1634,6 @@ msgstr "" msgid "Hashtags must to be alphanumerical and up to 32 characters." msgstr "" -#: openbook_importer/views.py:44 -msgid "done" -msgstr "klaar" - -#: openbook_importer/views.py:92 openbook_importer/views.py:98 -msgid "invalid archive" -msgstr "ongeldig archief" - #: openbook_insights/models.py:31 msgid "header image" msgstr "" diff --git a/locale/pt_br/LC_MESSAGES/django.po b/locale/pt_br/LC_MESSAGES/django.po index 601862b0954e38c555c6b8a385f073a906401ab0..7e5cc79b35e98c5a9eb947ee4ab5c102ebc75006 100644 --- a/locale/pt_br/LC_MESSAGES/django.po +++ b/locale/pt_br/LC_MESSAGES/django.po @@ -1636,14 +1636,6 @@ msgstr "Não existe hashtag com o nome fornecido." msgid "Hashtags must to be alphanumerical and up to 32 characters." msgstr "As hashtags devem ser alfanuméricas e ter até 32 caracteres." -#: openbook_importer/views.py:44 -msgid "done" -msgstr "concluÃdo" - -#: openbook_importer/views.py:92 openbook_importer/views.py:98 -msgid "invalid archive" -msgstr "arquivo inválido" - #: openbook_insights/models.py:31 msgid "header image" msgstr "" diff --git a/locale/sv_se/LC_MESSAGES/django.po b/locale/sv_se/LC_MESSAGES/django.po index af106fb29891424738ab35911e34f0549d0ea054..32a6ad1c60bfeecdf8cc4b179528567c9fab62fb 100644 --- a/locale/sv_se/LC_MESSAGES/django.po +++ b/locale/sv_se/LC_MESSAGES/django.po @@ -1636,14 +1636,6 @@ msgstr "Det finns ingen hashtag med det angivna namnet." msgid "Hashtags must to be alphanumerical and up to 32 characters." msgstr "Hashtaggar mÃ¥ste vara alfanumeriska och ha maximalt 32 tecken." -#: openbook_importer/views.py:44 -msgid "done" -msgstr "klar" - -#: openbook_importer/views.py:92 openbook_importer/views.py:98 -msgid "invalid archive" -msgstr "ogiltigt arkiv" - #: openbook_insights/models.py:31 msgid "header image" msgstr "" diff --git a/locale/tr/LC_MESSAGES/django.po b/locale/tr/LC_MESSAGES/django.po index c070d8a78ef11712d7412c41d51a483f3020fdf4..aeeaeba8e26d53a6e88ecd6312902d98626a41cf 100644 --- a/locale/tr/LC_MESSAGES/django.po +++ b/locale/tr/LC_MESSAGES/django.po @@ -1634,14 +1634,6 @@ msgstr "" msgid "Hashtags must to be alphanumerical and up to 32 characters." msgstr "" -#: openbook_importer/views.py:44 -msgid "done" -msgstr "tamam" - -#: openbook_importer/views.py:92 openbook_importer/views.py:98 -msgid "invalid archive" -msgstr "geçersiz arÅŸiv" - #: openbook_insights/models.py:31 msgid "header image" msgstr "" diff --git a/manage.py b/manage.py index b36286514d9f30133422c32f03cfe656befac96b..f9cad6cca510fbbf7a379b789bbc4eb28af432b9 100755 --- a/manage.py +++ b/manage.py @@ -11,12 +11,12 @@ if __name__ == "__main__": # issue is really that Django is missing to avoid masking other # exceptions on Python 2. try: - import django + import django # noqa: F401 except ImportError: raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" - ) + ) from None raise execute_from_command_line(sys.argv) diff --git a/okuna-cli.py b/okuna-cli.py index 88fddc499cc274b95afdd48224d2abe1c3fdc22c..3afb139adc326c3a7f83313a885d8908a94ee604 100755 --- a/okuna-cli.py +++ b/okuna-cli.py @@ -1,3 +1,6 @@ +# ruff: noqa: S603 +# ruff: noqa: S607 + import random import time import click @@ -8,7 +11,8 @@ import os.path from shutil import copyfile import json import atexit -import os, errno +import os +import errno import requests from halo import Halo @@ -61,7 +65,7 @@ def _generate_random_string( The default length of 12 with the a-z, A-Z, 0-9 character set returns a 71-bit value. log_2((26+26+10)^12) =~ 71 bits """ - return "".join(random.choice(allowed_chars) for i in range(length)) + return "".join(random.choice(allowed_chars) for i in range(length)) # noqa: S311 def _generate_django_secret_key(): @@ -87,10 +91,10 @@ def _copy_requirements_txt_to_docker_images_dir(): def _check_okuna_api_is_running(address, port): # Create a TCP socket try: - response = requests.get(f"http://{address}:{port}/health/") + response = requests.get(f"http://{address}:{port}/health/") # noqa: S113 response_status = response.status_code return response_status == 200 - except requests.ConnectionError as e: + except requests.ConnectionError: return False @@ -131,14 +135,8 @@ def _clean(): logger.info("✅ Clean up done!") -def _docker_name(suffix): - # docker uses the convention of prefixing resource names with the name of the current directory - cwd = os.path.basename(os.path.dirname(os.path.realpath(__file__))) - return f"{cwd}_{suffix}" - - def _print_okuna_logo(): - print( + print( # noqa: T201 r""" ____ _ / __ \| | @@ -242,9 +240,9 @@ def _bootstrap(is_local_api): logger.info("🚀 Bootstrapping Okuna with some data") if is_local_api: - subprocess.run(["./utils/scripts/bootstrap_development_data.sh"]) + subprocess.run(["./utils/scripts/bootstrap_development_data.sh"]) # noqa: S603 else: - subprocess.run( + subprocess.run( # noqa: S603 [ "docker", "compose", diff --git a/openbook/logging.py b/openbook/logging.py index 02dcab0d0ae3d491ef5bbccde0eecb7e5e464d44..e54a6eb1c5b5595c66738f7998bd8ec2f6ce827e 100644 --- a/openbook/logging.py +++ b/openbook/logging.py @@ -104,9 +104,6 @@ LOGGING_CONFIG = { "uvicorn.access": { "level": "WARNING", }, - # "django.db.backends": { - # "level": "DEBUG", - # }, }, "root": { "level": "INFO", @@ -117,7 +114,7 @@ LOGGING_CONFIG = { def initialize_logging(): if SQL_LOGGING != "DO_NOT_LOG": - print("Setting logger django.db.backends to level DEBUG") + print("Setting logger django.db.backends to level DEBUG") # noqa: T201 LOGGING_CONFIG["loggers"]["django.db.backends"] = { "level": "DEBUG", } diff --git a/openbook/middleware/__init__.py b/openbook/middleware/__init__.py index 8162cf8b40963cf0bd7add6c7618d81e6bb93eed..5397e28f3793813996cf58f11997c6fe1a3cd4b3 100644 --- a/openbook/middleware/__init__.py +++ b/openbook/middleware/__init__.py @@ -1,2 +1,2 @@ -from .slow_log_middleware import slow_log_middleware -from .graphql_error_extension import GraphQLErrorExtension +from .slow_log_middleware import slow_log_middleware # noqa: F401 +from .graphql_error_extension import GraphQLErrorExtension # noqa: F401 diff --git a/openbook/middleware/graphql_error_extension.py b/openbook/middleware/graphql_error_extension.py index 81b7f2731c2a873b10f16079cd2677ff9b773a90..db852ac0ce8f2307455e6fd22f82b736354d52ac 100644 --- a/openbook/middleware/graphql_error_extension.py +++ b/openbook/middleware/graphql_error_extension.py @@ -13,7 +13,7 @@ from strawberry.extensions import Extension class GraphQLErrorExtension(Extension): @classmethod - async def resolve(self, _next, root, info, *args, **kwargs): + async def resolve(cls, _next, root, info, *args, **kwargs): try: return await await_maybe(_next(root, info, *args, **kwargs)) except ValidationError as error: @@ -23,7 +23,7 @@ class GraphQLErrorExtension(Extension): "code": GraphQLErrorCodes.BAD_USER_INPUT, "errors": json.dumps(camelize(error.get_full_details())), }, - ) + ) from None except CoreValidationError as error: validationError = ValidationError(error.message_dict) raise GraphQLError( @@ -32,8 +32,8 @@ class GraphQLErrorExtension(Extension): "code": GraphQLErrorCodes.BAD_USER_INPUT, "errors": json.dumps(camelize(validationError.get_full_details())), }, - ) + ) from None except ObjectDoesNotExist: - raise GraphQLError("Object not found", extensions={"code": GraphQLErrorCodes.NOT_FOUND}) + raise GraphQLError("Object not found", extensions={"code": GraphQLErrorCodes.NOT_FOUND}) from None except PermissionDenied: - raise GraphQLError("Forbidden", extensions={"code": GraphQLErrorCodes.FORBIDDEN}) + raise GraphQLError("Forbidden", extensions={"code": GraphQLErrorCodes.FORBIDDEN}) from None diff --git a/openbook/middleware/slow_log_middleware.py b/openbook/middleware/slow_log_middleware.py index cbd40bcd0fc577cf2732f2bbc7f6412b2fdc5cc5..74de08ae0896c82950294f7392f2fa37ca33da30 100644 --- a/openbook/middleware/slow_log_middleware.py +++ b/openbook/middleware/slow_log_middleware.py @@ -45,7 +45,7 @@ class LoadAverage(object): with open("/proc/loadavg") as f: content = f.read() return [float(x) for x in content.split()[:3]] - except: + except: # noqa: E722 return [0.0, 0.0, 0.0] @@ -74,7 +74,7 @@ class MemoryStatus(object): content = f.read() size = self.matcher.search(content).groups()[0] return to_bytes(size) - except: + except: # noqa: E722 return 0 diff --git a/openbook/settings/__init__.py b/openbook/settings/__init__.py index 4a7d414d138763d1210724f4bcb51c9bb03131d2..abc3d91b90e4c9c78c30b9e162e16cff581a8f6a 100644 --- a/openbook/settings/__init__.py +++ b/openbook/settings/__init__.py @@ -50,7 +50,7 @@ DEBUG = environment_checker.is_debug() IS_PRODUCTION = environment_checker.is_production() IS_STAGING = environment_checker.is_staging() IS_BUILD = environment_checker.is_build() -TESTING = "pytest" in sys.argv[0] +TESTING = "pytest" in sys.modules APP_URL = os.environ.get("APP_URL", "https://staging.dev.holi.social") if IS_PRODUCTION: @@ -94,20 +94,17 @@ INSTALLED_APPS = [ "openbook_posts", "openbook_circles", "openbook_connections", - "openbook_importer", "openbook_insights", "openbook_polls", "openbook_lists", "openbook_follows", "openbook_communities", "openbook_invitations", - "openbook_tags", "openbook_hashtags", "openbook_categories", "openbook_notifications", "openbook_devices", "openbook_moderation", - "openbook_translation", "openbook_terms", "strawberry.django", "sortedm2m", @@ -233,14 +230,20 @@ CACHES = { "default": { "BACKEND": CACHE_BACKENDS["redis"], "LOCATION": REDIS_DEFAULT_CACHE_LOCATION, - "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient", "REDIS_CLIENT_KWARGS": {"health_check_interval": 30}}, + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + "REDIS_CLIENT_KWARGS": {"health_check_interval": 30}, + }, "KEY_PREFIX": "ob-api-" + ENVIRONMENT_ID + "-", "TIMEOUT": 60 * 60, }, USERBLOCK: { "BACKEND": CACHE_BACKENDS["redis"], "LOCATION": REDIS_USERBLOCK_CACHE_LOCATION, - "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient", "REDIS_CLIENT_KWARGS": {"health_check_interval": 30}}, + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + "REDIS_CLIENT_KWARGS": {"health_check_interval": 30}, + }, "KEY_PREFIX": f"ob-userblock-{ENVIRONMENT_ID}-", "TIMEOUT": 60 * 60, # 1 hour }, @@ -250,7 +253,7 @@ CACHES = { "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "SERIALIZER": "django_redis.serializers.json.JSONSerializer", - "REDIS_CLIENT_KWARGS": {"health_check_interval": 30} + "REDIS_CLIENT_KWARGS": {"health_check_interval": 30}, }, "KEY_PREFIX": "response-cache", "KEY_FUNCTION": "openbook.utils.unified_api_response_cache_key_function", @@ -260,19 +263,28 @@ CACHES = { RQ_DEFAULT_JOBS: { "BACKEND": CACHE_BACKENDS["redis"], "LOCATION": REDIS_RQ_DEFAULT_JOBS_CACHE_LOCATION, - "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient", "REDIS_CLIENT_KWARGS": {"health_check_interval": 30}}, + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + "REDIS_CLIENT_KWARGS": {"health_check_interval": 30}, + }, "KEY_PREFIX": "ob-api-rq-default-job-" + ENVIRONMENT_ID + "-", }, RQ_HIGH_JOBS: { "BACKEND": CACHE_BACKENDS["redis"], "LOCATION": REDIS_RQ_HIGH_JOBS_CACHE_LOCATION, - "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient", "REDIS_CLIENT_KWARGS": {"health_check_interval": 30}}, + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + "REDIS_CLIENT_KWARGS": {"health_check_interval": 30}, + }, "KEY_PREFIX": "ob-api-rq-high-job-" + ENVIRONMENT_ID + "-", }, RQ_LOW_JOBS: { "BACKEND": CACHE_BACKENDS["redis"], "LOCATION": REDIS_RQ_LOW_JOBS_CACHE_LOCATION, - "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient", "REDIS_CLIENT_KWARGS": {"health_check_interval": 30}}, + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + "REDIS_CLIENT_KWARGS": {"health_check_interval": 30}, + }, "KEY_PREFIX": "ob-api-rq-low-job-" + ENVIRONMENT_ID + "-", }, } @@ -412,23 +424,6 @@ REST_FRAMEWORK = { }, } -# AWS Translate -AWS_TRANSLATE_REGION = os.environ.get("AWS_TRANSLATE_REGION", "eu-central-1") -AWS_TRANSLATE_MAX_LENGTH = os.environ.get("AWS_TRANSLATE_MAX_LENGTH", 10000) -OS_TRANSLATION_STRATEGY_NAME = "default" -OS_TRANSLATION_CONFIG = { - "default": { - "STRATEGY": "openbook_translation.strategies.amazon.AmazonTranslate", - "TEXT_MAX_LENGTH": AWS_TRANSLATE_MAX_LENGTH, - "DEFAULT_TRANSLATION_LANGUAGE_CODE": "en", - }, - "testing": { - "STRATEGY": "openbook_translation.strategies.tests.MockAmazonTranslate", - "TEXT_MAX_LENGTH": 40, - "DEFAULT_TRANSLATION_LANGUAGE_CODE": "en", - }, -} - UNICODE_JSON = True # The sentry DSN for error reporting diff --git a/openbook/settings/testing.py b/openbook/settings/testing.py index e8ad6bb1a1fbb628b7237bb4c15529c91a55d875..c17040782867ad3ff15b0209db0f832505b7dba6 100644 --- a/openbook/settings/testing.py +++ b/openbook/settings/testing.py @@ -1,9 +1,10 @@ -from . import * +import os +from . import * # noqa: F403 -ENVIRONMENT = EnvironmentChecker.TEST_VALUE +ENVIRONMENT = EnvironmentChecker.TEST_VALUE # noqa: F405 TESTING = True -for queueConfig in RQ_QUEUES.values(): +for queueConfig in RQ_QUEUES.values(): # noqa: F405, N816 queueConfig["ASYNC"] = False TEST_DB_NAME = os.environ.get("TEST_DB_NAME", "okuna_test_db") diff --git a/openbook/tests/test_webhooks.py b/openbook/tests/test_webhooks.py index 8ede14f0af8bf28cec62b7d92356b9024103f443..62547370861f876473857824c6e7ae9b06fd908f 100644 --- a/openbook/tests/test_webhooks.py +++ b/openbook/tests/test_webhooks.py @@ -1,7 +1,7 @@ import json import pytest -from django.test import AsyncClient, Client, TransactionTestCase +from django.test import AsyncClient, TransactionTestCase from unittest import mock from django.urls import reverse from asgiref.sync import async_to_sync @@ -129,7 +129,7 @@ class NotificationWebhookTestCase(TransactionTestCase): # THEN assert response.status_code == 200 content = json.loads(response.content) - assert content["valid"] == True + assert content["valid"] is True def test_handles_if_subscriber_is_not_admin_for_membership_request(self): # GIVEN @@ -153,7 +153,7 @@ class NotificationWebhookTestCase(TransactionTestCase): # THEN assert response.status_code == 200 content = json.loads(response.content) - assert content["valid"] == False + assert content["valid"] is False def test_handles_missing_user_for_membership_request(self): # GIVEN @@ -176,7 +176,7 @@ class NotificationWebhookTestCase(TransactionTestCase): # THEN assert response.status_code == 200 content = json.loads(response.content) - assert content["valid"] == False + assert content["valid"] is False def test_handles_missing_space_for_membership_request(self): # GIVEN @@ -199,7 +199,7 @@ class NotificationWebhookTestCase(TransactionTestCase): # THEN assert response.status_code == 200 content = json.loads(response.content) - assert content["valid"] == False + assert content["valid"] is False def test_handles_invalid_notification_payload_for_membership_request(self): # GIVEN @@ -222,12 +222,12 @@ class NotificationWebhookTestCase(TransactionTestCase): # THEN assert response.status_code == 200 content = json.loads(response.content) - assert content["valid"] == False + assert content["valid"] is False def test_handles_invalid_payload_for_membership_request(self): # GIVEN client = AsyncClient() - user = make_user() + make_user() payload = { "payload": { "notificationType": NotificationType.SPACE_MEMBERSHIP_REQUEST.value, @@ -241,12 +241,12 @@ class NotificationWebhookTestCase(TransactionTestCase): # THEN assert response.status_code == 200 content = json.loads(response.content) - assert content["valid"] == False + assert content["valid"] is False def test_handles_unhandled_notification_type(self): # GIVEN client = AsyncClient() - user = make_user() + make_user() payload = { "payload": { "notificationType": NotificationType.SPACE_MEMBERSHIP_INVITE.value, @@ -263,7 +263,7 @@ class NotificationWebhookTestCase(TransactionTestCase): def test_handles_unknown_notification_type(self): # GIVEN client = AsyncClient() - user = make_user() + make_user() payload = { "payload": { "notificationType": "unknown", @@ -280,7 +280,7 @@ class NotificationWebhookTestCase(TransactionTestCase): def test_handles_missing_notification_type(self): # GIVEN client = AsyncClient() - user = make_user() + make_user() payload = { "payload": {}, } diff --git a/openbook/urls.py b/openbook/urls.py index e66a96b77659f90f629713aabb3aa272d19ca95c..552bd338c5deaac42478e356616b548ebbd714d1 100644 --- a/openbook/urls.py +++ b/openbook/urls.py @@ -1,5 +1,4 @@ import adrf.decorators as async_support -from django.http import HttpRequest from django.urls import re_path, path, include from django.contrib import admin from django.conf import settings diff --git a/openbook/webhooks.py b/openbook/webhooks.py index c687447598fcd88703a9cc2c4092a210e0dc3e69..c1b3899552c189227b86da7b7363c8b3d7beb3c6 100644 --- a/openbook/webhooks.py +++ b/openbook/webhooks.py @@ -95,7 +95,7 @@ class OryWebhook(View): except HTTPError as http_err: if http_err.response.status_code == 404: # We can not create the subscriber in novu, as we don't know the users locale - logger.debug(f"Could not update e-mail in novu as the subscriber does not exist yet") + logger.debug("Could not update e-mail in novu as the subscriber does not exist yet") else: raise http_err diff --git a/openbook_appointments/views.py b/openbook_appointments/views.py deleted file mode 100644 index 91ea44a218fbd2f408430959283f0419c921093e..0000000000000000000000000000000000000000 --- a/openbook_appointments/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/openbook_auth/checkers.py b/openbook_auth/checkers.py index 014d5435db11ce006ff6010a105afd5e6f6de916..7b9f416dd37f0341c6f5852cc0938527e56d1c67 100644 --- a/openbook_auth/checkers.py +++ b/openbook_auth/checkers.py @@ -86,19 +86,6 @@ def check_comments_enabled_for_post_with_id(user, post_id): raise ValidationError(_("Comments are disabled for this post")) -def check_can_translate_post_with_id(user, post_id): - Post = get_post_model() - post = Post.objects.get(id=post_id) - if not post.is_public: - raise ValidationError(_("Only public posts can be translated")) - if post.text is None: - raise ValidationError(_("Post has no text to be translated")) - if post.language is None: - raise ValidationError(_("Post has no assigned language to be able to translate")) - if user.translation_language is None: - raise ValidationError(_("User's preferred translation language not set")) - - def check_can_open_post_with_id(user, post_id): Post = get_post_model() post = Post.objects.select_related("community").get(id=post_id) @@ -811,19 +798,6 @@ def check_has_muted_post_comment_with_id(user, post_comment_id): ) -def check_can_translate_comment_with_id(user, post_comment_id): - Comment = get_comment_model() - post_comment = Comment.objects.get(pk=post_comment_id) - if not post_comment.commentable.is_public: - raise ValidationError(_("Only public post comments can be translated")) - if post_comment.text is None: - raise ValidationError(_("Post comment has no text to be translated")) - if post_comment.language is None: - raise ValidationError(_("Post comment has no assigned language to be able to translate")) - if user.translation_language is None: - raise ValidationError(_("User's preferred translation language not set")) - - def check_has_post(user, post): if not user.has_post(post=post): raise PermissionDenied( diff --git a/openbook_auth/management/commands/create_and_migrate_user_chat_identities.py b/openbook_auth/management/commands/create_and_migrate_user_chat_identities.py index 930efa78654507b9374d0b608ddf28c7d3a023f2..a3f9fd1859010c694e161cfa0dc7dab0bbdc9ca4 100644 --- a/openbook_auth/management/commands/create_and_migrate_user_chat_identities.py +++ b/openbook_auth/management/commands/create_and_migrate_user_chat_identities.py @@ -1,5 +1,5 @@ +# ruff: noqa: T201 import http -import random import requests from django.core.management.base import BaseCommand @@ -26,7 +26,7 @@ class Command(BaseCommand): {"op": "add", "path": "/traits/identity", "value": identity}, ] response = requests.patch( - f"{ORY_BASE_URL}/admin/identities/{user.username}", headers=ORY_HEADER, json=body_data + f"{ORY_BASE_URL}/admin/identities/{user.username}", headers=ORY_HEADER, json=body_data, timeout=30 ) match response.status_code: case http.HTTPStatus.OK: diff --git a/openbook_auth/management/commands/migrate_identities_to_metadata.py b/openbook_auth/management/commands/migrate_identities_to_metadata.py index 04c5600b121bb2b62a06de36c2f2ba6be77417a7..6270317ae46f0989df1ac9b0d9cda66b785271c1 100644 --- a/openbook_auth/management/commands/migrate_identities_to_metadata.py +++ b/openbook_auth/management/commands/migrate_identities_to_metadata.py @@ -1,8 +1,9 @@ +# ruff: noqa: T201 import http import requests from django.core.management.base import BaseCommand -from openbook.settings import ORY_ACCESS_TOKEN, ORY_BASE_URL +from openbook.settings import ORY_BASE_URL from openbook_auth.management.commands.sync_okuna_user_names_to_ory import get_ory_error_str from openbook_auth.management.common import for_every_ory_user, ory_user_to_str, ORY_HEADER @@ -15,9 +16,7 @@ class Command(BaseCommand): no_update_needed = 0 def handle(self, *args, **options): - # self._patch_ory_user( - # {"id": "869e0ade-1561-4cf8-81dc-ca7552367325", "traits": {"identity": "ho5", "email": "test@sxx"}} - # ) + # self._patch_ory_user({"id": "869e0ade-1561-4cf8-81dc-ca7552367325", "traits": {"identity": "ho5", "email": "test@sxx"}}) # noqa: ERA001 for_every_ory_user(self._patch_ory_user) print(f"{self.total_users_updated} users updated") print(f"{self.total_users_failed} user updates failed") @@ -32,7 +31,7 @@ class Command(BaseCommand): print(f"{count} Updating {ory_user_to_str(user)}") response = requests.patch( - f"{ORY_BASE_URL}/admin/identities/{user['id']}", headers=ORY_HEADER, json=body_data + f"{ORY_BASE_URL}/admin/identities/{user['id']}", headers=ORY_HEADER, json=body_data, timeout=30 ) if response.status_code != http.HTTPStatus.OK: @@ -43,7 +42,7 @@ class Command(BaseCommand): {"op": "remove", "path": "/traits/identity"}, ] response = requests.patch( - f"{ORY_BASE_URL}/admin/identities/{user['id']}", headers=ORY_HEADER, json=body_data + f"{ORY_BASE_URL}/admin/identities/{user['id']}", headers=ORY_HEADER, json=body_data, timeout=30 ) if response.status_code != http.HTTPStatus.OK: print(f"{count} {get_ory_error_str(response)}, {ory_user_to_str(user)} removing identity failed") diff --git a/openbook_auth/management/commands/migrate_ory_users_schema.py b/openbook_auth/management/commands/migrate_ory_users_schema.py index f4ff16759a928b4511b84002315906d185c67ed9..35bed4a335dd6a7bdce0ac4d845bbf5d2591218a 100644 --- a/openbook_auth/management/commands/migrate_ory_users_schema.py +++ b/openbook_auth/management/commands/migrate_ory_users_schema.py @@ -1,8 +1,9 @@ +# ruff: noqa: T201 import http import requests from django.core.management.base import BaseCommand -from openbook.settings import ORY_ACCESS_TOKEN, ORY_BASE_URL +from openbook.settings import ORY_BASE_URL from openbook_auth.management.commands.sync_okuna_user_names_to_ory import get_ory_error_str from openbook_auth.management.common import for_every_ory_user, ory_user_to_str, ORY_HEADER @@ -34,7 +35,7 @@ class Command(BaseCommand): print(f"{count} Updating {ory_user_to_str(user)}") response = requests.patch( - f"{ORY_BASE_URL}/admin/identities/{user['id']}", headers=ORY_HEADER, json=body_data + f"{ORY_BASE_URL}/admin/identities/{user['id']}", headers=ORY_HEADER, json=body_data, timeout=30 ) if response.status_code != http.HTTPStatus.OK: diff --git a/openbook_auth/management/commands/patch_ory.py b/openbook_auth/management/commands/patch_ory.py index cdeda409d63e7e1ad40de5673c48b35ecc177d4c..f1f812857a87e1f9e0390719b39e913bb2db6a39 100644 --- a/openbook_auth/management/commands/patch_ory.py +++ b/openbook_auth/management/commands/patch_ory.py @@ -1,3 +1,4 @@ +# ruff: noqa: T201 import requests from django.core.management import BaseCommand @@ -53,10 +54,12 @@ class Command(BaseCommand): print(f"User {ory_id} set to verified") def set_password(self, ory_id, new_password): - resp = requests.get(f"{ORY_BASE_URL}/admin/identities/{ory_id}", headers=ORY_HEADER) + resp = requests.get(f"{ORY_BASE_URL}/admin/identities/{ory_id}", headers=ORY_HEADER, timeout=30) ory_user = resp.json() ory_user["credentials"]["password"]["config"] = {"password": new_password} - response = requests.put(f"{ORY_BASE_URL}/admin/identities/{ory_id}", headers=ORY_HEADER, json=ory_user) + response = requests.put( + f"{ORY_BASE_URL}/admin/identities/{ory_id}", headers=ORY_HEADER, json=ory_user, timeout=30 + ) if no_ory_errors(response): print(f"User {ory_id} password set to {new_password}") @@ -64,7 +67,9 @@ class Command(BaseCommand): body_data = [ {"op": "add", "path": "/traits/email", "value": new_email}, ] - response = requests.patch(f"{ORY_BASE_URL}/admin/identities/{ory_id}", headers=ORY_HEADER, json=body_data) + response = requests.patch( + f"{ORY_BASE_URL}/admin/identities/{ory_id}", headers=ORY_HEADER, json=body_data, timeout=30 + ) if no_ory_errors(response): print(f"User {ory_id} email set to {new_email}") diff --git a/openbook_auth/management/commands/sync_okuna_user_names_to_ory.py b/openbook_auth/management/commands/sync_okuna_user_names_to_ory.py index efb06fb93287778dca49058edb0836c30bc79a3d..03db560a34fb47387cb6547b771bf7773c230820 100644 --- a/openbook_auth/management/commands/sync_okuna_user_names_to_ory.py +++ b/openbook_auth/management/commands/sync_okuna_user_names_to_ory.py @@ -1,3 +1,4 @@ +# ruff: noqa: T201 import http import requests @@ -54,7 +55,7 @@ class Command(BaseCommand): ) response = requests.patch( - f"{ORY_BASE_URL}/admin/identities/{user.username}", headers=ORY_HEADER, json=body_data + f"{ORY_BASE_URL}/admin/identities/{user.username}", headers=ORY_HEADER, json=body_data, timeout=30 ) match response.status_code: case http.HTTPStatus.OK: diff --git a/openbook_auth/management/commands/verify_ory_user.py b/openbook_auth/management/commands/verify_ory_user.py index f3c783377a68a1d772c1a7eceb4b51cf900ad925..1cbd517bf7cbc961531950db1ac366775ffa848a 100644 --- a/openbook_auth/management/commands/verify_ory_user.py +++ b/openbook_auth/management/commands/verify_ory_user.py @@ -1,3 +1,4 @@ +# ruff: noqa: T201 from django.core.management import BaseCommand import requests from openbook.settings import ORY_BASE_URL @@ -25,7 +26,7 @@ class Command(BaseCommand): {"op": "replace", "path": "/verifiable_addresses/0/status", "value": "completed"}, ] response = requests.patch( - f"{ORY_BASE_URL}/admin/identities/{user['id']}", headers=ORY_HEADER, json=body_data + f"{ORY_BASE_URL}/admin/identities/{user['id']}", headers=ORY_HEADER, json=body_data, timeout=30 ) if response.status_code != http.HTTPStatus.OK: print(get_ory_error_str(response)) diff --git a/openbook_auth/management/common.py b/openbook_auth/management/common.py index e9a44d21510af9336c6c111f8bc76e545f25d0e7..b34368093ed1815a4779a10b1631d86ca60a7422 100644 --- a/openbook_auth/management/common.py +++ b/openbook_auth/management/common.py @@ -1,5 +1,6 @@ +# ruff: noqa: T201 from time import sleep -from typing import Callable, Any +from typing import Callable import requests import http @@ -22,10 +23,12 @@ def for_every_ory_user(function: Callable[[dict], None]): while first or next_page_url: first = False if next_page_url: - response = requests.get(next_page_url, headers=ORY_HEADER) + response = requests.get(next_page_url, headers=ORY_HEADER, timeout=30) else: params = {"per_page": per_page} - response = requests.get(f"{settings.ORY_BASE_URL}/admin/identities/", params=params, headers=ORY_HEADER) + response = requests.get( + f"{settings.ORY_BASE_URL}/admin/identities/", params=params, headers=ORY_HEADER, timeout=30 + ) if response.status_code != http.HTTPStatus.OK: message = get_ory_error_str(response) @@ -38,7 +41,7 @@ def for_every_ory_user(function: Callable[[dict], None]): return else: - # total = response.headers.get("x-total-count") + # total = response.headers.get("x-total-count") # noqa: ERA001 retry_count = 0 response_data = response.json() for user in response_data: @@ -56,7 +59,9 @@ def get_next_page_parameters(next_page): def get_ory_user(email): params = {"per_page": 1, "credentials_identifier": email} - response = requests.get(f"{settings.ORY_BASE_URL}/admin/identities/", params=params, headers=ORY_HEADER) + response = requests.get( + f"{settings.ORY_BASE_URL}/admin/identities/", params=params, headers=ORY_HEADER, timeout=30 + ) if no_ory_errors(response, "Error getting users from Ory"): response_json = response.json() diff --git a/openbook_auth/models.py b/openbook_auth/models.py index d3e2f8ab35b9690edb8d3e344f8dbe3512c55d3b..54c9227efd5f3206e6e96eee1464d90cc0ea1090 100644 --- a/openbook_auth/models.py +++ b/openbook_auth/models.py @@ -38,7 +38,7 @@ from openbook_auth.helpers import ( ) from openbook_auth.utils import normalize_spaces from openbook_common.enums import VisibilityType -from openbook_common.helpers import ExifRotate, generate_blurhash, get_supported_translation_language, PrefixedQ +from openbook_common.helpers import ExifRotate, generate_blurhash, PrefixedQ from openbook_common.models import Badge, ModelWithUUID from openbook_common.schema.types import Paged from openbook_common.types import GeolocationFeature, ReactableType @@ -100,11 +100,10 @@ from openbook_hashtags.queries import ( make_search_hashtag_query_for_user_with_id, make_get_hashtag_with_name_for_user_with_id_query, ) -from openbook_notifications.toggles import notifications_toggle, mute_notifications +from openbook_notifications.toggles import mute_notifications from openbook_posts.queries import make_get_hashtag_posts_for_user_with_id_query from openbook_posts.query_collections import get_posts_for_user_collection from openbook_terms.models import Topic, SDG, Skill -from openbook_translation import translation_strategy if typing.TYPE_CHECKING: from comments.models import Comment @@ -708,7 +707,6 @@ class User(ModelWithUUID, AbstractUser): Language = get_language_model() language = Language.objects.get(pk=language_id) self.language = language - self.translation_language = get_supported_translation_language(language.code) self.save() def verify_password_reset_token(self, token, password): @@ -962,7 +960,10 @@ class User(ModelWithUUID, AbstractUser): return not connection.circles.exists() - def get_connection_status_with_user_id(self, user_id): + def get_connection_status_with_user_id(self, user_id) -> Optional[UserConnectionStatus]: + # users can't connect with themselves (quick exit saves query execution time) + if self.id == user_id: + return None # check if not connected (no user requested to connect) if not self.is_connected_with_user_with_id(user_id=user_id): return UserConnectionStatus.NOT_CONNECTED @@ -972,7 +973,7 @@ class User(ModelWithUUID, AbstractUser): # check if pending confirm connection (true only for the user who received the connection request) if self.is_pending_confirm_connection_for_user_with_id(user_id=user_id): return UserConnectionStatus.PENDING_CONFIRMATION - # if none of the above is true, the currently signed in user has made the request + # if none of the above is true, the currently signed-in user has made the request return UserConnectionStatus.REQUESTED def delete_all_pending_connection_requests(self): @@ -3139,17 +3140,6 @@ class User(ModelWithUUID, AbstractUser): check_can_get_user_with_id(user=self, user_id=user.pk) return user - def translate_post_with_id(self, post_id): - check_can_translate_post_with_id(user=self, post_id=post_id) - Post = get_post_model() - post = Post.objects.get(id=post_id) - result = translation_strategy.translate_text( - source_language_code=post.language.code, - target_language_code=self.translation_language.code, - text=post.text, - ) - return post, result.get("translated_text") - def open_post_with_id(self, post_id): check_can_open_post_with_id(user=self, post_id=post_id) Post = get_post_model() @@ -4051,13 +4041,11 @@ class User(ModelWithUUID, AbstractUser): def has_device_with_token_and_type(self, token, device_type): return self.devices.filter(token=token, device_type=device_type).exists() - @notifications_toggle def create_device(self, token, device_type): check_device_with_token_and_type_does_not_exist(user=self, device_token=token, device_type=device_type) Device = get_device_model() return Device.create_device(owner=self, token=token, device_type=device_type) - @notifications_toggle def delete_device_with_token_and_type(self, device_token, device_type): check_can_delete_device_with_token_and_type(user=self, device_token=device_token, device_type=device_type) device = self.devices.get(token=device_token, device_type=device_type) @@ -4101,17 +4089,6 @@ class User(ModelWithUUID, AbstractUser): self.comment_mutes.filter(post_comment_id=post_comment_id).delete() return post_comment - def translate_post_comment_with_id(self, post_comment_id): - check_can_translate_comment_with_id(user=self, post_comment_id=post_comment_id) - Comment = get_comment_model() - post_comment = Comment.objects.get(pk=post_comment_id) - result = translation_strategy.translate_text( - source_language_code=post_comment.language.code, - target_language_code=self.translation_language.code, - text=post_comment.text, - ) - return post_comment, result.get("translated_text") - def unsubscribe_from_user_notifications(self, user): UserNotificationsSubscription = get_user_notifications_subscription_model() UserNotificationsSubscription.delete_user_notifications_subscription(user=user, subscriber=self) diff --git a/openbook_auth/schema/mutations.py b/openbook_auth/schema/mutations.py index 43c2a68257eebc5e26c9b2ebe94730c46776d455..3e78895f8c9f2f855f04b2aca4cbd0a2b43265a7 100644 --- a/openbook_auth/schema/mutations.py +++ b/openbook_auth/schema/mutations.py @@ -39,11 +39,6 @@ logger = structlog.get_logger(__name__) @strawberry.type class Mutation: - # TODO HOLI-8088 remove test once get_authenticated_user is removed - @strawberry.mutation(deprecation_reason="Deprecated in favor of 'updatedAuthenticatedUserNew'.") - def update_authenticated_user(self, info, input: UpdateAuthenticatedUserInput) -> User: - return Mutation().update_authenticated_user_v2(info, input) - @strawberry.mutation @sync_to_async def update_authenticated_user_v2(self, info, input: UpdateAuthenticatedUserInput) -> AuthenticatedUser: diff --git a/openbook_auth/schema/queries.py b/openbook_auth/schema/queries.py index 6325e4fc9f7e81784ca5f6d70daba9fc306ca3fb..d98930835a78b8027af722a1a3c6dd37b4deef56 100644 --- a/openbook_auth/schema/queries.py +++ b/openbook_auth/schema/queries.py @@ -2,8 +2,6 @@ import uuid from datetime import datetime, timedelta from typing import List, Optional -from django.utils.translation import get_language_from_request - import strawberry import strawberry_django import structlog @@ -14,32 +12,20 @@ from django.db.models.lookups import IContains from openbook_auth.models import User as UserModel, UserBlock as UserBlockModel from openbook_auth.schema.types import User, AuthenticatedUser from openbook_auth.utils import verify_authorized_user, check_max_limit -from openbook_common.validators import string_not_empty from openbook_common.schema.types import Paged +from openbook_common.validators import string_not_empty from openbook_insights.schema.types import Insight -from openbook_notifications.api import NotificationApi logger = structlog.get_logger(__name__) @strawberry.type class Query: - # TODO HOLI-8088 remove test once get_authenticated_user is removed - @strawberry_django.field(deprecation_reason="Deprecated in favor of 'authenticatedUserV2'.") - def authenticated_user(self, info) -> Optional[User]: - return Query.authenticated_user_v2(self, info) - @strawberry_django.field() def authenticated_user_v2(self, info) -> Optional[AuthenticatedUser]: current_user = info.context.request.user if current_user.is_anonymous: return None - locale = get_language_from_request(info.context.request) - - # TODO: This is a hack to make sure that the subscriber is updated with the correct locale. - # Remove this when we add a language picker to the user profile. - NotificationApi.create_or_update_subscriber(user=current_user, locale=locale) - return current_user @strawberry_django.field() diff --git a/openbook_auth/schema/types.py b/openbook_auth/schema/types.py index fcae5e00bf79f00a329d42eb5dc349e31659a4e4..dfaddd2e7e7d87a1b6b2618fc459134e8e136b9a 100644 --- a/openbook_auth/schema/types.py +++ b/openbook_auth/schema/types.py @@ -108,9 +108,10 @@ class User: @strawberry_django.field() def connection_status_to_myself(self, info) -> Optional[UserConnectionStatus]: - if info.context.request.user.is_anonymous: + user = info.context.request.user + if user.is_anonymous: return None - return info.context.request.user.get_connection_status_with_user_id(user_id=self.id) + return user.get_connection_status_with_user_id(user_id=self.id) @strawberry_django.field() def connection_status_to_space(self, info, space_id: uuid.UUID) -> List[Optional[SpaceUserConnectionType]]: diff --git a/openbook_auth/serializers/auth_serializers.py b/openbook_auth/serializers/auth_serializers.py index 5f8f37768ab8a364eb012bc079b6b20bd7420c3a..2e7a0be89e67beb4ec5576a13417d32c6ecad155 100644 --- a/openbook_auth/serializers/auth_serializers.py +++ b/openbook_auth/serializers/auth_serializers.py @@ -13,7 +13,6 @@ from openbook_auth.validators import ( username_not_taken_validator, email_not_taken_validator, user_email_exists, - user_username_exists, ) from django.contrib.auth.password_validation import validate_password diff --git a/openbook_auth/serializers/linked_users_serializers.py b/openbook_auth/serializers/linked_users_serializers.py index 1216e2a3ef13bcb81779bafb0c09eb906753b188..3162c583ea839c8d22430d4055d291b75c492805 100644 --- a/openbook_auth/serializers/linked_users_serializers.py +++ b/openbook_auth/serializers/linked_users_serializers.py @@ -11,7 +11,6 @@ from openbook_common.serializers_fields.user import ( AreNewPostNotificationsEnabledForUserField, ) from openbook_communities.models import CommunityMembership, CommunityInvite -from openbook_communities.serializers_fields import CommunityMembershipsField from openbook_communities.validators import ( community_name_exists, community_name_characters_validator, diff --git a/openbook_auth/serializers/users_serializers.py b/openbook_auth/serializers/users_serializers.py index 77714d2facf75b45930f55651855941c7ffa0299..11c4232d4335a910fee93c5f5641dd04e5a5d033 100644 --- a/openbook_auth/serializers/users_serializers.py +++ b/openbook_auth/serializers/users_serializers.py @@ -68,20 +68,6 @@ class GetUserUserCircleSerializer(serializers.ModelSerializer): fields = ("id", "name", "color", "users_count") -class GetUserUserListEmojiSerializer(serializers.ModelSerializer): - class Meta: - model = Emoji - fields = ("id", "image", "keyword") - - -class GetUserUserListSerializer(serializers.ModelSerializer): - emoji = GetUserUserListEmojiSerializer(many=False) - - class Meta: - model = List - fields = ("id", "name", "emoji") - - class GetUserUserSerializer(serializers.ModelSerializer): profile = GetUserUserProfileSerializer(many=False) followers_count = FollowersCountField() diff --git a/openbook_auth/tasks.py b/openbook_auth/tasks.py index 2caabf93278478d727ba7636bf650bde9451db22..92918cd1fefc152a9b9620a389e4c26d20416c42 100644 --- a/openbook_auth/tasks.py +++ b/openbook_auth/tasks.py @@ -79,7 +79,7 @@ def create_matrix_user_account(identity: str): # status_code is 200, when the user already exists and 201, when it was created return response.status_code == 201 except RequestException as ex: - raise MatrixRequestError("Could not send request to the Matrix Server", str(ex), 0) + raise MatrixRequestError("Could not send request to the Matrix Server", str(ex), 0) from None def send_extend_ory_session(session_id: str): diff --git a/openbook_auth/tests/helpers.py b/openbook_auth/tests/helpers.py index ff4612dd646b83477e22f7293f8e5b100dbcfe0d..83974d20f37fe02b1f1356765e8fb1e4c4d521c9 100644 --- a/openbook_auth/tests/helpers.py +++ b/openbook_auth/tests/helpers.py @@ -58,7 +58,7 @@ def api_update_authenticated_user_names_data(user, first_name, last_name): response = async_to_sync(schema.execute)( """ mutation test($input: UpdateAuthenticatedUserInput!) { - updateAuthenticatedUser(input: $input) { + updateAuthenticatedUserV2(input: $input) { id firstName lastName @@ -70,7 +70,7 @@ def api_update_authenticated_user_names_data(user, first_name, last_name): variable_values={"input": {"firstName": first_name, "lastName": last_name}}, ) assert response.errors is None - return response.data["updateAuthenticatedUser"] + return response.data["updateAuthenticatedUserV2"] def get_user_connection_status_to_space_data(user_to_get, space, calling_user=None): diff --git a/openbook_auth/tests/test_graphql.py b/openbook_auth/tests/test_graphql.py index 84072244682f3095c8aa205c2778fcec89c86f94..eb2875047538ad6a1b037e03e8c2a9f310480174 100644 --- a/openbook_auth/tests/test_graphql.py +++ b/openbook_auth/tests/test_graphql.py @@ -32,8 +32,6 @@ from openbook_auth.tests.helpers import ( get_connected_users, get_connected_users_raw, ) -from openbook_common.enums import FeatureToggleName, FeatureToggleType -from openbook_common.models import FeatureToggle from openbook_common.tests.helpers import ( assert_is_unauthorized, make_authentication_headers_for_user, @@ -70,51 +68,6 @@ def django_db_setup(django_db_setup, django_db_blocker): @pytest.mark.django_db class TestUsers: - @pytest.fixture(autouse=True) - def setup(self): - notifications_toggle = FeatureToggle.objects.create( - name=FeatureToggleName.NOTIFICATIONS.value, status=FeatureToggleType.PRODUCTION - ) - yield - notifications_toggle.delete() - - # TODO HOLI-8088 remove test once get_authenticated_user is removed - @pytest.mark.asyncio - @mock.patch("django.utils.translation.get_language_from_request", return_value="en") - @mock.patch("openbook_notifications.api.NotificationApi.create_or_update_subscriber") - async def test_get_authenticated_user_returns_self_reference( - self, mock_create_or_update_subscriber, mock_get_language_from_request, client - ): - user = await sync_to_async(make_user)(name="John", last_name="Holi") - headers = await sync_to_async(make_authentication_headers_for_user)(user) - endpoint = reverse("graphql-endpoint") - graphql_query = """ - query { - authenticatedUser { - id - fullName - engagementLevel - } - } - """ - response = await client.post( - path=endpoint, - data=json.dumps({"query": graphql_query}), - content_type="application/json", - **headers, - ) - - assert response.status_code == status.HTTP_200_OK - - authenticated_user = json.loads(response.content)["data"]["authenticatedUser"] - assert authenticated_user["id"] == str(user.uuid) - assert authenticated_user["fullName"] == user.profile.full_name() - assert authenticated_user["engagementLevel"] == user.profile.engagement_level - - mock_create_or_update_subscriber.assert_called_once_with( - user=user, locale=mock_get_language_from_request.return_value - ) - @pytest.mark.asyncio @mock.patch("django.utils.translation.get_language_from_request", return_value="en") @mock.patch("openbook_notifications.api.NotificationApi.create_or_update_subscriber") @@ -133,6 +86,7 @@ class TestUsers: trackingConsentAnalytics trackingConsentPersonalization isEmployee + connectionStatusToMyself } } """ @@ -152,10 +106,7 @@ class TestUsers: assert authenticated_user["trackingConsentAnalytics"] == user.tracking_consent_analytics assert authenticated_user["trackingConsentPersonalization"] == user.tracking_consent_personalization assert authenticated_user["isEmployee"] == user.is_employee - - mock_create_or_update_subscriber.assert_called_once_with( - user=user, locale=mock_get_language_from_request.return_value - ) + assert authenticated_user["connectionStatusToMyself"] is None def test_get_user_by_name_returns_only_matching_users_without_logged_in_user(self): user = make_user(name="Peter", last_name="Moser") @@ -264,96 +215,6 @@ class TestUsers: assert response.errors is not None assert response.data is None - # TODO HOLI-8088 remove test once update_authenticated_user is removed - @mock.patch("openbook_auth.tasks.requests.patch") - @mock.patch("openbook_auth.schema.mutations.publish_user_name_updated_event") - @mock.patch("django.utils.translation.get_language_from_request", return_value="en") - @mock.patch("openbook_notifications.api.NotificationApi.create_or_update_subscriber") - @mock.patch("openbook_auth.schema.mutations.track") - def test_can_update_authenticated_user( - self, - mock_track, - mock_create_or_update_subscriber, - mock_get_language_from_request, - mock_publish_user_name_updated_event, - mock_patch, - ): - response = self.create_response(200) - mock_patch.return_value = response - user = make_user(name="Fritz", last_name="Meier", identity="fritz") - community = make_community() - community.add_member(user) - pronouns = "She/her" - location = "Bremerhaven" - - assert User.objects.get(username=user.username).profile.pronouns != pronouns - first_name = "Peter" - last_name = "Shaw" - engagement_level = EngagementLevelChoice.INITIATOR - - json_body = [ - {"op": "add", "path": "/traits/given_name", "value": f"{first_name}"}, - {"op": "add", "path": "/traits/family_name", "value": f"{last_name}"}, - {"op": "add", "path": "/traits/name", "value": f"{first_name} {last_name}"}, - ] - - request = HttpRequest() - request.COOKIES = {} - request.user = user - - executed = async_to_sync(schema.execute)( - """ - mutation test($input: UpdateAuthenticatedUserInput!) { - updateAuthenticatedUser(input: $input) { - id - pronouns - location - geolocationId - engagementLevel - } - } - """, - context_value=benedict({"request": request}), - variable_values={ - "input": { - "pronouns": pronouns, - "firstName": first_name, - "lastName": last_name, - "location": location, - "geolocation": geolocation, - "engagementLevel": engagement_level, - } - }, - ) - - assert executed.errors is None - - mock_patch.assert_called_once_with( - f"{settings.ORY_BASE_URL}/admin/identities/{user.username}", - headers={"Authorization": f"Bearer {settings.ORY_ACCESS_TOKEN}"}, - json=json_body, - timeout=30, - ) - updated_user = executed.data["updateAuthenticatedUser"] - graphql_pronouns = updated_user["pronouns"] - persisted_profile = User.objects.get(username=user.username).profile - - assert graphql_pronouns == persisted_profile.pronouns - assert graphql_pronouns == pronouns - assert last_name == persisted_profile.last_name - assert first_name == persisted_profile.name - assert persisted_profile.engagement_level == EngagementLevelChoice.INITIATOR - assert location == persisted_profile.location - assert geolocation_id == persisted_profile.geolocation_id - assert persisted_profile.geolocation is not None - assert geos.GEOSGeometry(json.dumps(geolocation_geometry)) == persisted_profile.geolocation - - mock_publish_user_name_updated_event.assert_called_once_with(user=user) - mock_create_or_update_subscriber.assert_called_once_with( - user=user, locale=mock_get_language_from_request.return_value - ) - mock_track.assert_called_once_with(user, TrackingEvent("userUpdated")) - @mock.patch("openbook_auth.tasks.requests.patch") @mock.patch("openbook_auth.schema.mutations.publish_user_name_updated_event") @mock.patch("django.utils.translation.get_language_from_request", return_value="en") diff --git a/openbook_auth/tests/test_user_model.py b/openbook_auth/tests/test_user_model.py index a56d92fb5b045ef1d1109759a9ec7fd62fd8acc0..139aa78393b532a015cd7e26613af36c184a9184 100644 --- a/openbook_auth/tests/test_user_model.py +++ b/openbook_auth/tests/test_user_model.py @@ -54,14 +54,14 @@ class UserModelTest(TestAssertionsMixin, TransactionTestCase): self.assertTrue(membership_requestor.is_member_of_community_with_name(space.name)) self.assertFalse(membership_requestor.is_membership_requestor_of_community_with_name(space.name)) - self.assertNotification( + self.assert_notification( membership_requestor.notifications.get(), type=Notification.COMMUNITY_MEMBERSHIP_APPROVED, user=membership_requestor, space=returned_space, ) - self.assertNotification( + self.assert_notification( space_creator.notifications.get(), type=Notification.COMMUNITY_MEMBERSHIP_APPROVED, user=membership_requestor, diff --git a/openbook_categories/i18n/update_translations.py b/openbook_categories/i18n/update_translations.py index 9c6da2a0a1ee3819f5e2efaf5ed3aa729a2fdb1a..d5648f9f6e5a6dab35a0939883ab5260d50dbc88 100644 --- a/openbook_categories/i18n/update_translations.py +++ b/openbook_categories/i18n/update_translations.py @@ -1,6 +1,7 @@ +# ruff: noqa: T201 import json import polib -import os, django +import os from django.conf import settings CATEGORIES_PATH = os.path.join(settings.BASE_DIR, "openbook_categories/fixtures/categories.json") @@ -29,7 +30,7 @@ for language in settings.LANGUAGES: category["fields"][f"{field}_{language_code}"] = field_locale_value except KeyError: print(f"Field {field_value} not found in {language_code}/django.po") - # print(category['fields']['description_en']) + # print(category['fields']['description_en']) # noqa: ERA001 with open(CATEGORIES_PATH, "w") as json_file: json.dump(categories_dict, json_file, ensure_ascii=False) diff --git a/openbook_circles/admin.py b/openbook_circles/admin.py deleted file mode 100644 index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..0000000000000000000000000000000000000000 --- a/openbook_circles/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/openbook_circles/models.py b/openbook_circles/models.py index a13b0acec7941702dae25093df8e8ef03d0a0eba..2c0d9010941efe66b6e61ac1e11b59b0e0c5cea8 100644 --- a/openbook_circles/models.py +++ b/openbook_circles/models.py @@ -1,13 +1,9 @@ -import uuid - -from django.conf import settings from django.db import models # Create your models here. from django.utils import timezone from openbook.settings import CIRCLE_MAX_LENGTH, COLOR_ATTR_MAX_LENGTH -from openbook_auth.models import User from openbook_common.models import ModelWithUUID from openbook_common.utils.model_loaders import get_connection_model from openbook_connections.models import Connection diff --git a/openbook_circles/serializers.py b/openbook_circles/serializers.py index 8a00021269d667261dbf62d50383c61336196372..3216febd2c48aff9d779b238981bbebda7dc8da8 100644 --- a/openbook_circles/serializers.py +++ b/openbook_circles/serializers.py @@ -9,7 +9,6 @@ from openbook_circles.validators import circle_id_exists from openbook_common.models import Badge from openbook_common.serializers_fields.user import ( IsFullyConnectedField, - IsPendingConnectionConfirmation, ) from openbook_common.validators import hex_color_validator diff --git a/openbook_common/admin.py b/openbook_common/admin.py index 71e58611c207f5b1cb42d5962ef7ef3321a386a7..ad7ee24bbbb2d398c697e91c717dae7142e02963 100644 --- a/openbook_common/admin.py +++ b/openbook_common/admin.py @@ -5,7 +5,7 @@ from modeltranslation.admin import TranslationAdmin from openbook_common.enums import NeedUpdateType from openbook_common.forms import AppVersionForm -from openbook_common.models import Emoji, EmojiGroup, Badge, ProxyBlacklistedDomain, AppVersion, FeatureToggle +from openbook_common.models import Emoji, EmojiGroup, Badge, ProxyBlacklistedDomain, AppVersion class EmojiGroupEmoji(admin.TabularInline): @@ -84,10 +84,3 @@ class AppVersionAdmin(admin.ModelAdmin): if obj: # Editing return self.readonly_fields return () # Creating - - -@admin.register(FeatureToggle) -class FeatureToggleAdmin(admin.ModelAdmin): - readonly_fields = ("created",) - list_display = ("name", "status", "created") - ordering = ("name",) diff --git a/openbook_common/enums.py b/openbook_common/enums.py index 06e55622949216e7c389206c4892279408f5c0b6..018a43bd16e52dddca6937b2ce8470f8ff294de2 100644 --- a/openbook_common/enums.py +++ b/openbook_common/enums.py @@ -26,15 +26,3 @@ class NeedUpdateType(TextChoices): NO = ("NO",) RECOMMENDED = ("RECOMMENDED",) MANDATORY = ("MANDATORY",) - - -@strawberry.enum -class FeatureToggleType(TextChoices): - OFF = ("OFF",) - PREVIEW = ("PREVIEW",) - PRODUCTION = ("PRODUCTION",) - MAINTENANCE = ("MAINTENANCE",) - - -class FeatureToggleName(Enum): - NOTIFICATIONS = "NOTIFICATIONS" diff --git a/openbook_common/helpers.py b/openbook_common/helpers.py index 6111356f584ccf3b61b5d8b26604951846812096..c5472a57bd5c7a6c01bad69b593f0b81f756eb5e 100644 --- a/openbook_common/helpers.py +++ b/openbook_common/helpers.py @@ -16,36 +16,10 @@ from urlextract import URLExtract from openbook.settings import ALERT_HOOK_URL from openbook_common.utils.model_loaders import get_language_model -from openbook_translation import translation_strategy # seed the language detector DetectorFactory.seed = 0 - -def get_detected_language_code(text): - try: - detected_lang = translation_strategy.get_detected_language_code(text) - except LangDetectException: - detected_lang = None - return detected_lang - - -def get_language_for_text(text): - language_code = get_detected_language_code(text) - Language = get_language_model() - if language_code is not None and Language.objects.filter(code=language_code).exists(): - return Language.objects.get(code=language_code) - - return None - - -def get_supported_translation_language(language_code): - Language = get_language_model() - supported_translation_code = translation_strategy.get_supported_translation_language_code(language_code) - - return Language.objects.get(code=supported_translation_code) - - extractor = URLExtract() diff --git a/openbook_common/i18n/update_translations_emoji_groups.py b/openbook_common/i18n/update_translations_emoji_groups.py index a5b6e443ff837a2dba206fb53fb9a1e98093b400..72194d820465937558de3e678f12eb2e78c7c85c 100644 --- a/openbook_common/i18n/update_translations_emoji_groups.py +++ b/openbook_common/i18n/update_translations_emoji_groups.py @@ -1,6 +1,7 @@ +# ruff: noqa: T201 import json import polib -import os, django +import os from django.conf import settings CATEGORIES_PATH = os.path.join(settings.BASE_DIR, "openbook_common/fixtures/emoji-groups.json") @@ -29,7 +30,7 @@ for language in settings.LANGUAGES: emoji["fields"][f"{field}_{language_code}"] = field_locale_value except KeyError: print(f"Field {field_value} not found in {language_code}/django.po") - # print(emoji['fields']['description_en']) + # print(emoji['fields']['description_en']) # noqa: ERA001 with open(CATEGORIES_PATH, "w") as json_file: json.dump(emoji_dict, json_file, ensure_ascii=False) diff --git a/openbook_common/i18n/update_translations_emojis.py b/openbook_common/i18n/update_translations_emojis.py index 672c81091dc0f29c08206f8090691512a1c93e05..ee07e73fe6a3a3b9d8fc2eff41beceefcce66e67 100644 --- a/openbook_common/i18n/update_translations_emojis.py +++ b/openbook_common/i18n/update_translations_emojis.py @@ -1,6 +1,7 @@ +# ruff: noqa: T201 import json import polib -import os, django +import os from django.conf import settings CATEGORIES_PATH = os.path.join(settings.BASE_DIR, "openbook_common/fixtures/emojis.json") diff --git a/openbook_common/management/commands/flush_proxy_blacklisted_domains.py b/openbook_common/management/commands/flush_proxy_blacklisted_domains.py index f7aa2d13952713f4c0e93902503328afb244afe7..eb006ee4aac803dd61a64133fc98925078b98b8b 100644 --- a/openbook_common/management/commands/flush_proxy_blacklisted_domains.py +++ b/openbook_common/management/commands/flush_proxy_blacklisted_domains.py @@ -1,11 +1,9 @@ from django.core.management.base import BaseCommand from openbook_common.models import ProxyBlacklistedDomain -from openbook_common.utils.model_loaders import get_user_invite_model, get_badge_model import logging from django.core.validators import URLValidator -from django.core.exceptions import ValidationError logger = logging.getLogger(__name__) diff --git a/openbook_common/management/commands/generate_dummy_data.py b/openbook_common/management/commands/generate_dummy_data.py index 5e67775b00635dcca8d152b3e3484642b2e6229c..8007010e6d459fd47be63f20ad639e645c8588de 100644 --- a/openbook_common/management/commands/generate_dummy_data.py +++ b/openbook_common/management/commands/generate_dummy_data.py @@ -1,3 +1,4 @@ +# ruff: noqa: T201 import io import random import sys @@ -33,7 +34,7 @@ NO_TOPICS_AVAILABLE = "no topics available, please generate topics first (via lo @contextmanager def image_fetcher(url=f"{settings.IMAGEKIT_URL}/tr:w-300,h-300/default-image.jpg"): - with request.urlopen(url) as response: + with request.urlopen(url) as response: # noqa: S310 with io.BytesIO(response.read()) as image_io: in_memory_file = InMemoryUploadedFile( image_io, "ImageField", "image.jpg", "JPEG", sys.getsizeof(image_io), None @@ -78,7 +79,7 @@ class Command(BaseCommand): raise CommandError(NO_USERS_AVAILABLE) for _ in range(nr): with image_fetcher() as image_file: - random.choice(users).create_post( + random.choice(users).create_post( # noqa: S311 is_public=True, title=make_fake_post_title(), text=fake.text(max_nb_chars=300), @@ -92,18 +93,18 @@ class Command(BaseCommand): raise CommandError(NO_USERS_AVAILABLE) for _ in range(nr): with image_fetcher() as image_file: - post = random.choice(users).create_post( + post = random.choice(users).create_post( # noqa: S311 is_public=True, title=make_fake_post_title(), text=fake.text(max_nb_chars=300), image=image_file ) - comment_count = random.randint(1, 5) + comment_count = random.randint(1, 5) # noqa: S311 for _ in range(comment_count): - post.comment(fake.text(max_nb_chars=300), random.choice(users)) + post.comment(fake.text(max_nb_chars=300), random.choice(users)) # noqa: S311 @staticmethod def _generate_reactions(obj, users): for user in users: - if random.choice([False, True]): - obj.react(user, random.choice([_ for _ in ReactionType])) + if random.choice([False, True]): # noqa: S311 + obj.react(user, random.choice([_ for _ in ReactionType])) # noqa: S311 def generate_post_with_comment_and_reaction(self, nr): print(f"generate {nr} posts with comments and reactions") @@ -112,14 +113,14 @@ class Command(BaseCommand): raise CommandError(NO_USERS_AVAILABLE) for _ in range(nr): with image_fetcher() as image_file: - post = random.choice(users).create_post( + post = random.choice(users).create_post( # noqa: S311 is_public=True, title=make_fake_post_title(), text=fake.text(max_nb_chars=300), image=image_file ) self._generate_reactions(post, users) - comment_count = random.randint(1, 5) + comment_count = random.randint(1, 5) # noqa: S311 for _ in range(comment_count): - post_comment = post.comment(fake.text(max_nb_chars=300), random.choice(users)) - if random.random() < 0.2: + post_comment = post.comment(fake.text(max_nb_chars=300), random.choice(users)) # noqa: S311 + if random.random() < 0.2: # noqa: S311 self._generate_reactions(post_comment, users) def generate_insight(self, nr): @@ -133,14 +134,14 @@ class Command(BaseCommand): for _ in range(nr): with image_fetcher() as image_file: insight = Insight.objects.create( - creator=random.choice(users), + creator=random.choice(users), # noqa: S311 title=make_fake_post_title(), description=fake.text(max_nb_chars=300), date_published=now(), header_image=image_file, ) - for _ in range(random.randint(1, 8)): - insight.posts.add(random.choice(posts)) + for _ in range(random.randint(1, 8)): # noqa: S311 + insight.posts.add(random.choice(posts)) # noqa: S311 def generate_space(self, nr): print(f"generate {nr} spaces") @@ -152,12 +153,12 @@ class Command(BaseCommand): raise CommandError(NO_TOPICS_AVAILABLE) for _ in range(nr): with image_fetcher() as image_file: - random.choice(users).create_community( + random.choice(users).create_community( # noqa: S311 title=make_community_title(), type="P", description=fake.text(max_nb_chars=300), avatar=image_file, cover=image_file, - topic_ids=[random.choice(topic_ids)], + topic_ids=[random.choice(topic_ids)], # noqa: S311 location="Germany", ) diff --git a/openbook_common/management/commands/import_proxy_blacklisted_domains.py b/openbook_common/management/commands/import_proxy_blacklisted_domains.py index efbd530939ff0dc7bd5a0c02db58dfb1168baca3..6759db707b9d41626724e7254b93d14c84bc5719 100644 --- a/openbook_common/management/commands/import_proxy_blacklisted_domains.py +++ b/openbook_common/management/commands/import_proxy_blacklisted_domains.py @@ -1,15 +1,12 @@ -import ipaddress from urllib.parse import urlparse from django.core.management.base import BaseCommand from tldextract import tldextract from openbook_common.models import ProxyBlacklistedDomain -from openbook_common.utils.model_loaders import get_user_invite_model, get_badge_model import logging from django.core.validators import URLValidator -from django.core.exceptions import ValidationError logger = logging.getLogger(__name__) @@ -23,7 +20,6 @@ class Command(BaseCommand): parser.add_argument("--file", type=str, help="The file to import the blacklisted domains from") def handle(self, *args, **options): - imported_domains = 0 already_existing_domains = 0 diff --git a/openbook_common/management/commands/worker_health_check.py b/openbook_common/management/commands/worker_health_check.py index 01e9c6b0a6f4bc5c8893dc540c31f13f32fd2463..4261a19dbe175796406ffc1c5b6e980fe5f35852 100644 --- a/openbook_common/management/commands/worker_health_check.py +++ b/openbook_common/management/commands/worker_health_check.py @@ -1,3 +1,4 @@ +# ruff: noqa: T201 from sys import exit from logging import getLogger @@ -7,7 +8,7 @@ from django.core.management.base import BaseCommand from openbook_common.utils.rq_helpers import RQStats from openbook_common.helpers import send_alert_to_channel -from openbook.settings import RQ_QUEUES, FAILED_JOB_THRESHOLD +from openbook.settings import RQ_QUEUES from openbook.settings import ACTIVE_JOB_THRESHOLD, ACTIVE_WORKER_THRESHOLD @@ -23,7 +24,6 @@ class Command(BaseCommand): env = settings.ENVIRONMENT for queue in RQ_QUEUES.keys(): - rq_stats = RQStats(queue) active_job_count = rq_stats.get_active_job_count() @@ -46,7 +46,6 @@ class Command(BaseCommand): self.retval += 1 def handle(self, *args, **options): - self.retval = 1 try: diff --git a/openbook_common/migrations/0011_delete_featuretoggle.py b/openbook_common/migrations/0011_delete_featuretoggle.py new file mode 100644 index 0000000000000000000000000000000000000000..546b14e121e8fd6553f6bb0b819490d6d0a1cb70 --- /dev/null +++ b/openbook_common/migrations/0011_delete_featuretoggle.py @@ -0,0 +1,16 @@ +# Generated by Django 5.0.8 on 2024-10-14 12:36 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("openbook_common", "0010_alter_appversion_version"), + ] + + operations = [ + migrations.DeleteModel( + name="FeatureToggle", + ), + ] diff --git a/openbook_common/models.py b/openbook_common/models.py index 9f1b6a4c58122db6724f60adb389db2da80e7beb..0cd1c2f921ab244b03224e6ac5ed0475084b533f 100644 --- a/openbook_common/models.py +++ b/openbook_common/models.py @@ -13,7 +13,7 @@ from requests import RequestException from openbook.settings import COLOR_ATTR_MAX_LENGTH from openbook_common.helpers import extract_urls_from_string, attr_filled -from openbook_common.enums import NeedUpdateType, FeatureToggleType +from openbook_common.enums import NeedUpdateType from openbook_common.utils.helpers import normalize_url from openbook_common.validators import hex_color_validator import tldextract @@ -277,15 +277,3 @@ class AppVersion(ModelWithUUID): "version", "build_number", ) - - -class FeatureToggle(ModelWithUUID): - name = models.CharField(max_length=50, unique=True, blank=False, null=False) - created = models.DateTimeField(default=timezone.now, editable=False) - status = models.CharField( - max_length=20, - choices=FeatureToggleType.choices, - default=FeatureToggleType.OFF, - blank=False, - null=False, - ) diff --git a/openbook_common/schema/queries.py b/openbook_common/schema/queries.py index 1e26d163a3206bb96eb8a7670cdc25de24c9373e..cfc09fb167cce2aace27403fe7ad93ea4feca674 100644 --- a/openbook_common/schema/queries.py +++ b/openbook_common/schema/queries.py @@ -1,13 +1,10 @@ -from typing import List - import strawberry import strawberry_django import structlog from django.db.models import Q from openbook_common.models import AppVersion as AppVersionModel -from openbook_common.models import FeatureToggle as FeatureToggleModel -from openbook_common.schema.types import AppVersionResponse, FeatureToggle +from openbook_common.schema.types import AppVersionResponse from openbook_common.serializers import AppVersionSerializer logger = structlog.get_logger(__name__) @@ -33,7 +30,3 @@ class Query: logger.info(f"App version {version} not found. Creating a new App Version.") return AppVersionModel.objects.create(version=data["version"], build_number=data["build_number"]) - - @strawberry_django.field(deprecation_reason="Deprecated since 1.26 as it was replaced by Posthog feature flags") - async def feature_toggles(self) -> List[FeatureToggle]: - return FeatureToggleModel.objects.all() diff --git a/openbook_common/schema/types.py b/openbook_common/schema/types.py index 715d2801b7b0e5d63420e4ccd08f4418169ed689..e6372b1a650b2e00fef53450af4a265fd7f33912 100644 --- a/openbook_common/schema/types.py +++ b/openbook_common/schema/types.py @@ -9,7 +9,6 @@ from django.contrib.gis import geos from django.db.models import QuerySet from openbook_common.models import AppVersion as AppVersionModel -from openbook_common.models import FeatureToggle as FeatureToggleModel from openbook_common.types import GeolocationFeature logger = structlog.get_logger(__name__) @@ -81,13 +80,6 @@ class AppVersionResponse: ) -@strawberry_django.type(FeatureToggleModel) -class FeatureToggle: - id: strawberry.auto - name: strawberry.auto - status: strawberry.auto - - @strawberry.type class Paged(Generic[T]): data: List[Optional[T]] = strawberry_django.field(description="The list of items in this pagination window.") diff --git a/openbook_common/tests/assertions.py b/openbook_common/tests/assertions.py index c5ea2259aa808b45f8726776ecdb27761638fd6a..28078e7367bf257b5cf256477f42489d3976ec80 100644 --- a/openbook_common/tests/assertions.py +++ b/openbook_common/tests/assertions.py @@ -7,8 +7,8 @@ from openbook_posts.schema.types import Post class TestAssertionsMixin: - def assertNotification( - self, + @staticmethod + def assert_notification( notification: Notification, type: Optional[str] = None, user: Optional[User] = None, diff --git a/openbook_common/tests/test_graphql.py b/openbook_common/tests/test_graphql.py index 042356aa09aec46fdfa840f23bfee0a81357b844..95c437df76fccbfbaff7fe96e6611b86cd0b415e 100644 --- a/openbook_common/tests/test_graphql.py +++ b/openbook_common/tests/test_graphql.py @@ -1,14 +1,11 @@ import logging import pytest -from asgiref.sync import async_to_sync from django.conf import settings -from django.core.management import call_command from django.test import AsyncClient -from openbook.graphql_schema import schema -from openbook_common.enums import FeatureToggleType, NeedUpdateType -from openbook_common.models import AppVersion, FeatureToggle +from openbook_common.enums import NeedUpdateType +from openbook_common.models import AppVersion from openbook_common.tests.helpers import ( check_app_version, ) @@ -21,41 +18,8 @@ def client(): return AsyncClient() -@pytest.fixture(scope="session") -def django_db_setup(django_db_setup, django_db_blocker): - with django_db_blocker.unblock(): - call_command("loaddata", "openbook_moderation/fixtures/moderation_categories.json") - - @pytest.mark.django_db class TestCommons: - def test_feature_toggles(self): - feature_toggles = [ - FeatureToggle.objects.create(name="TOGGLE_1_PRODUCTION", status=FeatureToggleType.PRODUCTION), - FeatureToggle.objects.create(name="TOGGLE_2_PRODUCTION", status=FeatureToggleType.PRODUCTION), - FeatureToggle.objects.create(name="TOGGLE_3_OFF", status=FeatureToggleType.OFF), - FeatureToggle.objects.create(name="TOGGLE_4_PREVIEW", status=FeatureToggleType.PREVIEW), - FeatureToggle.objects.create(name="TOGGLE_5_MAINTENANCE", status=FeatureToggleType.MAINTENANCE), - ] - - response = async_to_sync(schema.execute)( - f""" - query {{ - featureToggles {{ - id - name - status - }} - }} - """, - ) - assert response.errors is None - assert response.data is not None - assert len(response.data["featureToggles"]) == len(feature_toggles) - assert response.data["featureToggles"][0]["id"] == str(feature_toggles[0].id) - assert response.data["featureToggles"][0]["name"] == feature_toggles[0].name - assert response.data["featureToggles"][0]["status"] == feature_toggles[0].status - def test_check_app_version(self): app_version = AppVersion.objects.create(version="2023.5.1000", need_update=NeedUpdateType.MANDATORY) app_version_2 = AppVersion.objects.create( diff --git a/openbook_common/tests/test_models.py b/openbook_common/tests/test_models.py deleted file mode 100644 index 6dd039bcd720c437cb17ea56947818ead3d30d40..0000000000000000000000000000000000000000 --- a/openbook_common/tests/test_models.py +++ /dev/null @@ -1,48 +0,0 @@ -import pytest -from django.utils import timezone - -from openbook_common.models import FeatureToggle, FeatureToggleType - - -@pytest.mark.django_db -class TestFeatureToggleModel: - feature_toggle_name = "Test Toggle" - - def test_create_feature_toggle_success(self): - feature_toggle = FeatureToggle.objects.create( - name=self.feature_toggle_name, - status=FeatureToggleType.PRODUCTION, - ) - - saved_toggle = FeatureToggle.objects.get(name=feature_toggle.name) - - assert saved_toggle.name == self.feature_toggle_name - assert saved_toggle.status == FeatureToggleType.PRODUCTION - assert saved_toggle.created is not None - assert saved_toggle.created <= timezone.now() - - def test_create_feature_toggle_error_duplicate(self): - FeatureToggle.objects.create( - name=self.feature_toggle_name, - status=FeatureToggleType.PREVIEW, - ) - - with pytest.raises(Exception): - FeatureToggle.objects.create( - name=self.feature_toggle_name, - status=FeatureToggleType.PRODUCTION, - ) - - def test_update_feature_toggle_success(self): - feature_toggle = FeatureToggle.objects.create( - name=self.feature_toggle_name, - status=FeatureToggleType.PRODUCTION, - ) - - feature_toggle.status = FeatureToggleType.MAINTENANCE - feature_toggle.save() - - updated_toggle = FeatureToggle.objects.get(id=feature_toggle.id) - - assert updated_toggle.name == feature_toggle.name - assert updated_toggle.status == FeatureToggleType.MAINTENANCE diff --git a/openbook_common/utils/helpers.py b/openbook_common/utils/helpers.py index 728900623081c6872ef1daae405be8fc7f6ca2f5..6ff4c99dcf71bc708a7e4703c63cf64a7037ce07 100644 --- a/openbook_common/utils/helpers.py +++ b/openbook_common/utils/helpers.py @@ -15,8 +15,6 @@ from imagekit.models import ProcessedImageField import hashlib import tldextract -r = lambda: secrets.randbelow(255) - def normalise_request_data(request_data): """ @@ -55,19 +53,21 @@ def normalize_list_value_in_request_data(list_name, request_data): def generate_random_hex_color(): + def r(): + return secrets.randbelow(255) + return f"#{r():02X}{r():02X}{r():02X}" def get_random_pastel_color(): # It's a random color jeez, we don't need cryptographically secure randomness - h, s, l = ( - random.random(), - 0.5 + random.random() / 2.0, - 0.4 + random.random() / 5.0, - ) # nosec + h, s, l = ( # noqa: E741 + random.random(), # noqa: S311 + 0.5 + random.random() / 2.0, # noqa: S311 + 0.4 + random.random() / 5.0, # noqa: S311 + ) r, g, b = [int(256 * i) for i in colorsys.hls_to_rgb(h, l, s)] hex_color = f"#{r:02x}{g:02x}{b:02x}" - # color = spectra.html(hex_color).darken(amount=20) return hex_color @@ -82,7 +82,6 @@ def delete_file_field(filefield): pass else: - if isinstance(filefield.field, ProcessedImageField): # ImageKit has a bug where files are cached and not deleted right away # https://github.com/matthewwithanm/django-imagekit/issues/229#issuecomment-315690575 diff --git a/openbook_common/utils/rq_helpers.py b/openbook_common/utils/rq_helpers.py index 4bdfd19e78d8e843cccf10392d41e6f53fac2496..523cbbfbfcf0bef50b6064e12ac8dbb8cb120a4f 100644 --- a/openbook_common/utils/rq_helpers.py +++ b/openbook_common/utils/rq_helpers.py @@ -4,33 +4,28 @@ from django_rq.utils import get_statistics, FailedJobRegistry class RQStats: def __init__(self, queue): - self.stats = get_statistics() self.queue_name = queue def get_active_worker_count(self): - queue = self._get_queue_by_name(self.queue_name) workers = queue.get("workers", 0) return workers def get_active_job_count(self): - queue = self._get_queue_by_name(self.queue_name) jobs = queue.get("jobs", 0) return jobs def get_failed_job_count(self): - queue = self._get_queue_by_name(self.queue_name) failed_jobs = queue.get("failed_jobs", 0) return failed_jobs def _get_queue_by_name(self, name): - queues = self.stats.get("queues") for q in queues: @@ -42,13 +37,11 @@ class RQStats: class FailedRQJobs: def __init__(self, queue): - self.queue = django_rq.get_queue(queue) self.connection = django_rq.get_connection() self.failed_job_registry = FailedJobRegistry(self.queue, self.connection) def delete_all_failed_jobs_from_queue(self): - failed_job_ids = self.failed_job_registry.get_job_ids() for failed_job in failed_job_ids: diff --git a/openbook_communities/admin.py b/openbook_communities/admin.py index 856dc04a31c69d36a1be651a554f79acbb7ef21e..35e4c49005c684a6c21872bb39cb5a16fe442ea0 100644 --- a/openbook_communities/admin.py +++ b/openbook_communities/admin.py @@ -127,7 +127,7 @@ class CommunityTaskAdmin(admin.ModelAdmin): @admin.register(OnboardingStep) -class CommunityTaskAdmin(admin.ModelAdmin): +class CommunityOnboardingStepAdmin(admin.ModelAdmin): model = OnboardingStep list_display = ( "community", diff --git a/openbook_communities/events/payloads.py b/openbook_communities/events/payloads.py index 916f7e8628c2e8a6fb00f161fd959bcb54c4702c..53bdcf55767acdacb80d70f926683b8ebf34ea7f 100644 --- a/openbook_communities/events/payloads.py +++ b/openbook_communities/events/payloads.py @@ -32,12 +32,12 @@ class UserPayload: class SpacePayload: - def __init__(self, id: str, name: str, slug: str, avatar: str, avatarDefaultColor: str): + def __init__(self, id: str, name: str, slug: str, avatar: str, avatar_default_color: str): self.id = id self.name = name self.avatar = avatar self.slug = slug - self.avatarDefaultColor = avatarDefaultColor + self.avatarDefaultColor = avatar_default_color def to_dict(self): return { @@ -60,5 +60,5 @@ class SpacePayload: name=community.title, slug=community.name, avatar=avatarUrl, - avatarDefaultColor=community.avatar_default_color, + avatar_default_color=community.avatar_default_color, ) diff --git a/openbook_communities/schema/queries.py b/openbook_communities/schema/queries.py index 1874f5ba762f52bc5ad0ca4d7465b9907659e256..3e1faad3811cadd2d0525654dbb1adaa7dc3efac 100644 --- a/openbook_communities/schema/queries.py +++ b/openbook_communities/schema/queries.py @@ -354,3 +354,7 @@ class Query: offset, limit, ) + + @strawberry_django.field() + def get_stuff(self) -> List[SpaceUserConnectionType]: + return [SpaceUserConnectionType.MEMBER, SpaceUserConnectionType.CREATOR] diff --git a/openbook_communities/serializers_fields.py b/openbook_communities/serializers_fields.py index 89e33c3779fe0179dbcb3dff39308c936fb7f634..9667c4c3da3d3470ad03fd0e3cd7dfc0127499e8 100644 --- a/openbook_communities/serializers_fields.py +++ b/openbook_communities/serializers_fields.py @@ -1,4 +1,3 @@ -from rest_framework import serializers from rest_framework.fields import Field from openbook_communities.models import Community diff --git a/openbook_communities/tests/test_helpers.py b/openbook_communities/tests/test_helpers.py index 1871b98618918e8fb60510a39a35c425871c1625..29cb41a59fdf8e76ca4eed5b8e3d587ef5802a87 100644 --- a/openbook_communities/tests/test_helpers.py +++ b/openbook_communities/tests/test_helpers.py @@ -1,5 +1,3 @@ -from rest_framework.exceptions import ValidationError - from openbook_common.tests.helpers import make_community from openbook_common.tests.models import OpenbookAPITestCase from openbook_communities.helpers import create_community_name_from_title_and_hash diff --git a/openbook_connections/admin.py b/openbook_connections/admin.py deleted file mode 100644 index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..0000000000000000000000000000000000000000 --- a/openbook_connections/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/openbook_devices/admin.py b/openbook_devices/admin.py deleted file mode 100644 index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..0000000000000000000000000000000000000000 --- a/openbook_devices/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/openbook_devices/models.py b/openbook_devices/models.py index e2ad8b6d647f930b38dd131bd0864c06fca92832..2244c13216d92f4d0454d60306481805368f3703 100644 --- a/openbook_devices/models.py +++ b/openbook_devices/models.py @@ -7,7 +7,6 @@ import strawberry from openbook_auth.models import User from openbook_common.models import ModelWithUUID -from openbook_communities.models import Community class Device(ModelWithUUID): diff --git a/openbook_devices/validators.py b/openbook_devices/validators.py deleted file mode 100644 index b65e570338682df686a2d4a86c3ca2104f494788..0000000000000000000000000000000000000000 --- a/openbook_devices/validators.py +++ /dev/null @@ -1,4 +0,0 @@ -from rest_framework.exceptions import ValidationError, NotFound -from django.utils.translation import gettext_lazy as _ - -from openbook_devices.models import Device diff --git a/openbook_follows/admin.py b/openbook_follows/admin.py deleted file mode 100644 index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..0000000000000000000000000000000000000000 --- a/openbook_follows/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/openbook_hashtags/management/commands/process_post_hashtags.py b/openbook_hashtags/management/commands/process_post_hashtags.py index 54b9d9672b39b56ed09d85253eaaee47c5bb8712..b32d3371334967ed2e28f2c6066603b34ac7fbc0 100644 --- a/openbook_hashtags/management/commands/process_post_hashtags.py +++ b/openbook_hashtags/management/commands/process_post_hashtags.py @@ -1,8 +1,6 @@ from django.core.management.base import BaseCommand import logging -from django.db import transaction - from openbook_common.utils.model_loaders import get_post_model from openbook_posts.jobs import _chunked_queryset_iterator diff --git a/openbook_hashtags/management/commands/update_hashtags_luminance.py b/openbook_hashtags/management/commands/update_hashtags_luminance.py deleted file mode 100644 index 2ae54a18f5dc95f8e553a9666d96ce053f0e7c31..0000000000000000000000000000000000000000 --- a/openbook_hashtags/management/commands/update_hashtags_luminance.py +++ /dev/null @@ -1,45 +0,0 @@ -from django.core.management.base import BaseCommand -import logging - -from django.db import transaction - -from openbook_common.utils.helpers import get_random_pastel_color -from openbook_common.utils.model_loaders import get_hashtag_model - -logger = logging.getLogger(__name__) - - -class Command(BaseCommand): - help = "Update the hashtags luminance" - - def add_arguments(self, parser): - parser.add_argument("--luminance", type=int, help="The luminance to change") - - def handle(self, *args, **options): - raise Exception("This function is not available due to removed dependency on spectra.") - # luminance = options.get('luminance', None) - # - # if not luminance: - # raise Exception('--luminance is required') - # - # if luminance > 100 or luminance < -100: - # raise Exception('Luminance must be in between -100 and 100') - # - # Hashtag = get_hashtag_model() - # - # processed_hashtags = 0 - # - # for hashtag in Hashtag.objects.all().iterator(): - # with transaction.atomic(): - # color = spectra.html(hashtag.color) - # if luminance > 0: - # color = color.brighten(luminance) - # else: - # color = color.darken(luminance * -1) - # hashtag.color = color.hexcode - # hashtag.save() - # - # logger.info('Processed hashtags for post with name:' + str(hashtag.name)) - # processed_hashtags = processed_hashtags + 1 - # - # logger.info('Updated %d hashtags colors', processed_hashtags) diff --git a/openbook_importer/__init__.py b/openbook_importer/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/openbook_importer/admin.py b/openbook_importer/admin.py deleted file mode 100644 index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..0000000000000000000000000000000000000000 --- a/openbook_importer/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/openbook_importer/apps.py b/openbook_importer/apps.py deleted file mode 100644 index 43898eeb3deee89031494b63e3cd0f3a254b53f1..0000000000000000000000000000000000000000 --- a/openbook_importer/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class OpenbookImporterConfig(AppConfig): - name = "openbook_importer" diff --git a/openbook_importer/serializers.py b/openbook_importer/serializers.py deleted file mode 100644 index ce7bdf52ae36dfb805d81258770d6505febe2b5e..0000000000000000000000000000000000000000 --- a/openbook_importer/serializers.py +++ /dev/null @@ -1,6 +0,0 @@ -from rest_framework import serializers - - -class ZipfileSerializer(serializers.Serializer): - - serializers.FileField(max_length=20, required=True, allow_empty_file=False) diff --git a/openbook_importer/socialmedia_archive_parser/fb_parser.py b/openbook_importer/socialmedia_archive_parser/fb_parser.py deleted file mode 100755 index fd1c2a095da615e49080d8e43e79e77a26e1d472..0000000000000000000000000000000000000000 --- a/openbook_importer/socialmedia_archive_parser/fb_parser.py +++ /dev/null @@ -1,281 +0,0 @@ -#!/usr/bin/env python3 - -from json import loads -from yaml import safe_load -from hashlib import sha3_256 -from zipfile import PyZipFile -from os import access, R_OK, path -from tempfile import TemporaryDirectory - -from magic import from_buffer - - -class profile_import(object): - - friends = False - albums = False - messages = False - posts = False - - def __init__(self, friends, albums, messages, posts): - - self.friends = friends - self.albums = albums - self.messages = messages - self.posts = posts - - -class zip_parser: - - profile = False - - def __init__(self, filename): - - zipf = PyZipFile(filename) - # test this with corrupt zipfile - zipf.testzip() - size = self._get_extracted_zipsize(zipf) - - # if size > 1gb - if size > 1000000000: - raise BufferError("filesize exceeds 1GB") - - friends = self._extract_friends(zipf) - albums = self._extract_albums(zipf) - messages = self._extract_messages(zipf) - posts = self._extract_posts(zipf) - - self.profile = profile_import(friends, albums, messages, posts) - - def _file_access(self, filename): - - if not access(filename, R_OK): - raise FileNotFoundError(f"{filename} not found") - - return True - - def _return_mime_magic(self, extension): - - mpath = "openbook_importer/socialmedia_archive_parser" - - if not self._file_access(f"{mpath}/mimetypes.yml"): - return False - - types = safe_load(open(f"{mpath}/mimetypes.yml", "r")) - - if "mimetypes" not in types: - raise LookupError("file format incorrect, mimetypes key not found") - - types = types["mimetypes"] - - if extension not in types: - raise KeyError(f"extension not found, unknown filetype for " f"{extension}") - - return types[extension] - - def _check_file_magic(self, zipf, name): - - if name.find(".") != -1: - extension = name.split(".")[-1] - mime = self._return_mime_magic(extension) - - else: - raise TypeError(f"{name} filenames without extension not " "allowed") - - if from_buffer(zipf.read(name), mime=True) not in mime: - raise TypeError(f"{name}'s extension does not " f"match mime-type {mime}") - - def _read_file_from_zip(self, zipf, name): - - if name in zipf.namelist(): - self._check_file_magic(zipf, name) - return zipf.read(name) - - else: - raise FileNotFoundError(f"{name} not found in zip file") - - def _get_extracted_zipsize(self, zipf): - - size = 0 - - for entry in zipf.filelist: - size += entry.file_size - - return size - - def _get_files_from_directory(self, dir_name, zipf, directory=False, filename=False): - - files = set() - for entry in zipf.filelist: - - if not filename: - if not directory and not entry.is_dir(): - name = entry.filename - - elif directory and entry.is_dir(): - name = entry.filename - - else: - name = entry.filename - - if name[0 : len(dir_name) + 1] == f"{dir_name}/": - if name != f"{dir_name}/": - files.add(name) - - else: - if filename in entry.filename and not entry.is_dir(): - name = entry.filename - files.add(name) - - return files - - def _write_file_to_dir(self, dir_name, item, zipf): - - i_path = path.join(dir_name, item.split("/")[-1]) - - with open(i_path, "wb+") as fd: - fd.write(zipf.read(item)) - - def _get_fd_from_file(self, dir_name, itype, item, zipf, mode="r"): - - self._write_file_to_dir(dir_name, item, zipf) - - name = item.split("/")[-1] - fd = open(path.join(dir_name, name), mode) - - return (name, fd) - - def _parse_album_json(self, album_json, zipf): - - photo_attrs = ["uri", "creation_timestamp", "comments", "description"] - - json = loads(self._read_file_from_zip(zipf, album_json)) - - album_name = json["name"] - album = {} - album[album_name] = {"photos": []} - photos = {} - - for photo in json["photos"]: - for attr in photo_attrs: - if attr in photo.keys(): - photos[attr] = photo[attr] - - album[album_name]["photos"].append(photos) - photos = {} - - return album - - def _extract_albums(self, zipf): - - album_defs = self._get_files_from_directory("photos_and_videos/album", zipf) - albums = [] - - for album in album_defs: - albums.append(self._parse_album_json(album, zipf)) - - temp = TemporaryDirectory(dir="media") - - for album in albums: - for value in album.values(): - for file in value["photos"]: - file["uri"] = self._get_fd_from_file(temp.name, "photos_and_videos", file["uri"], zipf, mode="rb") - - return albums - - def _extract_friends(self, zipf): - - json = loads(self._read_file_from_zip(zipf, "friends/friends.json")) - friends = json["friends"] - - profile_info = "profile_information/profile_information.json" - profile_info = loads(self._read_file_from_zip(zipf, profile_info))["profile"] - full_name = profile_info["name"]["full_name"] - - sort_string = [] - friends_hash = [] - - for friend in friends: - sort_string.append(friend["name"]) - sort_string.append(full_name) - sort_string.sort() - - friend_string = f"{':'.join(sort_string)}:{friend['timestamp']}".encode("utf-8") - - friends_hash.append(sha3_256(friend_string).hexdigest()) - sort_string = [] - - return friends_hash - - def _parse_message(self, zipf, message): - - json = loads(self._read_file_from_zip(zipf, message)) - - temp = TemporaryDirectory(dir="media") - - if "messages" in json.keys(): - for m in json["messages"]: - - if "photos" in m.keys(): - for p in m["photos"]: - p["uri"] = self._get_fd_from_file(temp.name, "messages", p["uri"], zipf) - else: - raise KeyError("key messages not found in json") - - return json - - def _extract_messages(self, zipf): - - message_json = self._get_files_from_directory("messages", zipf, filename="message.json") - messages = [] - for message in message_json: - messages.append(self._parse_message(zipf, message)) - - return messages - - def _has_attachment(self, zipf, post): - - temp = TemporaryDirectory(dir="media") - - if "attachments" in post.keys(): - for attachment in post["attachments"]: - if "data" in attachment.keys(): - for item in attachment["data"]: - if "media" in item.keys(): - media = item["media"] - uri = self._get_fd_from_file( - temp.name, - media["uri"].split("/")[0], - media["uri"], - zipf, - mode="rb", - ) - media["uri"] = uri - - if "media_metadata" in media: - media.pop("media_metadata") - - return post - - else: - return False - - def _extract_posts(self, zipf): - - json = loads(self._read_file_from_zip(zipf, "posts/your_posts.json")) - - posts = [] - - if "status_updates" in json.keys(): - for post in json["status_updates"]: - media = self._has_attachment(zipf, post) - - if media: - post = media - - posts.append(post) - - else: - raise KeyError("key status_updates not found in json") - - return posts diff --git a/openbook_importer/socialmedia_archive_parser/mimetypes.yml b/openbook_importer/socialmedia_archive_parser/mimetypes.yml deleted file mode 100644 index 58b82083020e67a33faaa2fbba8e34ab100e2853..0000000000000000000000000000000000000000 --- a/openbook_importer/socialmedia_archive_parser/mimetypes.yml +++ /dev/null @@ -1,18 +0,0 @@ -mimetypes: - mp4: - - "video/mp4" - png: - - "image/png" - jpg: - - 'image/jpg' - - 'image/jpeg' - gif: - - "image/gif" - txt: - - "text/plain" - json: - - "text/plain" - - "application/json" - html: - - "text/plain" - - "text/html" diff --git a/openbook_importer/tests/evil.zip b/openbook_importer/tests/evil.zip deleted file mode 100644 index 7e98d1e8476c6157549d8e64fa52f793d11ce26d..0000000000000000000000000000000000000000 Binary files a/openbook_importer/tests/evil.zip and /dev/null differ diff --git a/openbook_importer/tests/facebook-jaybeenote5.zip b/openbook_importer/tests/facebook-jaybeenote5.zip deleted file mode 100644 index dc4a72c5f745d058c5fb7f0000aa0ea2bc418ff6..0000000000000000000000000000000000000000 Binary files a/openbook_importer/tests/facebook-jaybeenote5.zip and /dev/null differ diff --git a/openbook_importer/tests/invalid.zip b/openbook_importer/tests/invalid.zip deleted file mode 100644 index f6f20afa63ad2ee0a38241c2157a1ff080ecbc37..0000000000000000000000000000000000000000 Binary files a/openbook_importer/tests/invalid.zip and /dev/null differ diff --git a/openbook_insights/tests/test_graphql.py b/openbook_insights/tests/test_graphql.py index 18faeec235b9bcab9c7f465c338f64c9563c8630..5c90c2cf10de86519403493cf36316ec7a1695b7 100644 --- a/openbook_insights/tests/test_graphql.py +++ b/openbook_insights/tests/test_graphql.py @@ -323,7 +323,7 @@ class TestInsights: user_bc = make_user(name="firstB", last_name="lastC") user_cb = make_user(name="firstC", last_name="lastB") make_user(name="firstOther", last_name="lastOther") - user_without_profile = User.objects.create(username=str(uuid.uuid4()), email="ab@test.de", password="password") + user_without_profile = User.objects.create(username=str(uuid.uuid4()), email="ab@test.de", password="password") # noqa: S106 now = timezone.now() Insight.objects.create(creator=user_aa, title="Insight 1", date_published=now) diff --git a/openbook_invitations/management/commands/allocate_invites.py b/openbook_invitations/management/commands/allocate_invites.py index 314a7a3d274e20410672fa8a396cec4e1bf0da7f..81cf8becee504f0f3b377003d066d44196578d19 100644 --- a/openbook_invitations/management/commands/allocate_invites.py +++ b/openbook_invitations/management/commands/allocate_invites.py @@ -1,3 +1,4 @@ +# ruff: noqa: T201 from django.core.management.base import BaseCommand from django.db import IntegrityError diff --git a/openbook_invitations/management/commands/fix_indiegogo_usernames.py b/openbook_invitations/management/commands/fix_indiegogo_usernames.py deleted file mode 100644 index a85004eaf7ae6611bdaf17f849f28a414469b906..0000000000000000000000000000000000000000 --- a/openbook_invitations/management/commands/fix_indiegogo_usernames.py +++ /dev/null @@ -1,34 +0,0 @@ -from django.core.management.base import BaseCommand -from django.db import transaction, IntegrityError, DatabaseError -from openbook_invitations.parsers import ( - parse_kickstarter_csv, - parse_indiegogo_csv, - parse_conflicts_csv, - parse_indiegogo_csv_and_sanitise_usernames, -) - - -class Command(BaseCommand): - help = "Imports backer data into UserInvite models" - - def add_arguments(self, parser): - parser.add_argument("--path", type=str, help="Import from indiegogo typeform") - - def handle(self, *args, **options): - if options["path"]: - filepath = options["path"] - self.handle_indiegogo(filepath) - - def handle_indiegogo(self, filepath): - try: - with transaction.atomic(): - parse_indiegogo_csv_and_sanitise_usernames(filepath) - except IntegrityError as e: - print(f"IntegrityError {e} ") - self.stderr.write("Aborting import of file..") - return - except DatabaseError as e: - print(f"DatabaseError {e} ") - self.stderr.write("Aborting import of file..") - return - self.stdout.write(self.style.SUCCESS("Successfully altered data")) diff --git a/openbook_invitations/management/commands/import_invites.py b/openbook_invitations/management/commands/import_invites.py deleted file mode 100644 index 8f33cfcc8b9cbb35e0541c1af434c6ccff04cf0a..0000000000000000000000000000000000000000 --- a/openbook_invitations/management/commands/import_invites.py +++ /dev/null @@ -1,71 +0,0 @@ -from django.core.management.base import BaseCommand -from django.db import transaction, IntegrityError, DatabaseError -from openbook_invitations.parsers import ( - parse_kickstarter_csv, - parse_indiegogo_csv, - parse_conflicts_csv, -) - - -class Command(BaseCommand): - help = "Imports backer data into UserInvite models" - - def add_arguments(self, parser): - parser.add_argument("--kickstarter", type=str, help="Import from kickstarter csv") - parser.add_argument("--indiegogo", type=str, help="Import from indiegogo typeform") - parser.add_argument("--conflicts", type=str, help="Import from conflicts csv") - - def handle(self, *args, **options): - if options["kickstarter"]: - filepath = options["kickstarter"] - self.handle_kickstarter(filepath) - - if options["indiegogo"]: - filepath = options["indiegogo"] - self.handle_indiegogo(filepath) - - if options["conflicts"]: - filepath = options["conflicts"] - self.handle_conflicts(filepath) - - def handle_kickstarter(self, filepath): - try: - with transaction.atomic(): - parse_kickstarter_csv(filepath) - except IntegrityError as e: - print(f"IntegrityError {e} ") - self.stderr.write("Aborting import of file..") - return - except DatabaseError as e: - print(f"DatabaseError {e} ") - self.stderr.write("Aborting import of file..") - return - self.stdout.write(self.style.SUCCESS("Successfully imported data")) - - def handle_indiegogo(self, filepath): - try: - with transaction.atomic(): - parse_indiegogo_csv(filepath) - except IntegrityError as e: - print(f"IntegrityError {e} ") - self.stderr.write("Aborting import of file..") - return - except DatabaseError as e: - print(f"DatabaseError {e} ") - self.stderr.write("Aborting import of file..") - return - self.stdout.write(self.style.SUCCESS("Successfully imported data")) - - def handle_conflicts(self, filepath): - try: - with transaction.atomic(): - parse_conflicts_csv(filepath) - except IntegrityError as e: - print(f"IntegrityError {e} ") - self.stderr.write("Aborting import of file..") - return - except DatabaseError as e: - print(f"DatabaseError {e} ") - self.stderr.write("Aborting import of file..") - return - self.stdout.write(self.style.SUCCESS("Successfully imported data")) diff --git a/openbook_invitations/management/commands/send_invites.py b/openbook_invitations/management/commands/send_invites.py index bd5cc8c2b8377d0417dfaa478b65bb4f9ce6e35d..5b05efba75f1b2f7da87e4c968f4f6c02d4e05f9 100644 --- a/openbook_invitations/management/commands/send_invites.py +++ b/openbook_invitations/management/commands/send_invites.py @@ -1,5 +1,3 @@ -from smtplib import SMTPException - from django.core.management.base import BaseCommand from django.db import transaction @@ -17,6 +15,6 @@ class Command(BaseCommand): with transaction.atomic(): try: user.send_invite_email() - except Exception as e: + except Exception: self.stderr.write("Exception occurred during send_invite_email") self.stdout.write(self.style.SUCCESS("Successfully sent invitation emails")) diff --git a/openbook_invitations/models.py b/openbook_invitations/models.py index a6b1d82ab0eb262847b41bfa3dadbd3b1ec90c02..94ae508047ec63a18586ceeb35e7e3188b63c26b 100644 --- a/openbook_invitations/models.py +++ b/openbook_invitations/models.py @@ -1,7 +1,4 @@ -from django.contrib.auth.validators import ( - UnicodeUsernameValidator, - ASCIIUsernameValidator, -) +from django.contrib.auth.validators import UnicodeUsernameValidator from django.core.mail import EmailMultiAlternatives from django.core.serializers.json import DjangoJSONEncoder from django.db import models diff --git a/openbook_invitations/parsers.py b/openbook_invitations/parsers.py index d963a855aa0ad7b871fbdeaa5186363d63360b87..0a02eb154efbbd917e45ce5891a253bbcfd4ee4f 100644 --- a/openbook_invitations/parsers.py +++ b/openbook_invitations/parsers.py @@ -1,103 +1,10 @@ -import csv import re import secrets -from openbook_common.models import Badge from openbook_common.utils.model_loaders import get_user_invite_model, get_user_model +import logging -def parse_kickstarter_csv(filepath): - try: - with open(filepath, newline="") as csvfile: - backer_data_reader = csv.reader(csvfile, delimiter=",") - header_row = next(backer_data_reader) - ( - name_col, - email_col, - username_col, - badge_keyword_col, - email_kick_col, - ) = get_column_numbers_for_kickstarter(header_row) - for row in backer_data_reader: - name = row[name_col] - email = row[email_col] - if email is None or email == "": - email = row[email_kick_col] - username = sanitise_username(row[username_col]) - if username is None or username == "": - print("Username was empty for:", name) - username = get_temporary_username(email) - print("Using generated random username @", username) - badge_keyword = row[badge_keyword_col] - badge = Badge.objects.get(keyword=badge_keyword) - UserInvite = get_user_invite_model() - UserInvite.create_invite(name=name, email=email, username=username, badge=badge) - except IOError as e: - print("Unable to read file") - raise e - - -def parse_indiegogo_csv(filepath): - try: - with open(filepath, newline="") as csvfile: - backer_data_reader = csv.reader(csvfile, delimiter=",") - header_row = next(backer_data_reader) - ( - name_col, - email_col, - username_col, - badge_keyword_col, - ) = get_column_numbers_for_indiegogo(header_row) - for row in backer_data_reader: - name = row[name_col] - email = row[email_col] - username = sanitise_username(row[username_col]) - badge_keyword = row[badge_keyword_col] - if badge_keyword: - badge = Badge.objects.get(keyword=badge_keyword) - else: - badge = None - UserInvite = get_user_invite_model() - - if username is None or username == "0" or username == "": - print("Username was empty for:", name) - username = None - invited_user = UserInvite.create_invite(name=name, email=email, username=username, badge=badge) - invited_user.save() - except IOError as e: - print("Unable to read file") - raise e - - -def parse_indiegogo_csv_and_sanitise_usernames(filepath): - try: - with open(filepath, newline="") as csvfile: - backer_data_reader = csv.reader(csvfile, delimiter=",") - header_row = next(backer_data_reader) - ( - name_col, - email_col, - username_col, - badge_keyword_col, - ) = get_column_numbers_for_indiegogo(header_row) - for row in backer_data_reader: - name = row[name_col] - email = row[email_col] - username = sanitise_username(row[username_col]) - badge_keyword = row[badge_keyword_col] - if badge_keyword: - badge = Badge.objects.get(keyword=badge_keyword) - else: - badge = None - UserInvite = get_user_invite_model() - if username is None or username == "0" or username == "": - print("Username was empty for:", name) - username = get_temporary_username(email) - print("Using generated random username @", username) - print(username, email) - update_invite(name=name, email=email, username=username, badge=badge) - except IOError as e: - print("Unable to read file") - raise e +logger = logging.getLogger(__name__) def update_invite(email, name=None, username=None, badge=None): @@ -111,34 +18,10 @@ def update_invite(email, name=None, username=None, badge=None): invite = invites.first() invite.username = username invite.save() - print("New username is: ", invite.username) + logger.info("New username is: ", invite.username) return invite -def parse_conflicts_csv(filepath): - # Hack: Since username is unique, we populate name field with username during - # parsing of this csv so we can import all records. - # This is a one time operation before launch. - try: - with open(filepath, newline="") as csvfile: - backer_data_reader = csv.reader(csvfile, delimiter=",") - header_row = next(backer_data_reader) - name_col, email_col = get_column_numbers_for_conflicts_csv(header_row) - for row in backer_data_reader: - email = row[email_col] - username = row[name_col] - UserInvite = get_user_invite_model() - - if username is None or username == "0" or username == "": - print("Username was empty for:", username) - continue - invited_user = UserInvite.create_invite(name=username, email=email, username=None) - invited_user.save() - except IOError as e: - print("Unable to read file") - raise e - - def sanitise_username(username): chars = "[@#!±$%^&*()=|/><?,:;\~`{}]" return re.sub(chars, "", username).lower().replace(" ", "_").replace("+", "_").replace("-", "_").replace("\\", "") diff --git a/openbook_lists/admin.py b/openbook_lists/admin.py deleted file mode 100644 index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..0000000000000000000000000000000000000000 --- a/openbook_lists/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/openbook_moderation/i18n/update_translations.py b/openbook_moderation/i18n/update_translations.py index af27f0ecdbce5e00654598cccad699496f23597b..e6ebd699ca94937bf21c770401c44ded62b1070c 100644 --- a/openbook_moderation/i18n/update_translations.py +++ b/openbook_moderation/i18n/update_translations.py @@ -1,7 +1,10 @@ import json import polib -import os, django +import os from django.conf import settings +import logging + +logger = logging.getLogger(__name__) MODERATION_CATEGORIES_PATH = os.path.join(settings.BASE_DIR, "openbook_moderation/fixtures/moderation_categories.json") PO_LOCALES_PATH = os.path.join(settings.BASE_DIR, "locale") @@ -11,7 +14,7 @@ for language in settings.LANGUAGES: language_code = language[0] if language_code == "en": continue - print(f"Processing locale {language_code}") + logger.debug(f"Processing locale {language_code}") po_locale = polib.pofile(os.path.join(PO_LOCALES_PATH, f"{language_code}/LC_MESSAGES/django.po")) po_dict = {} @@ -28,7 +31,7 @@ for language in settings.LANGUAGES: field_locale_value = po_dict[field_value] category["fields"][f"{field}_{language_code}"] = field_locale_value except KeyError: - print(f"Field {field_value} not found in {language_code}/django.po") + logger.debug(f"Field {field_value} not found in {language_code}/django.po") with open(MODERATION_CATEGORIES_PATH, "w") as json_file: json.dump(categories_dict, json_file, ensure_ascii=False) diff --git a/openbook_notifications/api.py b/openbook_notifications/api.py index 03f1bc5122758efec3934c9d229145e5eae516b6..5995ecc5dc0d39cc72f5e7e51474985ed993d175 100644 --- a/openbook_notifications/api.py +++ b/openbook_notifications/api.py @@ -1,14 +1,16 @@ from __future__ import annotations + +from functools import wraps from typing import List, Optional, TYPE_CHECKING -from requests.exceptions import HTTPError -import structlog -from novu.api.message import MessageApi +import structlog from novu.api.event import EventApi +from novu.api.message import MessageApi from novu.api.subscriber import SubscriberApi from novu.dto.subscriber import SubscriberDto +from requests.exceptions import HTTPError -from openbook_notifications.toggles import notifications_toggle +from openbook.settings import IS_PRODUCTION, IS_STAGING from openbook_notifications.enums import NotificationProvider, NotificationChannel logger = structlog.get_logger(__name__) @@ -18,9 +20,24 @@ if TYPE_CHECKING: from openbook_auth.models import User +def noop_when_disabled(func): + @wraps(func) + def wrapper(*args, **kwargs): + # As long as the old Okuna-based subsystem is not removed, some tests still rely on the code being executed. + if NotificationApi.enabled: + return func(*args, **kwargs) + else: + return + + return wrapper + + class NotificationApi: + # translates to practically being disabled in testing and local development, unless overridden by test setup + enabled = IS_PRODUCTION or IS_STAGING + @staticmethod - @notifications_toggle + @noop_when_disabled def create_subscriber(user: User, locale: Optional[str]): subscriber = SubscriberDto( first_name=user.profile.name, @@ -34,12 +51,12 @@ class NotificationApi: return SubscriberApi().create(subscriber) @staticmethod - @notifications_toggle + @noop_when_disabled def find_subscriber(user_id: str): return SubscriberApi().get(subscriber_id=str(user_id)) @staticmethod - @notifications_toggle + @noop_when_disabled def find_or_create_subscriber(user: User, locale: Optional[str]): try: subscriber = NotificationApi.find_subscriber(user_id=user.username) @@ -52,7 +69,7 @@ class NotificationApi: return subscriber @staticmethod - @notifications_toggle + @noop_when_disabled def update_subscriber(user: User, locale: Optional[str]): subscriber = SubscriberDto( subscriber_id=str(user.username), @@ -67,7 +84,7 @@ class NotificationApi: return SubscriberApi().put(subscriber) @staticmethod - @notifications_toggle + @noop_when_disabled def create_or_update_subscriber(user: User, locale: Optional[str]): try: subscriber = NotificationApi.update_subscriber(user, locale) @@ -82,7 +99,7 @@ class NotificationApi: return subscriber @staticmethod - @notifications_toggle + @noop_when_disabled def set_subscriber_credentials( subscriber_id: str, provider_id: NotificationProvider, @@ -95,7 +112,7 @@ class NotificationApi: return SubscriberApi().credentials(subscriber_id, provider_id.value.value, webhook_url, device_tokens) @staticmethod - @notifications_toggle + @noop_when_disabled def delete_subscriber(user_id: str): try: return SubscriberApi().delete(subscriber_id=str(user_id)) @@ -107,12 +124,12 @@ class NotificationApi: raise @staticmethod - @notifications_toggle + @noop_when_disabled def cancel_workflow(transaction_id: str): EventApi().delete(transaction_id=transaction_id) @staticmethod - @notifications_toggle + @noop_when_disabled def delete_in_app_messages(transaction_id: str, subscriber_id: str | None = None): messages = MessageApi().list( transaction_id=transaction_id, diff --git a/openbook_notifications/models/__init__.py b/openbook_notifications/models/__init__.py index 25c18f94bbe50a5aa99693cfcd77ade58ae3f2e6..82da363a47b9dedbbda989e1b9878601c6563805 100644 --- a/openbook_notifications/models/__init__.py +++ b/openbook_notifications/models/__init__.py @@ -1,21 +1,38 @@ -from .notification import Notification -from .connection_confirmed_notification import ConnectionConfirmedNotification -from .connection_request_notification import ConnectionRequestNotification -from .follow_notification import FollowNotification -from .post_comment_notification import PostCommentNotification -from .post_comment_reply_notification import PostCommentReplyNotification -from .post_reaction_notification import PostReactionNotification -from .community_invite_notification import CommunityInviteNotification -from .post_comment_reaction_notification import PostCommentReactionNotification -from .post_comment_user_mention_notification import PostCommentUserMentionNotification -from .post_user_mention_notification import PostUserMentionNotification -from .community_new_post_notification import CommunityNewPostNotification -from .user_new_post_notification import UserNewPostNotification -from .follow_request_approved_notification import FollowRequestApprovedNotification -from .follow_request_notification import FollowRequestNotification -from .community_membership_approved_notification import CommunityMembershipApprovedNotification -from .community_membership_request_notification import CommunityMembershipRequestNotification -from .community_membership_revoked_notification import CommunityMembershipRevokedNotification -from .community_admin_privileges_granted_notification import CommunityAdminPrivilegesGrantedNotification -from .community_admin_privileges_revoked_notification import CommunityAdminPrivilegesRevokedNotification -from .community_invite_declined_notification import CommunityInviteDeclinedNotification +# class are imported with 'import A as A' to instruct Ruff to avoid marking it as unused, see https://docs.astral.sh/ruff/rules/unused-import/ +from .notification import Notification as Notification +from .connection_confirmed_notification import ConnectionConfirmedNotification as ConnectionConfirmedNotification +from .connection_request_notification import ConnectionRequestNotification as ConnectionRequestNotification +from .follow_notification import FollowNotification as FollowNotification +from .post_comment_notification import PostCommentNotification as PostCommentNotification +from .post_comment_reply_notification import PostCommentReplyNotification as PostCommentReplyNotification +from .post_reaction_notification import PostReactionNotification as PostReactionNotification +from .community_invite_notification import CommunityInviteNotification as CommunityInviteNotification +from .post_comment_reaction_notification import PostCommentReactionNotification as PostCommentReactionNotification +from .post_comment_user_mention_notification import ( + PostCommentUserMentionNotification as PostCommentUserMentionNotification, +) +from .post_user_mention_notification import PostUserMentionNotification as PostUserMentionNotification +from .community_new_post_notification import CommunityNewPostNotification as CommunityNewPostNotification +from .user_new_post_notification import UserNewPostNotification as UserNewPostNotification +from .follow_request_approved_notification import ( + FollowRequestApprovedNotification as FollowRequestApprovedNotification, +) +from .follow_request_notification import FollowRequestNotification as FollowRequestNotification +from .community_membership_approved_notification import ( + CommunityMembershipApprovedNotification as CommunityMembershipApprovedNotification, +) +from .community_membership_request_notification import ( + CommunityMembershipRequestNotification as CommunityMembershipRequestNotification, +) +from .community_membership_revoked_notification import ( + CommunityMembershipRevokedNotification as CommunityMembershipRevokedNotification, +) +from .community_admin_privileges_granted_notification import ( + CommunityAdminPrivilegesGrantedNotification as CommunityAdminPrivilegesGrantedNotification, +) +from .community_admin_privileges_revoked_notification import ( + CommunityAdminPrivilegesRevokedNotification as CommunityAdminPrivilegesRevokedNotification, +) +from .community_invite_declined_notification import ( + CommunityInviteDeclinedNotification as CommunityInviteDeclinedNotification, +) diff --git a/openbook_notifications/models/connection_request_notification.py b/openbook_notifications/models/connection_request_notification.py index d6f5fc57b30225a08fa4c51bbbd43c7eeb57d95e..3ad8e8648264b76837f2642eea9cb05178224f82 100644 --- a/openbook_notifications/models/connection_request_notification.py +++ b/openbook_notifications/models/connection_request_notification.py @@ -1,8 +1,6 @@ from django.contrib.contenttypes.fields import GenericRelation from django.db import models from django.db.models import Q -from django.db.models.signals import pre_delete -from django.dispatch import receiver from openbook_auth.models import User from openbook_common.models import ModelWithUUID diff --git a/openbook_notifications/notifications.py b/openbook_notifications/notifications.py index dc0d11dc89356a72ced9f34a044c0aab99eaeae2..da962635e36150bcfb17ecf1ddd6ac1325076708 100644 --- a/openbook_notifications/notifications.py +++ b/openbook_notifications/notifications.py @@ -29,7 +29,6 @@ from openbook_notifications.payloads import ( PostReactionPayload, CommentReactionPayload, ) -from openbook_notifications.toggles import notifications_toggle class Workflow(Enum): @@ -81,7 +80,6 @@ class Notification: "apns": {"payload": payload}, } - @notifications_toggle def send(self): if not self.recipients: return diff --git a/openbook_notifications/tests/test_admin.py b/openbook_notifications/tests/test_admin.py index cda468a770b4f177c3abce7085580f7f81f8ea99..106078da55cfcd4207cc935b9f2da92193103ef1 100644 --- a/openbook_notifications/tests/test_admin.py +++ b/openbook_notifications/tests/test_admin.py @@ -16,7 +16,7 @@ class MyAdminTestCase(TestAssertionsMixin, TransactionTestCase): def setUp(self) -> None: self.username = "superuser" - self.password = "123" + self.password = "123" # noqa: S105 self.superuser = User.objects.create_superuser( username=self.username, email="foo@bar.de", password=self.password, is_staff=True ) @@ -61,6 +61,6 @@ class MyAdminTestCase(TestAssertionsMixin, TransactionTestCase): for user in users: self.assertEqual(len(user.notifications.all()), 1) - self.assertNotification( + self.assert_notification( user.notifications.get(), type=Notification.RELEASE_INFO, post=unsent_notification.post ) diff --git a/openbook_notifications/tests/test_api.py b/openbook_notifications/tests/test_api.py index 4e0b4ac09e4d57f77412d0d95c6711d40e94c176..61eabbfa199c6bba8c6d9982118614870f82bd58 100644 --- a/openbook_notifications/tests/test_api.py +++ b/openbook_notifications/tests/test_api.py @@ -13,24 +13,20 @@ from openbook_common.tests.helpers import make_user from openbook_notifications.api import NotificationApi from openbook_notifications.enums import NotificationProvider -from openbook_common.enums import FeatureToggleName, FeatureToggleType -from openbook_common.models import FeatureToggle as FeatureToggleModel - @pytest.mark.django_db class NotificationApiTestCase(TestCase): def setUp(self): self.user = make_user(name="John", last_name="Doe") self.locale = "en" - self.notifications_toggle = FeatureToggleModel.objects.create( - name=FeatureToggleName.NOTIFICATIONS.value, status=FeatureToggleType.PRODUCTION - ) def tearDown(self): self.user.delete() + @patch.object(NotificationApi, "enabled") @patch.object(SubscriberApi, "create") - def test_create_subscriber(self, mock_subscriber_api_create): + def test_create_subscriber(self, mock_subscriber_api_create, mock_enabled): + mock_enabled.return_value = True subscriber_data = { "first_name": self.user.profile.name, "last_name": self.user.profile.last_name, @@ -45,26 +41,32 @@ class NotificationApiTestCase(TestCase): self.assertEqual(subscriber, subscriber_data) + @patch.object(NotificationApi, "enabled") @patch.object(SubscriberApi, "delete") - def test_delete_subscriber(self, mock_subscriber_api_delete): + def test_delete_subscriber(self, mock_subscriber_api_delete, mock_enabled): + mock_enabled.return_value = True subscriber_id = str(self.user.username) NotificationApi.delete_subscriber(subscriber_id) mock_subscriber_api_delete.assert_called_once_with(subscriber_id=subscriber_id) + @patch.object(NotificationApi, "enabled") @patch.object( SubscriberApi, "delete", side_effect=HTTPError(response=type("Response", (object,), {"status_code": 404})) ) - def test_delete_subscriber_if_already_deleted(self, mock_subscriber_api_delete): + def test_delete_subscriber_if_already_deleted(self, mock_subscriber_api_delete, mock_enabled): + mock_enabled.return_value = True subscriber_id = str(self.user.username) NotificationApi.delete_subscriber(subscriber_id) mock_subscriber_api_delete.assert_called_once_with(subscriber_id=subscriber_id) + @patch.object(NotificationApi, "enabled") @patch.object(SubscriberApi, "get") - def test_find_subscriber(self, mock_subscriber_api_get): + def test_find_subscriber(self, mock_subscriber_api_get, mock_enabled): + mock_enabled.return_value = True subscriber_data = { "first_name": self.user.profile.name, "last_name": self.user.profile.last_name, @@ -79,9 +81,13 @@ class NotificationApiTestCase(TestCase): self.assertEqual(subscriber, subscriber_data) + @patch.object(NotificationApi, "enabled") @patch.object(SubscriberApi, "get") @patch.object(SubscriberApi, "create") - def test_find_or_create_subscriber_existing(self, mock_subscriber_api_create, mock_subscriber_api_get): + def test_find_or_create_subscriber_existing( + self, mock_subscriber_api_create, mock_subscriber_api_get, mock_enabled + ): + mock_enabled.return_value = True subscriber_data = { "first_name": self.user.profile.name, "last_name": self.user.profile.last_name, @@ -97,11 +103,15 @@ class NotificationApiTestCase(TestCase): self.assertEqual(subscriber, subscriber_data) mock_subscriber_api_create.assert_not_called() + @patch.object(NotificationApi, "enabled") @patch.object( SubscriberApi, "get", side_effect=HTTPError(response=type("Response", (object,), {"status_code": 404})) ) @patch.object(SubscriberApi, "create") - def test_find_or_create_subscriber_not_existing(self, mock_subscriber_api_create, mock_subscriber_api_get): + def test_find_or_create_subscriber_not_existing( + self, mock_subscriber_api_create, mock_subscriber_api_get, mock_enabled + ): + mock_enabled.return_value = True subscriber_data = { "first_name": self.user.profile.name, "last_name": self.user.profile.last_name, @@ -119,8 +129,10 @@ class NotificationApiTestCase(TestCase): mock_subscriber_api_get.assert_called_once() mock_subscriber_api_create.assert_called_once() + @patch.object(NotificationApi, "enabled") @patch.object(SubscriberApi, "put") - def test_update_subscriber(self, mock_subscriber_api_put): + def test_update_subscriber(self, mock_subscriber_api_put, mock_enabled): + mock_enabled.return_value = True subscriber_data = { "subscriber_id": str(self.user.username), "first_name": self.user.profile.name, @@ -135,9 +147,13 @@ class NotificationApiTestCase(TestCase): self.assertEqual(subscriber, subscriber_data) + @patch.object(NotificationApi, "enabled") @patch.object(SubscriberApi, "put") @patch.object(SubscriberApi, "create") - def test_create_or_update_subscriber_existing(self, mock_subscriber_api_create, mock_subscriber_api_put): + def test_create_or_update_subscriber_existing( + self, mock_subscriber_api_create, mock_subscriber_api_put, mock_enabled + ): + mock_enabled.return_value = True subscriber_data = { "first_name": self.user.profile.name, "last_name": self.user.profile.last_name, @@ -153,11 +169,15 @@ class NotificationApiTestCase(TestCase): self.assertEqual(subscriber, subscriber_data) mock_subscriber_api_create.assert_not_called() + @patch.object(NotificationApi, "enabled") @patch.object( SubscriberApi, "put", side_effect=HTTPError(response=type("Response", (object,), {"status_code": 404})) ) @patch.object(SubscriberApi, "create") - def test_create_or_update_subscriber_not_existing(self, mock_subscriber_api_create, mock_subscriber_api_put): + def test_create_or_update_subscriber_not_existing( + self, mock_subscriber_api_create, mock_subscriber_api_put, mock_enabled + ): + mock_enabled.return_value = True subscriber_data = { "first_name": self.user.profile.name, "last_name": self.user.profile.last_name, @@ -175,13 +195,15 @@ class NotificationApiTestCase(TestCase): mock_subscriber_api_put.assert_called_once() mock_subscriber_api_create.assert_called_once() + @patch.object(NotificationApi, "enabled") @patch.object( SubscriberApi, "put", side_effect=HTTPError(response=type("Response", (object,), {"status_code": 400})) ) @patch.object(SubscriberApi, "create") def test_create_or_update_subscriber_not_existing_code_400( - self, mock_subscriber_api_create, mock_subscriber_api_put + self, mock_subscriber_api_create, mock_subscriber_api_put, mock_enabled ): + mock_enabled.return_value = True subscriber_data = { "first_name": self.user.profile.name, "last_name": self.user.profile.last_name, @@ -199,9 +221,13 @@ class NotificationApiTestCase(TestCase): mock_subscriber_api_put.assert_called_once() mock_subscriber_api_create.assert_called_once() + @patch.object(NotificationApi, "enabled") @patch.object(SubscriberApi, "credentials") @patch.object(SubscriberApi, "delete_credentials") - def test_set_subscriber_credentials(self, mock_subscriber_api_delete_credentials, mock_subscriber_api_credentials): + def test_set_subscriber_credentials( + self, mock_subscriber_api_delete_credentials, mock_subscriber_api_credentials, mock_enabled + ): + mock_enabled.return_value = True user_id = str(self.user.username) provider_id = NotificationProvider.APNS webhook_url = "https://example.com/webhook" @@ -225,17 +251,21 @@ class NotificationApiTestCase(TestCase): user_id, provider_id.value.value, webhook_url, device_tokens ) + @patch.object(NotificationApi, "enabled") @patch.object(EventApi, "delete") - def test_cancel_workflow(self, mock_event_api_delete): + def test_cancel_workflow(self, mock_event_api_delete, mock_enabled): + mock_enabled.return_value = True mock_event_api_delete.return_value = None NotificationApi.cancel_workflow(transaction_id="transaction-id") mock_event_api_delete.assert_called_once_with(transaction_id="transaction-id") + @patch.object(NotificationApi, "enabled") @patch.object(MessageApi, "list") @patch.object(MessageApi, "delete") - def test_delete_in_app_messages(self, mock_message_api_delete, mock_message_api_list): + def test_delete_in_app_messages(self, mock_message_api_delete, mock_message_api_list, mock_enabled): + mock_enabled.return_value = True mock_message_api_list.return_value = PaginatedMessageDto( data=[MessageDto(_id=1), MessageDto(_id=2)], total_count=2, @@ -247,9 +277,13 @@ class NotificationApiTestCase(TestCase): ) mock_message_api_delete.assert_has_calls([call(message_id=1), call(message_id=2)]) + @patch.object(NotificationApi, "enabled") @patch.object(MessageApi, "list") @patch.object(MessageApi, "delete") - def test_delete_multiple_pages_of_in_app_messages(self, mock_message_api_delete, mock_message_api_list): + def test_delete_multiple_pages_of_in_app_messages( + self, mock_message_api_delete, mock_message_api_list, mock_enabled + ): + mock_enabled.return_value = True mock_message_api_list.side_effect = [ PaginatedMessageDto( data=[MessageDto(_id=1), MessageDto(_id=2)], @@ -267,12 +301,14 @@ class NotificationApiTestCase(TestCase): NotificationApi.delete_in_app_messages(transaction_id="transaction-id") # THEN - mock_message_api_list.call_count == 2 + assert mock_message_api_list.call_count == 2 mock_message_api_delete.assert_has_calls([call(message_id=1), call(message_id=2), call(message_id=3)]) + @patch.object(NotificationApi, "enabled") @patch.object(MessageApi, "list") @patch.object(MessageApi, "delete") - def test_delete_in_app_messages_for_subscriber(self, mock_message_api_delete, mock_message_api_list): + def test_delete_in_app_messages_for_subscriber(self, mock_message_api_delete, mock_message_api_list, mock_enabled): + mock_enabled.return_value = True subscriber_id = str(self.user.username) mock_message_api_list.return_value = PaginatedMessageDto( data=[MessageDto(_id=1), MessageDto(_id=2)], @@ -285,11 +321,15 @@ class NotificationApiTestCase(TestCase): ) mock_message_api_delete.assert_has_calls([call(message_id=1), call(message_id=2)]) + @patch.object(NotificationApi, "enabled") @patch.object(MessageApi, "list") @patch.object( MessageApi, "delete", side_effect=HTTPError(response=type("Response", (object,), {"status_code": 404})) ) - def test_delete_in_app_messages_if_already_deleted(self, mock_message_api_delete, mock_message_api_list): + def test_delete_in_app_messages_if_already_deleted( + self, mock_message_api_delete, mock_message_api_list, mock_enabled + ): + mock_enabled.return_value = True mock_message_api_list.return_value = PaginatedMessageDto( data=[MessageDto(_id=1)], total_count=1, diff --git a/openbook_notifications/tests/test_notifications.py b/openbook_notifications/tests/test_notifications.py index 5e16c2159f94bb34449d1d1c7f5dc7b7374fd66a..48bccce781cc557f5f658032910878b8d9945af5 100644 --- a/openbook_notifications/tests/test_notifications.py +++ b/openbook_notifications/tests/test_notifications.py @@ -7,8 +7,6 @@ import sentry_sdk from django.test import TestCase from novu.api import EventApi -from openbook_common.enums import FeatureToggleName, FeatureToggleType -from openbook_common.models import FeatureToggle as FeatureToggleModel from openbook_common.tests.helpers import ( make_community, make_community_post, @@ -65,10 +63,6 @@ class NotificationTestCase(TestCase): payload={"data": {"key": "value"}}, transaction_id="test-transaction-id", ) - cls.notifications_toggle = FeatureToggleModel.objects.create( - name=FeatureToggleName.NOTIFICATIONS.value, status=FeatureToggleType.PRODUCTION - ) - cls.overrides = { "type": "data", "fcm": {"type": "data", "data": {"key": "value"}}, diff --git a/openbook_notifications/tests/test_toggles.py b/openbook_notifications/tests/test_toggles.py deleted file mode 100644 index bf6ca44b78a65ee169e66942e1efdbac0b7141d1..0000000000000000000000000000000000000000 --- a/openbook_notifications/tests/test_toggles.py +++ /dev/null @@ -1,76 +0,0 @@ -import pytest - -from django.test import TestCase -from openbook_common.enums import FeatureToggleName, FeatureToggleType -from openbook_common.models import FeatureToggle as FeatureToggleModel -from openbook_notifications.toggles import notifications_toggle, mute_notifications - - -@pytest.mark.django_db -class NotificationsToggleTestCase(TestCase): - def setUp(self): - self.notifications_toggle = FeatureToggleModel.objects.create( - name=FeatureToggleName.NOTIFICATIONS.value, status=FeatureToggleType.PRODUCTION - ) - - def test_notifications_toggle_enabled(self): - @notifications_toggle - def test_send_notification(): - return True - - result = test_send_notification() - self.assertTrue(result) - - def test_notifications_toggle_disabled(self): - self.notifications_toggle.status = FeatureToggleType.OFF - self.notifications_toggle.save() - - @notifications_toggle - def test_send_notification(): - return True - - result = test_send_notification() - self.assertIsNone(result) - - def test_notifications_toggle_not_exist(self): - self.notifications_toggle.delete() - - @notifications_toggle - def test_send_notification(): - return True - - result = test_send_notification() - self.assertIsNone(result) - - def test_mute_notifications_with_enabled_toggle(self): - @mute_notifications - def test_send_notification(): - return True - - result = test_send_notification() - self.assertIsNone(result) - - def test_mute_notifications_with_disabled_toggle(self): - self.notifications_toggle.status = FeatureToggleType.OFF - self.notifications_toggle.save() - - @mute_notifications - def test_send_notification(): - return True - - result = test_send_notification() - self.assertTrue(result) - - def test_mute_notifications_without_toggle(self): - self.notifications_toggle.delete() - - @mute_notifications - def test_send_notification(): - return True - - result = test_send_notification() - self.assertTrue(result) - - def tearDown(self): - if self.notifications_toggle.id is not None: - self.notifications_toggle.delete() diff --git a/openbook_notifications/toggles.py b/openbook_notifications/toggles.py index 7d3085fb9184fc2bbdf950f9809883ae1ca030b5..dabb7c11966b9769c5b69c4f08d72961768ed46a 100644 --- a/openbook_notifications/toggles.py +++ b/openbook_notifications/toggles.py @@ -1,34 +1,21 @@ import logging from functools import wraps -from openbook_common.enums import FeatureToggleName, FeatureToggleType -from openbook_common.models import FeatureToggle as FeatureToggleModel +from openbook.settings import TESTING, IS_BUILD logger = logging.getLogger(__name__) -def notifications_toggle(func): - @wraps(func) - def wrapper(*args, **kwargs): - try: - notifications_feature_toggle = FeatureToggleModel.objects.get(name=FeatureToggleName.NOTIFICATIONS.value) - if notifications_feature_toggle.status == FeatureToggleType.PRODUCTION: - return func(*args, **kwargs) - except FeatureToggleModel.DoesNotExist: - logger.warning(f"Feature toggle {FeatureToggleName.NOTIFICATIONS.value} does not exist") - - return wrapper - - +# Functions annotated with mute_notifications will never be executed in production. This decorator was introduced +# to be used in the context of migrating individual notification types from the Okuna-based Notification system +# to Novu. Once the old system is removed, this decorator and code using it can also be removed. def mute_notifications(func): @wraps(func) def wrapper(*args, **kwargs): - try: - notifications_feature_toggle = FeatureToggleModel.objects.get(name=FeatureToggleName.NOTIFICATIONS.value) - if notifications_feature_toggle.status == FeatureToggleType.PRODUCTION: - return - except FeatureToggleModel.DoesNotExist: - logger.warning(f"Feature toggle {FeatureToggleName.NOTIFICATIONS.value} does not exist") - return func(*args, **kwargs) + # As long as the old Okuna-based subsystem is not removed, some tests still rely on the code being executed. + if TESTING or IS_BUILD: + return func(*args, **kwargs) + else: + return return wrapper diff --git a/openbook_posts/checkers.py b/openbook_posts/checkers.py index 262085d22f2016e994a75c42d6e1c0c555362566..ee71561a6a58845d060e491a128a74f8d51889cf 100644 --- a/openbook_posts/checkers.py +++ b/openbook_posts/checkers.py @@ -46,7 +46,7 @@ def check_can_be_published(post): def check_mimetype_is_supported_media_mimetypes(mimetype): - if not mimetype in settings.SUPPORTED_MEDIA_MIMETYPES: + if mimetype not in settings.SUPPORTED_MEDIA_MIMETYPES: raise ValidationError( _("%s is not a supported mimetype") % mimetype, ) diff --git a/openbook_posts/queries.py b/openbook_posts/queries.py index e6efd83030025bdb2479473773dbc65d7ad8f220..d81b9f57e09bd17e008e8cae6d8d2da7af6fb688 100644 --- a/openbook_posts/queries.py +++ b/openbook_posts/queries.py @@ -1,7 +1,6 @@ from django.db.models import Q from openbook_common.utils.model_loaders import ( - get_circle_model, get_community_model, get_moderated_object_model, get_post_model, diff --git a/openbook_posts/serializers/post_media_serializers.py b/openbook_posts/serializers/post_media_serializers.py index 12bb38843c09b42a37a12839a3f458dac26d4ff4..910272d2e1483d246ffb64c6ec9469ed66dc6f91 100644 --- a/openbook_posts/serializers/post_media_serializers.py +++ b/openbook_posts/serializers/post_media_serializers.py @@ -3,7 +3,7 @@ from generic_relations.relations import GenericRelatedField from rest_framework import serializers from openbook_common.serializers_fields.request import RestrictedFileSizeField -from openbook_posts.models import PostMedia, PostImage, PostVideo +from openbook_posts.models import PostMedia, PostImage from openbook_posts.validators import post_id_exists diff --git a/openbook_posts/serializers/post_reaction_serializers.py b/openbook_posts/serializers/post_reaction_serializers.py index b1082a21207e0d4779d0fb9169a521113bb93139..852201912891f9460f628ca15f2b91f29a0adbe4 100644 --- a/openbook_posts/serializers/post_reaction_serializers.py +++ b/openbook_posts/serializers/post_reaction_serializers.py @@ -1,7 +1,6 @@ from rest_framework import serializers from openbook_auth.models import UserProfile, User -from openbook_common.models import Emoji from openbook_posts.models import PostReaction from openbook_posts.validators import post_id_exists, post_reaction_id_exists from openbook_posts.serializers.post_comment_reaction_serializers import ( diff --git a/openbook_posts/tests/test_graphql.py b/openbook_posts/tests/test_graphql.py index 759654428895e703d861cae30ea7b5e67c5d3ac6..d5ce07724e1f1de2567c6530b40678183d97456c 100644 --- a/openbook_posts/tests/test_graphql.py +++ b/openbook_posts/tests/test_graphql.py @@ -9,8 +9,7 @@ from django.test import AsyncClient from novu.api import EventApi from comments.models import Comment -from openbook_common.enums import FeatureToggleName, FeatureToggleType, VisibilityType -from openbook_common.models import FeatureToggle as FeatureToggleModel +from openbook_common.enums import VisibilityType from openbook_common.tests.helpers import ( make_community, make_fake_post_text, @@ -60,14 +59,6 @@ def django_db_setup(django_db_setup, django_db_blocker): @pytest.mark.usefixtures("mock_novu_api") @pytest.mark.django_db class TestPosts(TestCase): - @pytest.fixture(autouse=True) - def setup(self): - notifications_toggle = FeatureToggleModel.objects.create( - name=FeatureToggleName.NOTIFICATIONS.value, status=FeatureToggleType.PRODUCTION - ) - yield - notifications_toggle.delete() - def test_can_fetch_post(self): posting_user = make_user() post_text = make_fake_post_text() diff --git a/openbook_tags/__init__.py b/openbook_tags/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/openbook_tags/apps.py b/openbook_tags/apps.py deleted file mode 100644 index 048f5c6478e86231589bba18cc6600490fa9fb0e..0000000000000000000000000000000000000000 --- a/openbook_tags/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class OpenbookTagsConfig(AppConfig): - name = "openbook_tags" diff --git a/openbook_tags/migrations/__init__.py b/openbook_tags/migrations/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/openbook_tags/validators.py b/openbook_tags/validators.py deleted file mode 100644 index b9677956c27199bebb5512e13934fe0c45b8f286..0000000000000000000000000000000000000000 --- a/openbook_tags/validators.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.utils.translation import gettext_lazy as _ - -# Create your tests here. -from rest_framework.exceptions import ValidationError - -from openbook_common.utils.model_loaders import get_hashtag_model diff --git a/openbook_translation/__init__.py b/openbook_translation/__init__.py deleted file mode 100644 index 58220fb7dbbe4f28387ea9af6d841fce765a85c7..0000000000000000000000000000000000000000 --- a/openbook_translation/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -Translation framework. - -The app defines a base strategy abstract class for translation of text -that has a simple API. - -This can be extended depending on the translation framework one wants to use -and configured accordingly in the settings.py - -""" - -from django.conf import settings -from django.utils.module_loading import import_string -from openbook_translation.strategies.base import InvalidTranslationStrategyError - -DEFAULT_STRATEGY_ALIAS = "default" - - -class TranslationStrategyManager: - def __init__(self, config_name=DEFAULT_STRATEGY_ALIAS): - if config_name not in settings.OS_TRANSLATION_CONFIG: - raise InvalidTranslationStrategyError( - f"Could not find config for '{config_name}' in settings.OS_TRANSLATION_CONFIG" - ) - self.strategy_instance = self._create_translation_strategy(config_name) - - def _create_translation_strategy(self, name, **kwargs): - try: - # Try to get the OS_TRANSLATION_CONFIG entry for the given name first - conf = settings.OS_TRANSLATION_CONFIG[name] - params = {**conf, **kwargs} - strategy = params.pop("STRATEGY") - strategy_cls = import_string(strategy) - except ImportError as e: - raise InvalidTranslationStrategyError(f"Could not find strategy '{strategy}': {e}") - - return strategy_cls(params) - - def get_instance(self): - return self.strategy_instance - - -translation_strategy = TranslationStrategyManager(settings.OS_TRANSLATION_STRATEGY_NAME).get_instance() diff --git a/openbook_translation/apps.py b/openbook_translation/apps.py deleted file mode 100644 index 3914e5718575adfd04ea589cd4058ecda780209b..0000000000000000000000000000000000000000 --- a/openbook_translation/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class OpenbookTranslationConfig(AppConfig): - name = "openbook_translation" diff --git a/openbook_translation/management/commands/assign_language.py b/openbook_translation/management/commands/assign_language.py deleted file mode 100644 index 097766b9a3bb4f4c5c77e4724490743b2691da80..0000000000000000000000000000000000000000 --- a/openbook_translation/management/commands/assign_language.py +++ /dev/null @@ -1,53 +0,0 @@ -from django.core.management.base import BaseCommand -from langdetect.lang_detect_exception import LangDetectException - -from openbook_common.helpers import get_language_for_text -from openbook_common.utils.model_loaders import get_comment_model, get_post_model - - -class Command(BaseCommand): - help = "Assigns Language to Post and Comment models, usage python manage.py assign_language --type posts|comments" - - def add_arguments(self, parser): - parser.add_argument( - "--type", - type=str, - help="Type of model to assign lang to, valid values: posts, comments", - ) - - def handle(self, *args, **options): - if options["type"] == "posts": - self.assign_language_posts() - - if options["type"] == "comments": - self.assign_language_comments() - - def assign_language_posts(self): - Post = get_post_model() - posts = Post.objects.filter(text__isnull=False) - for post in posts: - try: - language = get_language_for_text(post.text) - except LangDetectException: - print("Caught exception while detecting language, skipping") # noqa: T201 - - if language: - post.language = language - post.save() - else: - print("Could not detect language for id", post.id) # noqa: T201 - - def assign_language_comments(self): - Comment = get_comment_model() - post_comments = Comment.objects.filter(text__isnull=False) - for post_comment in post_comments: - try: - language = get_language_for_text(post_comment.text) - except LangDetectException: - print("Caught exception while detecting language, skipping") # noqa: T201 - - if language: - post_comment.language = language - post_comment.save() - else: - print("Could not detect language for id", post_comment.id) # noqa: T201 diff --git a/openbook_translation/migrations/__init__.py b/openbook_translation/migrations/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/openbook_translation/serializers.py b/openbook_translation/serializers.py deleted file mode 100644 index edfc11209c06d6834293c41f0a7ea7f5ac7fc36c..0000000000000000000000000000000000000000 --- a/openbook_translation/serializers.py +++ /dev/null @@ -1,19 +0,0 @@ -from rest_framework import serializers -from openbook_common.validators import language_code_exists -from openbook_translation import translation_strategy - - -class TranslateTextSerializer(serializers.Serializer): - source_language_code = serializers.CharField( - required=True, - max_length=translation_strategy.text_max_length, - validators=[language_code_exists], - allow_blank=False, - ) - target_language_code = serializers.CharField( - required=True, - max_length=translation_strategy.text_max_length, - validators=[language_code_exists], - allow_blank=False, - ) - text = serializers.CharField(required=True) diff --git a/openbook_translation/strategies/__init__.py b/openbook_translation/strategies/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/openbook_translation/strategies/amazon.py b/openbook_translation/strategies/amazon.py deleted file mode 100644 index 0312c3c495670101fd0abf4622c943fbbee348c0..0000000000000000000000000000000000000000 --- a/openbook_translation/strategies/amazon.py +++ /dev/null @@ -1,103 +0,0 @@ -from openbook_translation.strategies.base import ( - BaseTranslationStrategy, - MaxTextLengthExceededError, - TranslationClientError, - UnsupportedLanguagePairException, -) -from django.conf import settings -from langdetect import DetectorFactory, detect -import boto3 -from botocore.exceptions import ClientError - -# seed the language detector -DetectorFactory.seed = 0 - - -class AmazonTranslate(BaseTranslationStrategy): - - client = boto3.client( - service_name="translate", - region_name=settings.AWS_TRANSLATE_REGION, - use_ssl=True, - ) - - supported_languages = ( - "ar", - "zh", - "zh-TW", - "cs", - "da", - "nl", - "fi", - "fr", - "de", - "hi", - "he", - "id", - "it", - "ja", - "ko", - "ms", - "no", - "fa", - "pl", - "pt", - "ru", - "es", - "sv", - "tr", - ) - - def get_detected_language_code(self, text): - # amazons translate API codes as stored in the languages.json are slightly different - # from what langdetect provides for chinese (zh) and chinese traditional (zh-TW) - - detected_language = detect(text) - if detected_language == "zh-cn": - detected_language = "zh" - if detected_language == "zh-tw": - detected_language = "zh-TW" - - return detected_language - - def get_default_translation_language_code(self): - return self.default_translation_language_code - - def get_supported_translation_language_code(self, language_code): - # Returns English as default if no match - parsed_code = language_code - if language_code != "zh-TW": - code_parts = language_code.split("-") - if len(code_parts) == 2: - parsed_code = code_parts[0] - - if parsed_code in self.supported_languages: - return parsed_code - else: - return self.default_translation_language_code - - def translate_text(self, text, source_language_code, target_language_code): - - if len(text) > self.text_max_length: - raise MaxTextLengthExceededError("MaxTextLengthExceededError") - - try: - response = self.client.translate_text( - Text=text, - SourceLanguageCode=source_language_code, - TargetLanguageCode=target_language_code, - ) - except self.client.exceptions.UnsupportedLanguagePairException as e: - print(f"Client error from AWS Translate : \n {e}") - raise UnsupportedLanguagePairException - except ClientError as e: - print(f"Client error from AWS Translate : \n {e}") - raise TranslationClientError - - result = { - "translated_text": response.get("TranslatedText"), - "source_language_code": response.get("SourceLanguageCode"), - "target_language_code": response.get("TargetLanguageCode"), - } - - return result diff --git a/openbook_translation/strategies/base.py b/openbook_translation/strategies/base.py deleted file mode 100644 index 7a97107303e01371a2e5559246621adbe84a25c8..0000000000000000000000000000000000000000 --- a/openbook_translation/strategies/base.py +++ /dev/null @@ -1,39 +0,0 @@ -from abc import ABC, abstractmethod - -from django.core.exceptions import ImproperlyConfigured, ValidationError - - -class InvalidTranslationStrategyError(ImproperlyConfigured): - pass - - -class MaxTextLengthExceededError(ValidationError): - pass - - -class TranslationClientError(Exception): - pass - - -class UnsupportedLanguagePairException(Exception): - pass - - -class BaseTranslationStrategy(ABC): - def __init__(self, params): - if "TEXT_MAX_LENGTH" in params: - self.text_max_length = int(params.pop("TEXT_MAX_LENGTH")) - self.default_translation_language_code = params.pop("DEFAULT_TRANSLATION_LANGUAGE_CODE") - super().__init__() - - @abstractmethod - def get_detected_language_code(self, text): - pass - - @abstractmethod - def get_supported_translation_language_code(self, language_code): - pass - - @abstractmethod - def translate_text(self, *args, **kwargs): - pass diff --git a/openbook_translation/strategies/tests.py b/openbook_translation/strategies/tests.py deleted file mode 100644 index 10f7d33e61fd044d48f4a4ce6aabea5ff609d6a2..0000000000000000000000000000000000000000 --- a/openbook_translation/strategies/tests.py +++ /dev/null @@ -1,32 +0,0 @@ -from openbook_translation.strategies.base import ( - BaseTranslationStrategy, - UnsupportedLanguagePairException, - MaxTextLengthExceededError, -) - - -class MockAmazonTranslate(BaseTranslationStrategy): - # Both methods are hardcoded to respond to the tests - - def get_detected_language_code(self, text): - if text == "Ik ben en man 😀. Jij bent en vrouw.": - return "nl" - else: - return "no" - - def get_default_translation_language_code(self): - return "en" - - def get_supported_translation_language_code(self, language_code): - return "en" - - def translate_text(self, text, source_language_code, target_language_code): - - if len(text) > self.text_max_length: - raise MaxTextLengthExceededError("MaxTextLengthExceededError") - - if target_language_code == "en" and source_language_code == "nl": - return {"translated_text": "I am a man 😀. You're a woman."} - - if target_language_code == "ar" and source_language_code == "no": - raise UnsupportedLanguagePairException diff --git a/requirements.txt b/requirements.txt index 16daa122322c4ab53baf4b50a82d5a432f070bfb..6dbc7b366d1204e7aab9857e51491dfa9e2c7bed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,6 @@ # Afterwards, some packages might need a downgrade or version tweak for the bundle fitting together and fitting the code adrf~=0.1.7 aiofiles~=24.1.0 -aiohappyeyeballs~=2.3.4 aiohttp~=3.10.1 aiosignal~=1.3.1 annotated-types~=0.7.0 @@ -104,7 +103,7 @@ pillow~=10.4.0 pinocchio~=0.4.3 platformdirs~=4.2.0 pluggy~=1.5.0 -posthog==3.5.0 +posthog==3.7.0 proto-plus~=1.24.0 protobuf~=4.25.3 psycopg~=3.2.1 @@ -172,4 +171,4 @@ urllib3~=1.26.19 # imagekit requires <1.27 uvicorn~=0.30.5 watchdog~=4.0.1 yarl~=1.9.4 -zipp~=3.19.2 +zipp~=3.20.2 diff --git a/start_server.py b/start_server.py index 7e0b4f3ec1a863760214d40948238b4e7736d7e5..45257e7d72fbaf8d34dcedb01b621745e18f3b82 100755 --- a/start_server.py +++ b/start_server.py @@ -41,7 +41,6 @@ def main(): "./openbook_devices", "./openbook_follows", "./openbook_hashtags", - "./openbook_importer", "./openbook_insights", "./openbook_invitations", "./openbook_lists", @@ -49,9 +48,7 @@ def main(): "./openbook_notifications", "./openbook_polls", "./openbook_posts", - "./openbook_tags", "./openbook_terms", - "./openbook_translation", "./comments", ], ) diff --git a/utils/make_admin_headers.py b/utils/make_admin_headers.py index 9b5b60ea5c7cb0fe003f649b8a20356fac44f4fc..4b5143de3125c6588e4588728e21a392e67113f0 100644 --- a/utils/make_admin_headers.py +++ b/utils/make_admin_headers.py @@ -4,25 +4,21 @@ from jinja2 import Environment, FileSystemLoader def make_eb_config_block_admin(lb_name, utils_dir): - j2_env = Environment(loader=FileSystemLoader(utils_dir), autoescape=True) return j2_env.get_template("templates/eb_extensions/block_admin.config.yml").render(LB_NAME=lb_name) def make_eb_config_admin(sauth_server_name, utils_dir): - j2_env = Environment(loader=FileSystemLoader(utils_dir), autoescape=True) return j2_env.get_template("templates/eb_extensions/admin.config.yml").render(SAUTH_SERVER_NAME=sauth_server_name) def write_eb_config(dest, contents): - with open(dest, "w") as fd: fd.write(contents) def main(): - parser = argparse.ArgumentParser(description="EB Extension Admin Conf Maker") parser.add_argument("--lb-name", type=str, required=True, help="The value of the loadbalancer.") diff --git a/utils/scripts/ruff-changed.sh b/utils/scripts/ruff-changed.sh deleted file mode 100755 index 09de52d7345de3dace77076a4c31829c4aca98d2..0000000000000000000000000000000000000000 --- a/utils/scripts/ruff-changed.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash - -DEBUG=1 -debug () { - if [ "$DEBUG" = "1" ]; then - echo "pre-commit (debug) - $1" - fi -} - -if [[ -z "${VIRTUAL_ENV}" ]] -then - debug "sourcing venv activation" - . "$(dirname -- "$0")/../../.venv/bin/activate" -fi - -CHANGED_PYTHON_FILES=$(git diff --cached --name-only -z --diff-filter=d -- '*.py') -debug "changed files: ${CHANGED_PYTHON_FILES}" - -# if we don't have any changed python files, exit with a success -[[ -z "${CHANGED_PYTHON_FILES}" ]] || exit 0 - -for file in ${CHANGED_PYTHON_FILES}; do - ruff check --force-exclude $file - ruff format --check --force-exclude $file -done diff --git a/utils/scripts/ruff.sh b/utils/scripts/ruff.sh new file mode 100755 index 0000000000000000000000000000000000000000..84ad0a54093bb44c5120f3e5fc7f97a88000ea09 --- /dev/null +++ b/utils/scripts/ruff.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +DEBUG=1 +debug () { + if [ "$DEBUG" = "1" ]; then + echo "pre-commit (debug) - $1" + fi +} + +if [[ -z "${VIRTUAL_ENV}" ]] +then + debug "sourcing venv activation" + . "$(dirname -- "$0")/../../.venv/bin/activate" +fi + +ruff check +ruff format