From aa234e45a90d384d52bcd1ab9135d912bedf6bf8 Mon Sep 17 00:00:00 2001 From: Gagan Date: Wed, 28 Aug 2024 13:35:10 +0530 Subject: [PATCH] fix(django-upgrade): upgrade django major version (#4136) --- api/Makefile | 6 +- api/audit/__init__.py | 1 - api/core/custom_admin/__init__.py | 1 - api/environments/__init__.py | 1 - api/environments/apps.py | 1 + api/environments/identities/__init__.py | 1 - api/environments/identities/models.py | 4 +- api/environments/identities/views.py | 2 +- api/environments/permissions/__init__.py | 1 - api/features/__init__.py | 1 - api/features/apps.py | 1 + .../feature_external_resources/__init__.py | 3 - api/features/feature_segments/views.py | 6 - api/features/multivariate/__init__.py | 1 - api/features/multivariate/apps.py | 1 + api/features/versioning/__init__.py | 1 - api/features/workflows/core/__init__.py | 1 - .../workflows/core/{app.py => apps.py} | 1 + api/integrations/amplitude/__init__.py | 1 - api/integrations/datadog/__init__.py | 1 - api/integrations/dynatrace/__init__.py | 1 - api/integrations/github/__init__.py | 1 - api/integrations/grafana/__init__.py | 1 - api/integrations/heap/__init__.py | 1 - api/integrations/launch_darkly/apps.py | 1 + api/integrations/launch_darkly/models.py | 6 +- api/integrations/mixpanel/__init__.py | 1 - api/integrations/new_relic/__init__.py | 1 - api/integrations/rudderstack/__init__.py | 1 - api/integrations/segment/__init__.py | 1 - api/integrations/sentry/__init__.py | 1 - api/integrations/webhook/__init__.py | 1 - api/organisations/__init__.py | 1 - api/organisations/invites/__init__.py | 1 - api/organisations/permissions/__init__.py | 1 - api/poetry.lock | 262 ++++++++++++++---- api/pyproject.toml | 18 +- api/segments/__init__.py | 1 - api/segments/apps.py | 1 + api/telemetry/__init__.py | 1 - .../test_unit_environments_views.py | 63 ++++- .../test_unit_feature_segments_views.py | 109 ++++++-- .../unit/features/test_unit_features_views.py | 113 +++++++- .../test_unit_sales_dashboard_views.py | 23 +- .../unit/segments/test_unit_segments_views.py | 84 ++++-- api/users/__init__.py | 1 - api/users/models.py | 3 +- 47 files changed, 563 insertions(+), 172 deletions(-) rename api/features/workflows/core/{app.py => apps.py} (88%) diff --git a/api/Makefile b/api/Makefile index e3090e8c6815..947036d908b9 100644 --- a/api/Makefile +++ b/api/Makefile @@ -107,9 +107,13 @@ generate-ld-client-types: .PHONY: integrate-private-tests integrate-private-tests: $(eval WORKFLOW_REVISION := $(shell grep -A 1 "\[tool.poetry.group.workflows.dependencies\]" pyproject.toml | awk -F '"' '{printf $$4}')) + $(eval TASK_PROCESSOR_REVISION := $(shell grep -A 0 "flagsmith-task-processor" pyproject.toml | awk -F '"' '{printf $$4}')) $(eval AUTH_CONTROLLER_REVISION := $(shell grep -A 1 "\[tool.poetry.group.auth-controller.dependencies\]" pyproject.toml | awk -F '"' '{printf $$4}')) + git clone https://github.com/flagsmith/flagsmith-saml --depth 1 --branch ${SAML_REVISION} && mv ./flagsmith-saml/tests tests/saml_tests git clone https://github.com/flagsmith/flagsmith-rbac --depth 1 --branch ${RBAC_REVISION} && mv ./flagsmith-rbac/tests tests/rbac_tests git clone https://github.com/flagsmith/flagsmith-workflows --depth 1 --branch ${WORKFLOW_REVISION} && mv ./flagsmith-workflows/tests tests/workflow_tests git clone https://github.com/flagsmith/flagsmith-auth-controller --depth 1 --branch ${AUTH_CONTROLLER_REVISION} && mv ./flagsmith-auth-controller/tests tests/auth_controller_tests - rm -rf ./flagsmith-saml ./flagsmith-rbac ./flagsmith-workflows ./flagsmith-auth-controller + + git clone https://github.com/flagsmith/flagsmith-task-processor --depth 1 --branch ${TASK_PROCESSOR_REVISION} && mv ./flagsmith-task-processor/tests tests/task_processor_tests + rm -rf ./flagsmith-saml ./flagsmith-rbac ./flagsmith-workflows ./flagsmith-auth-controller ./flagsmith-task-processor diff --git a/api/audit/__init__.py b/api/audit/__init__.py index 9a6d49f35270..e69de29bb2d1 100644 --- a/api/audit/__init__.py +++ b/api/audit/__init__.py @@ -1 +0,0 @@ -default_app_config = "audit.apps.AuditConfig" diff --git a/api/core/custom_admin/__init__.py b/api/core/custom_admin/__init__.py index 27c09bb6f316..e69de29bb2d1 100644 --- a/api/core/custom_admin/__init__.py +++ b/api/core/custom_admin/__init__.py @@ -1 +0,0 @@ -default_app_config = "core.custom_admin.apps.CustomAdminAppConfig" diff --git a/api/environments/__init__.py b/api/environments/__init__.py index 39086e255972..e69de29bb2d1 100644 --- a/api/environments/__init__.py +++ b/api/environments/__init__.py @@ -1 +0,0 @@ -default_app_config = "environments.apps.EnvironmentsConfig" diff --git a/api/environments/apps.py b/api/environments/apps.py index 4501bd9aae19..69a3cc0630f2 100644 --- a/api/environments/apps.py +++ b/api/environments/apps.py @@ -6,3 +6,4 @@ class EnvironmentsConfig(BaseAppConfig): name = "environments" + default = True diff --git a/api/environments/identities/__init__.py b/api/environments/identities/__init__.py index b928f7e3fa33..e69de29bb2d1 100644 --- a/api/environments/identities/__init__.py +++ b/api/environments/identities/__init__.py @@ -1 +0,0 @@ -default_app_config = "environments.identities.apps.IdentitiesConfig" diff --git a/api/environments/identities/models.py b/api/environments/identities/models.py index 60f36f8a4464..f22e5e2b9452 100644 --- a/api/environments/identities/models.py +++ b/api/environments/identities/models.py @@ -163,7 +163,9 @@ def get_segments( :return: List of matching segments """ matching_segments = [] - traits = self.identity_traits.all() if traits is None else traits + traits = ( + self.identity_traits.all() if (traits is None and self.id) else traits or [] + ) if overrides_only: all_segments = self.environment.get_segments_from_cache() diff --git a/api/environments/identities/views.py b/api/environments/identities/views.py index bbf9e2ce566d..a86d240f73d1 100644 --- a/api/environments/identities/views.py +++ b/api/environments/identities/views.py @@ -310,7 +310,7 @@ def _get_all_feature_states_for_user_response( { "flags": all_feature_states, "identifier": identity.identifier, - "traits": identity.identity_traits.all(), + "traits": identity.identity_traits.all() if identity.id else [], }, context=self.get_serializer_context(), ) diff --git a/api/environments/permissions/__init__.py b/api/environments/permissions/__init__.py index b551abd7d501..e69de29bb2d1 100644 --- a/api/environments/permissions/__init__.py +++ b/api/environments/permissions/__init__.py @@ -1 +0,0 @@ -default_app_config = "environments.permissions.apps.EnvironmentPermissionsConfig" diff --git a/api/features/__init__.py b/api/features/__init__.py index bafad0e98f78..e69de29bb2d1 100644 --- a/api/features/__init__.py +++ b/api/features/__init__.py @@ -1 +0,0 @@ -default_app_config = "features.apps.FeaturesConfig" diff --git a/api/features/apps.py b/api/features/apps.py index 4246bf90884b..c6225347dad9 100644 --- a/api/features/apps.py +++ b/api/features/apps.py @@ -6,6 +6,7 @@ class FeaturesConfig(BaseAppConfig): name = "features" + default = True def ready(self): super().ready() diff --git a/api/features/feature_external_resources/__init__.py b/api/features/feature_external_resources/__init__.py index 6c776da5cedf..e69de29bb2d1 100644 --- a/api/features/feature_external_resources/__init__.py +++ b/api/features/feature_external_resources/__init__.py @@ -1,3 +0,0 @@ -default_app_config = ( - "features.feature_external_resources.apps.FeatureExternalResourcesConfig" -) diff --git a/api/features/feature_segments/views.py b/api/features/feature_segments/views.py index 6359a79f605b..691c916fff16 100644 --- a/api/features/feature_segments/views.py +++ b/api/features/feature_segments/views.py @@ -29,12 +29,6 @@ name="list", decorator=swagger_auto_schema(query_serializer=FeatureSegmentQuerySerializer()), ) -@method_decorator( - name="update_priorities", - decorator=swagger_auto_schema( - responses={200: FeatureSegmentListSerializer(many=True)} - ), -) class FeatureSegmentViewSet( viewsets.ModelViewSet, ): diff --git a/api/features/multivariate/__init__.py b/api/features/multivariate/__init__.py index f93b5c5f63cf..e69de29bb2d1 100644 --- a/api/features/multivariate/__init__.py +++ b/api/features/multivariate/__init__.py @@ -1 +0,0 @@ -default_app_config = "features.multivariate.apps.MultivariateConfig" diff --git a/api/features/multivariate/apps.py b/api/features/multivariate/apps.py index bdff9c111786..6ae1123624dc 100644 --- a/api/features/multivariate/apps.py +++ b/api/features/multivariate/apps.py @@ -3,3 +3,4 @@ class MultivariateConfig(BaseAppConfig): name = "features.multivariate" + default = True diff --git a/api/features/versioning/__init__.py b/api/features/versioning/__init__.py index 887d637836f2..e69de29bb2d1 100644 --- a/api/features/versioning/__init__.py +++ b/api/features/versioning/__init__.py @@ -1 +0,0 @@ -default_app_config = "features.versioning.apps.FeatureVersioningAppConfig" diff --git a/api/features/workflows/core/__init__.py b/api/features/workflows/core/__init__.py index 0da98fe29276..e69de29bb2d1 100644 --- a/api/features/workflows/core/__init__.py +++ b/api/features/workflows/core/__init__.py @@ -1 +0,0 @@ -default_app_config = "features.workflows.core.app.WorkflowsCoreAppConfig" diff --git a/api/features/workflows/core/app.py b/api/features/workflows/core/apps.py similarity index 88% rename from api/features/workflows/core/app.py rename to api/features/workflows/core/apps.py index c949143b65f5..29c971e1d3b5 100644 --- a/api/features/workflows/core/app.py +++ b/api/features/workflows/core/apps.py @@ -4,3 +4,4 @@ class WorkflowsCoreAppConfig(BaseAppConfig): name = "features.workflows.core" label = "workflows_core" + default = True diff --git a/api/integrations/amplitude/__init__.py b/api/integrations/amplitude/__init__.py index 4b388ef7e73a..e69de29bb2d1 100644 --- a/api/integrations/amplitude/__init__.py +++ b/api/integrations/amplitude/__init__.py @@ -1 +0,0 @@ -default_app_config = "integrations.amplitude.apps.AmplitudeIntegrationConfig" diff --git a/api/integrations/datadog/__init__.py b/api/integrations/datadog/__init__.py index e07a4e1adf9f..e69de29bb2d1 100644 --- a/api/integrations/datadog/__init__.py +++ b/api/integrations/datadog/__init__.py @@ -1 +0,0 @@ -default_app_config = "integrations.datadog.apps.DataDogConfigurationConfig" diff --git a/api/integrations/dynatrace/__init__.py b/api/integrations/dynatrace/__init__.py index 9ef33269b90a..e69de29bb2d1 100644 --- a/api/integrations/dynatrace/__init__.py +++ b/api/integrations/dynatrace/__init__.py @@ -1 +0,0 @@ -default_app_config = "integrations.dynatrace.apps.DynatraceConfigurationConfig" diff --git a/api/integrations/github/__init__.py b/api/integrations/github/__init__.py index 56dc15f06e4d..e69de29bb2d1 100644 --- a/api/integrations/github/__init__.py +++ b/api/integrations/github/__init__.py @@ -1 +0,0 @@ -default_app_config = "integrations.github.apps.GithubIntegrationConfig" diff --git a/api/integrations/grafana/__init__.py b/api/integrations/grafana/__init__.py index 55afdc499649..e69de29bb2d1 100644 --- a/api/integrations/grafana/__init__.py +++ b/api/integrations/grafana/__init__.py @@ -1 +0,0 @@ -default_app_config = "integrations.grafana.apps.GrafanaConfigurationConfig" diff --git a/api/integrations/heap/__init__.py b/api/integrations/heap/__init__.py index 8319f62525b8..e69de29bb2d1 100644 --- a/api/integrations/heap/__init__.py +++ b/api/integrations/heap/__init__.py @@ -1 +0,0 @@ -default_app_config = "integrations.heap.apps.HeapIntegrationConfig" diff --git a/api/integrations/launch_darkly/apps.py b/api/integrations/launch_darkly/apps.py index 54826396c3fd..afcb9a869887 100644 --- a/api/integrations/launch_darkly/apps.py +++ b/api/integrations/launch_darkly/apps.py @@ -3,3 +3,4 @@ class LaunchDarklyConfigurationConfig(BaseAppConfig): name = "integrations.launch_darkly" + default = True diff --git a/api/integrations/launch_darkly/models.py b/api/integrations/launch_darkly/models.py index b68a5172cb47..338904b1cadd 100644 --- a/api/integrations/launch_darkly/models.py +++ b/api/integrations/launch_darkly/models.py @@ -21,7 +21,9 @@ class LaunchDarklyImportStatus(TypedDict): class LaunchDarklyImportRequest( abstract_base_auditable_model_factory(), ): - history_record_class_path = "features.models.HistoricalLaunchDarklyImportRequest" + history_record_class_path = ( + "integrations.launch_darkly.models.HistoricalLaunchDarklyImportRequest" + ) related_object_type = RelatedObjectType.IMPORT_REQUEST created_by = models.ForeignKey("users.FFAdminUser", on_delete=models.CASCADE) @@ -51,7 +53,7 @@ def get_update_log_message(self, _) -> Optional[str]: ) return "LaunchDarkly import failed" - def get_audit_log_author(self) -> "FFAdminUser": + def get_audit_log_author(self, history_instance) -> "FFAdminUser": return self.created_by def _get_project(self) -> Project: diff --git a/api/integrations/mixpanel/__init__.py b/api/integrations/mixpanel/__init__.py index 3bdde9c888c8..e69de29bb2d1 100644 --- a/api/integrations/mixpanel/__init__.py +++ b/api/integrations/mixpanel/__init__.py @@ -1 +0,0 @@ -default_app_config = "integrations.mixpanel.apps.MixpanelIntegrationConfig" diff --git a/api/integrations/new_relic/__init__.py b/api/integrations/new_relic/__init__.py index fe1c804bc893..e69de29bb2d1 100644 --- a/api/integrations/new_relic/__init__.py +++ b/api/integrations/new_relic/__init__.py @@ -1 +0,0 @@ -default_app_config = "integrations.new_relic.apps.NewRelicConfigurationConfig" diff --git a/api/integrations/rudderstack/__init__.py b/api/integrations/rudderstack/__init__.py index 9067af129b75..e69de29bb2d1 100644 --- a/api/integrations/rudderstack/__init__.py +++ b/api/integrations/rudderstack/__init__.py @@ -1 +0,0 @@ -default_app_config = "integrations.rudderstack.apps.RudderstackIntegrationConfig" diff --git a/api/integrations/segment/__init__.py b/api/integrations/segment/__init__.py index 18e58f828e14..e69de29bb2d1 100644 --- a/api/integrations/segment/__init__.py +++ b/api/integrations/segment/__init__.py @@ -1 +0,0 @@ -default_app_config = "integrations.segment.apps.SegmentIntegrationConfig" diff --git a/api/integrations/sentry/__init__.py b/api/integrations/sentry/__init__.py index 7c1d9e357c9d..e69de29bb2d1 100644 --- a/api/integrations/sentry/__init__.py +++ b/api/integrations/sentry/__init__.py @@ -1 +0,0 @@ -default_app_config = "integrations.sentry.apps.SentryConfig" diff --git a/api/integrations/webhook/__init__.py b/api/integrations/webhook/__init__.py index deb4e5f131d1..e69de29bb2d1 100644 --- a/api/integrations/webhook/__init__.py +++ b/api/integrations/webhook/__init__.py @@ -1 +0,0 @@ -default_app_config = "integrations.webhook.apps.WebhookIntegrationConfig" diff --git a/api/organisations/__init__.py b/api/organisations/__init__.py index 33acf6939e18..e69de29bb2d1 100644 --- a/api/organisations/__init__.py +++ b/api/organisations/__init__.py @@ -1 +0,0 @@ -default_app_config = "organisations.apps.OrganisationsConfig" diff --git a/api/organisations/invites/__init__.py b/api/organisations/invites/__init__.py index 1e4444a84f3a..e69de29bb2d1 100644 --- a/api/organisations/invites/__init__.py +++ b/api/organisations/invites/__init__.py @@ -1 +0,0 @@ -default_app_config = "organisations.invites.apps.InvitesConfig" diff --git a/api/organisations/permissions/__init__.py b/api/organisations/permissions/__init__.py index 93e7b6accff8..e69de29bb2d1 100644 --- a/api/organisations/permissions/__init__.py +++ b/api/organisations/permissions/__init__.py @@ -1 +0,0 @@ -default_app_config = "organisations.permissions.apps.OrganisationPermissionsConfig" diff --git a/api/poetry.lock b/api/poetry.lock index 87f841237dcc..6cda152f64d7 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -38,18 +38,33 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] [[package]] name = "asgiref" -version = "3.5.2" +version = "3.8.1" description = "ASGI specs, helper code, and adapters" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, - {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, + {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, + {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, ] [package.extras] tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] +[[package]] +name = "astroid" +version = "2.15.8" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, + {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, +] + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +wrapt = {version = ">=1.14,<2", markers = "python_version >= \"3.11\""} + [[package]] name = "async-timeout" version = "4.0.3" @@ -79,6 +94,20 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib- tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +[[package]] +name = "autopep8" +version = "2.0.4" +description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" +optional = false +python-versions = ">=3.6" +files = [ + {file = "autopep8-2.0.4-py2.py3-none-any.whl", hash = "sha256:067959ca4a07b24dbd5345efa8325f5f58da4298dab0dde0443d5ed765de80cb"}, + {file = "autopep8-2.0.4.tar.gz", hash = "sha256:2913064abd97b3419d1cc83ea71f042cb821f87e45b9c88cad5ad3c4ea87fe0c"}, +] + +[package.dependencies] +pycodestyle = ">=2.10.0" + [[package]] name = "azure-core" version = "1.28.0" @@ -661,6 +690,21 @@ files = [ [package.dependencies] packaging = "*" +[[package]] +name = "dill" +version = "0.3.8" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, + {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + [[package]] name = "distlib" version = "0.3.7" @@ -685,19 +729,19 @@ files = [ [[package]] name = "django" -version = "3.2.25" -description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +version = "4.2.13" +description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "Django-3.2.25-py3-none-any.whl", hash = "sha256:a52ea7fcf280b16f7b739cec38fa6d3f8953a5456986944c3ca97e79882b4e38"}, - {file = "Django-3.2.25.tar.gz", hash = "sha256:7ca38a78654aee72378594d63e51636c04b8e28574f5505dff630895b5472777"}, + {file = "Django-4.2.13-py3-none-any.whl", hash = "sha256:a17fcba2aad3fc7d46fdb23215095dbbd64e6174bf4589171e732b18b07e426a"}, + {file = "Django-4.2.13.tar.gz", hash = "sha256:837e3cf1f6c31347a1396a3f6b65688f2b4bb4a11c580dcb628b5afe527b68a5"}, ] [package.dependencies] -asgiref = ">=3.3.2,<4" -pytz = "*" -sqlparse = ">=0.2.2" +asgiref = ">=3.6.0,<4" +sqlparse = ">=0.3.1" +tzdata = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] argon2 = ["argon2-cffi (>=19.1.0)"] @@ -705,17 +749,17 @@ bcrypt = ["bcrypt"] [[package]] name = "django-admin-sso" -version = "3.1.0" +version = "5.2.0" description = "Django SSO solution" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "django-admin-sso-3.1.0.tar.gz", hash = "sha256:7357a20b7bb77655b1d7efc82985206c9724d37563697b41d4b737588566ee81"}, - {file = "django_admin_sso-3.1.0-py2.py3-none-any.whl", hash = "sha256:4af7d5ac639e24b293e47ba6309a9fc92bdc94c826d4ac035e782c6ab2a7f693"}, + {file = "django-admin-sso-5.2.0.tar.gz", hash = "sha256:878f1776825c8454c440781153b9a7a755192f97f6a09d66961cd2d1066fd04c"}, + {file = "django_admin_sso-5.2.0-py2.py3-none-any.whl", hash = "sha256:5cab4c9a3746a60c36f384ec7aa9cd4bc6eb73f44de6bcdba7293346d31fcfc8"}, ] [package.dependencies] -Django = ">=1.11" +Django = ">=3.2" oauth2client = ">=1.2" [[package]] @@ -734,20 +778,6 @@ django = ">=3.2" django-ipware = ">=3" setuptools = "*" -[[package]] -name = "django-capture-on-commit-callbacks" -version = "1.11.0" -description = "Capture and make assertions on transaction.on_commit() callbacks." -optional = false -python-versions = ">=3.7" -files = [ - {file = "django-capture-on-commit-callbacks-1.11.0.tar.gz", hash = "sha256:ee5a79dc74937a0318c192b54d904ce0826ced47748d160bf15324fc77e98c41"}, - {file = "django_capture_on_commit_callbacks-1.11.0-py3-none-any.whl", hash = "sha256:a75300586390411a7e4641128c4251fdc5db25b6e76543329d82fb2c2bc71163"}, -] - -[package.dependencies] -Django = ">=3.2" - [[package]] name = "django-cors-headers" version = "3.5.0" @@ -887,19 +917,19 @@ files = [ [[package]] name = "django-python3-ldap" -version = "0.15.5" +version = "0.15.6" description = "Django LDAP user authentication backend for Python 3." optional = false python-versions = "*" files = [ - {file = "django-python3-ldap-0.15.5.tar.gz", hash = "sha256:44cac6638184cdefb2ed2934a6ca20c9c2512d36bc3ddb801d26fcf119cfbc0f"}, - {file = "django_python3_ldap-0.15.5-py3-none-any.whl", hash = "sha256:7f1142a3ca4500fe433d2c5d5aadb9fafa21774a36d37acb4a939c7cff130a0c"}, + {file = "django-python3-ldap-0.15.6.tar.gz", hash = "sha256:780ac3a2d20de17857990dd0a1bdb8f899855a59725c23638fd07f349439f588"}, + {file = "django_python3_ldap-0.15.6-py3-none-any.whl", hash = "sha256:04f59c25d76265e8f112d0563928877c5b08fa3422c4afea34912f02473e7fcb"}, ] [package.dependencies] django = ">=1.11" ldap3 = ">=2.5,<3" -pyasn1 = ">=0.4.6,<0.5" +pyasn1 = ">=0.4.6,<0.6" [[package]] name = "django-redis" @@ -1241,13 +1271,13 @@ tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"] [[package]] name = "execnet" -version = "2.0.2" +version = "2.1.1" description = "execnet: rapid multi-Python deployment" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"}, - {file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"}, + {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, + {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, ] [package.extras] @@ -1358,19 +1388,18 @@ name = "flagsmith-ldap" version = "0.1.0" description = "LDAP plugin for Flagsmith application." optional = false -python-versions = "^3.10" +python-versions = ">=3.10,<4.0" files = [] develop = false [package.dependencies] -django = "~3.2.23" -django-python3-ldap = "^0.15.4" +django-python3-ldap = "^0.15.6" [package.source] type = "git" url = "https://github.com/flagsmith/flagsmith-ldap" -reference = "v0.1.0" -resolved_reference = "e9edc5668711523eabdb833203d677bf64f98531" +reference = "v0.1.1" +resolved_reference = "d7cf0dd9e306a529498839c3f7a1d7c652093228" [[package]] name = "flagsmith-task-processor" @@ -2034,6 +2063,52 @@ files = [ {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, ] +[[package]] +name = "lazy-object-proxy" +version = "1.10.0" +description = "A fast and thorough lazy object proxy." +optional = false +python-versions = ">=3.8" +files = [ + {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-win32.whl", hash = "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-win32.whl", hash = "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd"}, + {file = "lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d"}, +] + [[package]] name = "ldap3" version = "2.9.1" @@ -2127,6 +2202,17 @@ docs = ["alabaster (==0.7.13)", "autodocsumm (==0.2.11)", "sphinx (==7.0.1)", "s lint = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)"] tests = ["pytest", "pytz", "simplejson"] +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + [[package]] name = "monotonic" version = "1.6" @@ -2396,6 +2482,17 @@ wmctrl = "*" funcsigs = ["funcsigs"] testing = ["funcsigs", "pytest"] +[[package]] +name = "pep8" +version = "1.7.1" +description = "Python style guide checker" +optional = false +python-versions = "*" +files = [ + {file = "pep8-1.7.1-py2.py3-none-any.whl", hash = "sha256:b22cfae5db09833bb9bd7c8463b53e1a9c9b39f12e304a8d0bba729c501827ee"}, + {file = "pep8-1.7.1.tar.gz", hash = "sha256:fe249b52e20498e59e0b5c5256aa52ee99fc295b26ec9eaa85776ffdb9fe6374"}, +] + [[package]] name = "platformdirs" version = "3.10.0" @@ -2617,6 +2714,17 @@ files = [ [package.dependencies] pyasn1 = ">=0.4.6,<0.6.0" +[[package]] +name = "pycodestyle" +version = "2.12.0" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.12.0-py2.py3-none-any.whl", hash = "sha256:949a39f6b86c3e1515ba1787c2022131d165a8ad271b11370a8819aa070269e4"}, + {file = "pycodestyle-2.12.0.tar.gz", hash = "sha256:442f950141b4f43df752dd303511ffded3a04c2b6fb7f65980574f0c31e6e79c"}, +] + [[package]] name = "pycparser" version = "2.21" @@ -2808,6 +2916,30 @@ dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pyte docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] +[[package]] +name = "pylint" +version = "2.16.4" +description = "python code static checker" +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "pylint-2.16.4-py3-none-any.whl", hash = "sha256:4a770bb74fde0550fa0ab4248a2ad04e7887462f9f425baa0cd8d3c1d098eaee"}, + {file = "pylint-2.16.4.tar.gz", hash = "sha256:8841f26a0dbc3503631b6a20ee368b3f5e0e5461a1d95cf15d103dab748a0db3"}, +] + +[package.dependencies] +astroid = ">=2.14.2,<=2.16.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""} +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomlkit = ">=0.10.1" + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + [[package]] name = "pymemcache" version = "4.0.0" @@ -2992,13 +3124,13 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "pytest-django" -version = "4.7.0" +version = "4.8.0" description = "A Django plugin for pytest." optional = false python-versions = ">=3.8" files = [ - {file = "pytest-django-4.7.0.tar.gz", hash = "sha256:92d6fd46b1d79b54fb6b060bbb39428073396cec717d5f2e122a990d4b6aa5e8"}, - {file = "pytest_django-4.7.0-py3-none-any.whl", hash = "sha256:4e1c79d5261ade2dd58d91208017cd8f62cb4710b56e012ecd361d15d5d662a2"}, + {file = "pytest-django-4.8.0.tar.gz", hash = "sha256:5d054fe011c56f3b10f978f41a8efb2e5adfc7e680ef36fb571ada1f24779d90"}, + {file = "pytest_django-4.8.0-py3-none-any.whl", hash = "sha256:ca1ddd1e0e4c227cf9e3e40a6afc6d106b3e70868fd2ac5798a22501271cd0c7"}, ] [package.dependencies] @@ -3056,18 +3188,18 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] [[package]] name = "pytest-xdist" -version = "3.2.1" +version = "3.6.1" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-xdist-3.2.1.tar.gz", hash = "sha256:1849bd98d8b242b948e472db7478e090bf3361912a8fed87992ed94085f54727"}, - {file = "pytest_xdist-3.2.1-py3-none-any.whl", hash = "sha256:37290d161638a20b672401deef1cba812d110ac27e35d213f091d15b8beb40c9"}, + {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, + {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, ] [package.dependencies] -execnet = ">=1.1" -pytest = ">=6.2.0" +execnet = ">=2.1" +pytest = ">=7.0.0" [package.extras] psutil = ["psutil (>=3.0)"] @@ -3745,6 +3877,17 @@ files = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +[[package]] +name = "tomlkit" +version = "0.13.0" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomlkit-0.13.0-py3-none-any.whl", hash = "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"}, + {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"}, +] + [[package]] name = "types-toml" version = "0.10.8.7" @@ -3767,6 +3910,17 @@ files = [ {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] +[[package]] +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, +] + [[package]] name = "uritemplate" version = "3.0.1" @@ -4022,4 +4176,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.11, <3.13" -content-hash = "a5f910d13738afb4bd290ddd38c61ef315aa3f9940ebe2e69460ba0dc8806343" +content-hash = "9f9603c295e15089536446582289ea0efb7ba28563dc5f474068286aa1525d33" diff --git a/api/pyproject.toml b/api/pyproject.toml index a7a798a28454..0a01e612cd47 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -108,7 +108,7 @@ readme = "readme.md" [tool.poetry.dependencies] python = ">=3.11, <3.13" -django = "~3.2.25" +django = "~4.2.13" rudder-sdk-python = "~2.0.2" segment-analytics-python = "~2.2.3" backoff = "~2.2.1" @@ -134,7 +134,7 @@ packaging = "~23.0" chargebee = "^2.7.7" python-http-client = "~3.3.7" django-health-check = "~3.18.2" -django-admin-sso = "~3.1.0" +django-admin-sso = "~5.2.0" drf-yasg = "~1.21.6" django-debug-toolbar = "~3.2.1" sentry-sdk = "~2.8.0" @@ -145,7 +145,7 @@ django-filter = "~2.4.0" flagsmith-flag-engine = "^5.1.1" boto3 = "~1.28.78" slack-sdk = "~3.9.0" -asgiref = "~3.5.0" +asgiref = "~3.8.1" opencensus-ext-azure = "~1.1.4" opencensus-ext-django = "~0.7.6" djangorestframework-api-key = "~2.2.0" @@ -171,6 +171,7 @@ djangorestframework-dataclasses = "^1.3.1" pyotp = "^2.9.0" flagsmith-task-processor = { git = "https://github.com/Flagsmith/flagsmith-task-processor", tag = "v1.0.2" } flagsmith-common = { git = "https://github.com/Flagsmith/flagsmith-common", tag = "v1.0.0" } +tzdata = "^2024.1" [tool.poetry.group.auth-controller] optional = true @@ -188,7 +189,7 @@ pysaml2 = "^7.4.2" optional = true [tool.poetry.group.ldap.dependencies] -flagsmith-ldap = { git = "https://github.com/flagsmith/flagsmith-ldap", tag = "v0.1.0" } +flagsmith-ldap = { git = "https://github.com/flagsmith/flagsmith-ldap", tag = "v0.1.1" } [tool.poetry.group.workflows] optional = true @@ -204,15 +205,18 @@ pytest-mock = "~3.10.0" pytest-lazy-fixture = "~0.6.3" moto = "~4.1.3" pytest-freezegun = "~0.4.2" -pytest-xdist = "~3.2.1" +pytest-xdist = "~3.6.1" +pylint = "~2.16.2" +pep8 = "~1.7.1" +autopep8 = "~2.0.1" pytest = "~7.2.1" -pytest-django = "^4.5.2" +pytest-django = "^4.8.0" +black = "~24.3.0" pytest-cov = "~4.1.0" datamodel-code-generator = "~0.25" requests-mock = "^1.11.0" django-extensions = "^3.2.3" pdbpp = "^0.10.3" -django-capture-on-commit-callbacks = "^1.11.0" mypy-boto3-dynamodb = "^1.33.0" [build-system] diff --git a/api/segments/__init__.py b/api/segments/__init__.py index de3cda3369fd..e69de29bb2d1 100644 --- a/api/segments/__init__.py +++ b/api/segments/__init__.py @@ -1 +0,0 @@ -default_app_config = "segments.apps.SegmentsConfig" diff --git a/api/segments/apps.py b/api/segments/apps.py index a016b8eef6f0..4858f6de1926 100644 --- a/api/segments/apps.py +++ b/api/segments/apps.py @@ -3,6 +3,7 @@ class SegmentsConfig(BaseAppConfig): name = "segments" + default = True def ready(self) -> None: super().ready() diff --git a/api/telemetry/__init__.py b/api/telemetry/__init__.py index ae8e5c13d8ab..e69de29bb2d1 100644 --- a/api/telemetry/__init__.py +++ b/api/telemetry/__init__.py @@ -1 +0,0 @@ -default_app_config = "telemetry.apps.TelemetryConfig" diff --git a/api/tests/unit/environments/test_unit_environments_views.py b/api/tests/unit/environments/test_unit_environments_views.py index d5fec31fe8de..c03448bee9b2 100644 --- a/api/tests/unit/environments/test_unit_environments_views.py +++ b/api/tests/unit/environments/test_unit_environments_views.py @@ -3,6 +3,7 @@ import pytest from core.constants import STRING +from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.urls import reverse from flag_engine.segments.constants import EQUAL @@ -552,7 +553,65 @@ def test_create_environment_without_required_metadata_returns_400( assert "Missing required metadata field" in response.json()["metadata"][0] -def test_view_environment_with_staff__query_count_is_expected( +@pytest.mark.skipif( + settings.IS_RBAC_INSTALLED is True, + reason="Skip this test if RBAC is installed", +) +def test_view_environment_with_staff__query_count_is_expected_without_rbac( + staff_client: APIClient, + environment: Environment, + with_environment_permissions: WithEnvironmentPermissionsCallable, + project: Project, + django_assert_num_queries: DjangoAssertNumQueries, + environment_metadata_a: Metadata, + environment_metadata_b: Metadata, + required_a_environment_metadata_field: MetadataModelField, + environment_content_type: ContentType, +) -> None: + _assert_view_environment_with_staff__query_count( + staff_client, + environment, + with_environment_permissions, + project, + django_assert_num_queries, + environment_metadata_a, + environment_metadata_b, + required_a_environment_metadata_field, + environment_content_type, + expected_query_count=9, + ) + + +@pytest.mark.skipif( + settings.IS_RBAC_INSTALLED is False, + reason="Skip this test if RBAC is not installed", +) +def test_view_environment_with_staff__query_count_is_expected_with_rbac( + staff_client: APIClient, + environment: Environment, + with_environment_permissions: WithEnvironmentPermissionsCallable, + project: Project, + django_assert_num_queries: DjangoAssertNumQueries, + environment_metadata_a: Metadata, + environment_metadata_b: Metadata, + required_a_environment_metadata_field: MetadataModelField, + environment_content_type: ContentType, +) -> None: # pragma: no cover + _assert_view_environment_with_staff__query_count( + staff_client, + environment, + with_environment_permissions, + project, + django_assert_num_queries, + environment_metadata_a, + environment_metadata_b, + required_a_environment_metadata_field, + environment_content_type, + expected_query_count=10, + ) + + +def _assert_view_environment_with_staff__query_count( staff_client: APIClient, environment: Environment, with_environment_permissions: WithEnvironmentPermissionsCallable, @@ -562,6 +621,7 @@ def test_view_environment_with_staff__query_count_is_expected( environment_metadata_b: Metadata, required_a_environment_metadata_field: MetadataModelField, environment_content_type: ContentType, + expected_query_count: int, ) -> None: # Given with_environment_permissions([VIEW_ENVIRONMENT]) @@ -569,7 +629,6 @@ def test_view_environment_with_staff__query_count_is_expected( url = reverse("api-v1:environments:environment-list") data = {"project": project.id} - expected_query_count = 9 # When with django_assert_num_queries(expected_query_count): response = staff_client.get(url, data=data, content_type="application/json") diff --git a/api/tests/unit/features/feature_segments/test_unit_feature_segments_views.py b/api/tests/unit/features/feature_segments/test_unit_feature_segments_views.py index b2b55e3d303f..f6a68460015e 100644 --- a/api/tests/unit/features/feature_segments/test_unit_feature_segments_views.py +++ b/api/tests/unit/features/feature_segments/test_unit_feature_segments_views.py @@ -1,7 +1,9 @@ import json import pytest +from django.conf import settings from django.urls import reverse +from pytest_django import DjangoAssertNumQueries from pytest_lazyfixture import lazy_fixture from rest_framework import status from rest_framework.test import APIClient @@ -27,6 +29,10 @@ from users.models import FFAdminUser +@pytest.mark.skipif( + settings.IS_RBAC_INSTALLED is True, + reason="Skip this test if RBAC is installed", +) @pytest.mark.parametrize( "client, num_queries", [ @@ -37,37 +43,70 @@ ( lazy_fixture("admin_master_api_key_client"), 4, - ), # an extra one for master_api_key + ), # one for each for master_api_key ], ) -def test_list_feature_segments( - segment, - feature, - environment, - project, - django_assert_num_queries, - client, - feature_segment, - num_queries, -): +def test_list_feature_segments_without_rbac( + segment: Segment, + feature: Feature, + environment: Environment, + project: Project, + django_assert_num_queries: DjangoAssertNumQueries, + client: APIClient, + feature_segment: FeatureSegment, + num_queries: int, +) -> None: # Given base_url = reverse("api-v1:features:feature-segment-list") url = f"{base_url}?environment={environment.id}&feature={feature.id}" - environment_2 = Environment.objects.create( - project=project, name="Test environment 2" - ) - segment_2 = Segment.objects.create(project=project, name="Segment 2") - segment_3 = Segment.objects.create(project=project, name="Segment 3") + _list_feature_segment_setup_data(project, environment, feature, segment) - FeatureSegment.objects.create( - feature=feature, segment=segment_2, environment=environment - ) - FeatureSegment.objects.create( - feature=feature, segment=segment_3, environment=environment - ) - FeatureSegment.objects.create( - feature=feature, segment=segment, environment=environment_2 - ) + # When + with django_assert_num_queries(num_queries): + response = client.get(url) + + # Then + assert response.status_code == status.HTTP_200_OK + response_json = response.json() + assert response_json["count"] == 3 + for result in response_json["results"]: + assert result["environment"] == environment.id + assert "uuid" in result + assert "segment_name" in result + assert not result["is_feature_specific"] + + +@pytest.mark.skipif( + settings.IS_RBAC_INSTALLED is False, + reason="Skip this test if RBAC is not installed", +) +@pytest.mark.parametrize( + "client, num_queries", + [ + ( + lazy_fixture("admin_client"), + 7, + ), # 1 for paging, 4 for permissions, 1 for result, 1 for getting the current live version + ( + lazy_fixture("admin_master_api_key_client"), + 4, + ), # one for each for master_api_key + ], +) +def test_list_feature_segments_with_rbac( + segment: Segment, + feature: Feature, + environment: Environment, + project: Project, + django_assert_num_queries: DjangoAssertNumQueries, + client: APIClient, + feature_segment: FeatureSegment, + num_queries: int, +) -> None: # pragma: no cover + # Given + base_url = reverse("api-v1:features:feature-segment-list") + url = f"{base_url}?environment={environment.id}&feature={feature.id}" + _list_feature_segment_setup_data(project, environment, feature, segment) # When with django_assert_num_queries(num_queries): @@ -84,6 +123,26 @@ def test_list_feature_segments( assert not result["is_feature_specific"] +def _list_feature_segment_setup_data( + project: Project, environment: Environment, feature: Feature, segment: Segment +) -> None: + environment_2 = Environment.objects.create( + project=project, name="Test environment 2" + ) + segment_2 = Segment.objects.create(project=project, name="Segment 2") + segment_3 = Segment.objects.create(project=project, name="Segment 3") + + FeatureSegment.objects.create( + feature=feature, segment=segment_2, environment=environment + ) + FeatureSegment.objects.create( + feature=feature, segment=segment_3, environment=environment + ) + FeatureSegment.objects.create( + feature=feature, segment=segment, environment=environment_2 + ) + + @pytest.mark.parametrize( "client", [lazy_fixture("admin_client"), lazy_fixture("admin_master_api_key_client")], diff --git a/api/tests/unit/features/test_unit_features_views.py b/api/tests/unit/features/test_unit_features_views.py index 092d9b7c08a1..97c5d701426f 100644 --- a/api/tests/unit/features/test_unit_features_views.py +++ b/api/tests/unit/features/test_unit_features_views.py @@ -7,6 +7,7 @@ import pytz from app_analytics.dataclasses import FeatureEvaluationData from core.constants import FLAGSMITH_UPDATED_AT_HEADER +from django.conf import settings from django.forms import model_to_dict from django.urls import reverse from django.utils import timezone @@ -2675,7 +2676,11 @@ def test_update_segment_override__using_simple_feature_state_viewset__denies_upd assert response.status_code == status.HTTP_403_FORBIDDEN -def test_list_features_n_plus_1( +@pytest.mark.skipif( + settings.IS_RBAC_INSTALLED is True, + reason="Skip this test if RBAC is installed", +) +def test_list_features_n_plus_1_without_rbac( staff_client: APIClient, project: Project, feature: Feature, @@ -2683,7 +2688,49 @@ def test_list_features_n_plus_1( django_assert_num_queries: DjangoAssertNumQueries, environment: Environment, ) -> None: - # Given + _assert_list_feature_n_plus_1( + staff_client, + project, + feature, + with_project_permissions, + django_assert_num_queries, + environment, + num_queries=16, + ) + + +@pytest.mark.skipif( + settings.IS_RBAC_INSTALLED is False, + reason="Skip this test if RBAC is not installed", +) +def test_list_features_n_plus_1_with_rbac( + staff_client: APIClient, + project: Project, + feature: Feature, + with_project_permissions: WithProjectPermissionsCallable, + django_assert_num_queries: DjangoAssertNumQueries, + environment: Environment, +) -> None: # pragma: no cover + _assert_list_feature_n_plus_1( + staff_client, + project, + feature, + with_project_permissions, + django_assert_num_queries, + environment, + num_queries=17, + ) + + +def _assert_list_feature_n_plus_1( + staff_client: APIClient, + project: Project, + feature: Feature, + with_project_permissions: WithProjectPermissionsCallable, + django_assert_num_queries: DjangoAssertNumQueries, + environment: Environment, + num_queries: int, +) -> None: with_project_permissions([VIEW_PROJECT]) base_url = reverse("api-v1:projects:project-features-list", args=[project.id]) @@ -2697,7 +2744,7 @@ def test_list_features_n_plus_1( v1_feature_state.clone(env=environment, version=i, live_from=timezone.now()) # When - with django_assert_num_queries(16): + with django_assert_num_queries(num_queries): response = staff_client.get(url) # Then @@ -2837,7 +2884,6 @@ def test_list_features_with_feature_state( ) -> None: # Given with_project_permissions([VIEW_PROJECT]) - feature2 = Feature.objects.create( name="another_feature", project=project, initial_value="initial_value" ) @@ -2928,8 +2974,7 @@ def test_list_features_with_feature_state( url = f"{base_url}?environment={environment.id}" # When - with django_assert_num_queries(16): - response = staff_client.get(url) + response = staff_client.get(url) # Then assert response.status_code == status.HTTP_200_OK @@ -3178,8 +3223,12 @@ def test_simple_feature_state_returns_only_latest_versions( assert response_json["count"] == 2 +@pytest.mark.skipif( + settings.IS_RBAC_INSTALLED is True, + reason="Skip this test if RBAC is installed", +) @pytest.mark.freeze_time(two_hours_ago) -def test_feature_list_last_modified_values( +def test_feature_list_last_modified_values_without_rbac( staff_client: APIClient, staff_user: FFAdminUser, environment_v2_versioning: Environment, @@ -3188,6 +3237,54 @@ def test_feature_list_last_modified_values( with_project_permissions: WithProjectPermissionsCallable, django_assert_num_queries: DjangoAssertNumQueries, ) -> None: + _assert_feature_list_last_modified_values( + staff_client, + staff_user, + environment_v2_versioning, + project, + feature, + with_project_permissions, + django_assert_num_queries, + num_queries=18, + ) + + +@pytest.mark.skipif( + settings.IS_RBAC_INSTALLED is False, + reason="Skip this test if RBAC is not installed", +) +@pytest.mark.freeze_time(two_hours_ago) +def test_feature_list_last_modified_values_with_rbac( + staff_client: APIClient, + staff_user: FFAdminUser, + environment_v2_versioning: Environment, + project: Project, + feature: Feature, + with_project_permissions: WithProjectPermissionsCallable, + django_assert_num_queries: DjangoAssertNumQueries, +) -> None: # pragma: no cover + _assert_feature_list_last_modified_values( + staff_client, + staff_user, + environment_v2_versioning, + project, + feature, + with_project_permissions, + django_assert_num_queries, + num_queries=19, + ) + + +def _assert_feature_list_last_modified_values( + staff_client: APIClient, + staff_user: FFAdminUser, + environment_v2_versioning: Environment, + project: Project, + feature: Feature, + with_project_permissions: WithProjectPermissionsCallable, + django_assert_num_queries: DjangoAssertNumQueries, + num_queries: int, +): # Given # another v2 versioning environment environment_v2_versioning_2 = Environment.objects.create( @@ -3222,7 +3319,7 @@ def test_feature_list_last_modified_values( Feature.objects.create(name=f"feature_{i}", project=project) # When - with django_assert_num_queries(18): # TODO: reduce this number of queries! + with django_assert_num_queries(num_queries): # TODO: reduce this number of queries! response = staff_client.get(url) # Then diff --git a/api/tests/unit/sales_dashboard/test_unit_sales_dashboard_views.py b/api/tests/unit/sales_dashboard/test_unit_sales_dashboard_views.py index 2cbeacd723cf..e19c450936e3 100644 --- a/api/tests/unit/sales_dashboard/test_unit_sales_dashboard_views.py +++ b/api/tests/unit/sales_dashboard/test_unit_sales_dashboard_views.py @@ -75,18 +75,16 @@ def test_get_organisation_info__get_event_list_for_organisation( def test_list_organisations_search_by_name( organisation: Organisation, - client: Client, - admin_user: FFAdminUser, + superuser_client: Client, ) -> None: # Given # use the truncated organisation name to ensure fuzzy search works search_term = organisation.name[1:-1] url = "%s?search=%s" % (reverse("sales_dashboard:index"), search_term) - client.force_login(admin_user) # When - response = client.get(url) + response = superuser_client.get(url) # Then assert response.status_code == 200 @@ -97,17 +95,15 @@ def test_list_organisations_search_by_name( def test_list_organisations_search_by_subscription_id( organisation: Organisation, chargebee_subscription: Subscription, - client: Client, - admin_user: FFAdminUser, + superuser_client: Client, ) -> None: # Given search_term = chargebee_subscription.subscription_id url = "%s?search=%s" % (reverse("sales_dashboard:index"), search_term) - client.force_login(admin_user) # When - response = client.get(url) + response = superuser_client.get(url) # Then assert response.status_code == 200 @@ -116,17 +112,16 @@ def test_list_organisations_search_by_subscription_id( def test_list_organisations_search_by_user_email( organisation: Organisation, - client: Client, + superuser_client: Client, admin_user: FFAdminUser, ) -> None: # Given search_term = admin_user.email url = "%s?search=%s" % (reverse("sales_dashboard:index"), search_term) - client.force_login(admin_user) # When - response = client.get(url) + response = superuser_client.get(url) # Then assert response.status_code == 200 @@ -136,18 +131,16 @@ def test_list_organisations_search_by_user_email( def test_list_organisations_filter_plan( organisation: Organisation, chargebee_subscription: Subscription, - client: Client, - admin_user: FFAdminUser, + superuser_client: Client, ) -> None: # Given url = "%s?filter_plan=%s" % ( reverse("sales_dashboard:index"), chargebee_subscription.plan, ) - client.force_login(admin_user) # When - response = client.get(url) + response = superuser_client.get(url) # Then assert response.status_code == 200 diff --git a/api/tests/unit/segments/test_unit_segments_views.py b/api/tests/unit/segments/test_unit_segments_views.py index a59d5f39d82d..680a67ec7963 100644 --- a/api/tests/unit/segments/test_unit_segments_views.py +++ b/api/tests/unit/segments/test_unit_segments_views.py @@ -2,6 +2,7 @@ import random import pytest +from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.urls import reverse @@ -362,6 +363,10 @@ def test_get_segment_by_uuid(client, project, segment): assert response.json()["uuid"] == str(segment.uuid) +@pytest.mark.skipif( + settings.IS_RBAC_INSTALLED is True, + reason="Skip this test if RBAC is installed", +) @pytest.mark.parametrize( "client, num_queries", [ @@ -369,32 +374,16 @@ def test_get_segment_by_uuid(client, project, segment): (lazy_fixture("admin_client"), 14), ], ) -def test_list_segments( +def test_list_segments_num_queries_without_rbac( django_assert_num_queries: DjangoAssertNumQueries, project: Project, client: APIClient, num_queries: int, required_a_segment_metadata_field: MetadataModelField, -): +) -> None: # Given num_segments = 5 - segments = [] - for i in range(num_segments): - segment = Segment.objects.create(project=project, name=f"segment {i}") - Metadata.objects.create( - object_id=segment.id, - content_type=ContentType.objects.get_for_model(segment), - model_field=required_a_segment_metadata_field, - field_value="test", - ) - all_rule = SegmentRule.objects.create( - segment=segment, type=SegmentRule.ALL_RULE - ) - any_rule = SegmentRule.objects.create(rule=all_rule, type=SegmentRule.ANY_RULE) - Condition.objects.create( - property="foo", value=str(random.randint(0, 10)), rule=any_rule - ) - segments.append(segment) + _list_segment_setup_data(project, required_a_segment_metadata_field, num_segments) # When with django_assert_num_queries(num_queries): @@ -413,6 +402,63 @@ def test_list_segments( assert response_json["count"] == num_segments +@pytest.mark.skipif( + settings.IS_RBAC_INSTALLED is False, + reason="Skip this test if RBAC is not installed", +) +@pytest.mark.parametrize( + "client, num_queries", + [ + (lazy_fixture("admin_master_api_key_client"), 12), + (lazy_fixture("admin_client"), 15), + ], +) +def test_list_segments_num_queries_with_rbac( + django_assert_num_queries: DjangoAssertNumQueries, + project: Project, + client: APIClient, + num_queries: int, + required_a_segment_metadata_field: MetadataModelField, +) -> None: # pragma: no cover + # Given + num_segments = 5 + _list_segment_setup_data(project, required_a_segment_metadata_field, num_segments) + + # When + with django_assert_num_queries(num_queries): + response = client.get( + reverse("api-v1:projects:project-segments-list", args=[project.id]) + ) + + # Then + assert response.status_code == status.HTTP_200_OK + + response_json = response.json() + assert response_json["count"] == num_segments + + +def _list_segment_setup_data( + project: Project, + required_a_segment_metadata_field: MetadataModelField, + num_segments: int, +) -> None: + for i in range(num_segments): + segment = Segment.objects.create(project=project, name=f"segment {i}") + Metadata.objects.create( + object_id=segment.id, + content_type=ContentType.objects.get_for_model(segment), + model_field=required_a_segment_metadata_field, + field_value="test", + ) + all_rule = SegmentRule.objects.create( + segment=segment, type=SegmentRule.ALL_RULE + ) + any_rule = SegmentRule.objects.create(rule=all_rule, type=SegmentRule.ANY_RULE) + Condition.objects.create( + property="foo", value=str(random.randint(0, 10)), rule=any_rule + ) + + @pytest.mark.parametrize( "client", [lazy_fixture("admin_master_api_key_client"), lazy_fixture("admin_client")], diff --git a/api/users/__init__.py b/api/users/__init__.py index 594a3d12c5f9..e69de29bb2d1 100644 --- a/api/users/__init__.py +++ b/api/users/__init__.py @@ -1 +0,0 @@ -default_app_config = "users.apps.UsersConfig" diff --git a/api/users/models.py b/api/users/models.py index 3c458e3b0e94..f370b00dffab 100644 --- a/api/users/models.py +++ b/api/users/models.py @@ -155,7 +155,8 @@ def delete( def set_password(self, raw_password): super().set_password(raw_password) - self.password_reset_requests.all().delete() + if self.id: + self.password_reset_requests.all().delete() @property def auth_type(self):