From b7b7a1fd6504f18868172fc2b105e185c96bccad Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Wed, 17 Jan 2024 14:24:08 -0500 Subject: [PATCH] Refactor into apps and lib --- ansible_base/README.md | 67 ------------------- .../__init__.py | 0 ansible_base/api_documentation/apps.py | 6 ++ .../migrations}/__init__.py | 0 ansible_base/api_documentation/urls.py | 8 +++ ansible_base/apps.py | 8 --- ansible_base/authentication/apps.py | 2 +- .../authenticator_plugins/base.py | 2 +- .../authenticator_plugins/keycloak.py | 2 +- .../authenticator_plugins/ldap.py | 4 +- .../authenticator_plugins/saml.py | 6 +- .../authentication/models/authenticator.py | 8 +-- .../models/authenticator_map.py | 2 +- .../serializers/authenticator.py | 4 +- .../serializers/authenticator_map.py | 2 +- ansible_base/authentication/views/ui_auth.py | 4 +- .../abstract_models}/__init__.py | 0 .../models => lib/abstract_models}/common.py | 6 +- .../abstract_models}/organization.py | 0 .../{filters => lib/channels}/__init__.py | 0 ansible_base/{ => lib}/channels/middleware.py | 2 +- ansible_base/{ => lib}/checks.py | 0 .../dynamic_config}/__init__.py | 0 .../dynamic_config}/dynamic_settings.py | 42 +++++++----- .../dynamic_config/dynamic_urls.py} | 2 +- .../{common => lib}/serializers/common.py | 4 +- .../{common => lib}/serializers/fields.py | 4 +- .../{settings => lib/utils}/__init__.py | 0 .../{common => lib}/utils/encryption.py | 0 ansible_base/{common => lib}/utils/models.py | 2 +- .../{common => lib}/utils/settings.py | 2 +- .../{common => lib}/utils/validation.py | 0 .../rest_filters}/__init__.py | 0 ansible_base/rest_filters/apps.py | 8 +++ .../rest_filters/migrations}/__init__.py | 0 .../rest_filters/rest_framework}/__init__.py | 0 .../rest_framework/field_lookup_backend.py | 4 +- .../rest_framework/order_backend.py | 2 +- .../rest_framework/type_filter_backend.py | 2 +- ansible_base/rest_filters/urls.py | 3 + .../{filters => rest_filters}/utils.py | 2 +- docs/Installation.md | 46 +++++-------- docs/api_documentation.md | 35 ---------- docs/apps/api_documentation.md | 63 +++++++++++++++++ .../authentication}/authentication.md | 37 ++++++---- .../authentication}/authenticator_plugins.md | 56 +++++++++++++++- .../authentication}/management_commands.md | 0 .../rest_filters.md} | 25 +++++-- docs/apps_or_lib.md | 66 ++++++++++++++++++ docs/default_models.md | 14 ---- docs/{ => lib}/channels_authentication.md | 6 +- docs/lib/default_models.md | 14 ++++ docs/{ => lib}/encryption.md | 4 +- docs/{ => lib}/organizations.md | 4 +- docs/lib/serializers.md | 5 ++ docs/lib/validation.md | 13 ++++ docs/serializers.md | 5 -- docs/validation.md | 13 ---- pyproject.toml | 8 +-- ...r.in => requirements_api_documentation.in} | 0 ...ilters.in => requirements_rest_filters.in} | 0 test_app/models.py | 4 +- test_app/serializers.py | 2 +- test_app/settings.py | 29 ++------ .../authenticator_plugins/test_ldap.py | 2 +- .../authenticator_plugins/test_saml.py | 2 +- .../tests/{common/utils => lib}/__init__.py | 0 .../abstract_models}/__init__.py | 0 .../abstract_models}/test_common.py | 0 .../abstract_models}/test_organization.py | 0 .../channels}/__init__.py | 0 .../{ => lib}/channels/test_middleware.py | 4 +- .../dynamic_config}/__init__.py | 0 .../dynamic_config}/test_dynamic_settings.py | 6 +- .../serializers/test_common.py | 4 +- .../serializers/test_fields.py | 2 +- test_app/tests/lib/utils/__init__.py | 0 .../{common => lib}/utils/test_encryption.py | 2 +- .../{common => lib}/utils/test_models.py | 2 +- .../{common => lib}/utils/test_settings.py | 4 +- .../{common => lib}/utils/test_validation.py | 2 +- test_app/tests/rest_filters/__init__.py | 0 .../rest_filters/rest_framework/__init__.py | 0 .../test_field_lookup_backend.py | 2 +- .../rest_framework/test_order_backend.py | 2 +- .../test_type_filter_backend.py | 2 +- .../{filters => rest_filters}/test_utils.py | 2 +- test_app/tests/test_checks.py | 2 +- test_app/urls.py | 2 +- 89 files changed, 402 insertions(+), 299 deletions(-) delete mode 100644 ansible_base/README.md rename ansible_base/{channels => api_documentation}/__init__.py (100%) create mode 100644 ansible_base/api_documentation/apps.py rename ansible_base/{common/utils => api_documentation/migrations}/__init__.py (100%) create mode 100644 ansible_base/api_documentation/urls.py delete mode 100644 ansible_base/apps.py rename ansible_base/{common/models => lib/abstract_models}/__init__.py (100%) rename ansible_base/{common/models => lib/abstract_models}/common.py (96%) rename ansible_base/{common/models => lib/abstract_models}/organization.py (100%) rename ansible_base/{filters => lib/channels}/__init__.py (100%) rename ansible_base/{ => lib}/channels/middleware.py (96%) rename ansible_base/{ => lib}/checks.py (100%) rename ansible_base/{filters/rest_framework => lib/dynamic_config}/__init__.py (100%) rename ansible_base/{settings => lib/dynamic_config}/dynamic_settings.py (74%) rename ansible_base/{urls.py => lib/dynamic_config/dynamic_urls.py} (86%) rename ansible_base/{common => lib}/serializers/common.py (95%) rename ansible_base/{common => lib}/serializers/fields.py (96%) rename ansible_base/{settings => lib/utils}/__init__.py (100%) rename ansible_base/{common => lib}/utils/encryption.py (100%) rename ansible_base/{common => lib}/utils/models.py (91%) rename ansible_base/{common => lib}/utils/settings.py (94%) rename ansible_base/{common => lib}/utils/validation.py (100%) rename {test_app/tests/channels => ansible_base/rest_filters}/__init__.py (100%) create mode 100644 ansible_base/rest_filters/apps.py rename {test_app/tests/common => ansible_base/rest_filters/migrations}/__init__.py (100%) rename {test_app/tests/common/models => ansible_base/rest_filters/rest_framework}/__init__.py (100%) rename ansible_base/{filters => rest_filters}/rest_framework/field_lookup_backend.py (98%) rename ansible_base/{filters => rest_filters}/rest_framework/order_backend.py (97%) rename ansible_base/{filters => rest_filters}/rest_framework/type_filter_backend.py (95%) create mode 100644 ansible_base/rest_filters/urls.py rename ansible_base/{filters => rest_filters}/utils.py (97%) delete mode 100644 docs/api_documentation.md create mode 100644 docs/apps/api_documentation.md rename docs/{ => apps/authentication}/authentication.md (88%) rename docs/{ => apps/authentication}/authenticator_plugins.md (76%) rename docs/{ => apps/authentication}/management_commands.md (100%) rename docs/{filtering_and_sorting.md => apps/rest_filters.md} (67%) create mode 100644 docs/apps_or_lib.md delete mode 100644 docs/default_models.md rename docs/{ => lib}/channels_authentication.md (74%) create mode 100644 docs/lib/default_models.md rename docs/{ => lib}/encryption.md (79%) rename docs/{ => lib}/organizations.md (81%) create mode 100644 docs/lib/serializers.md create mode 100644 docs/lib/validation.md delete mode 100644 docs/serializers.md delete mode 100644 docs/validation.md rename requirements/{requirements_swagger.in => requirements_api_documentation.in} (100%) rename requirements/{requirements_filters.in => requirements_rest_filters.in} (100%) rename test_app/tests/{common/utils => lib}/__init__.py (100%) rename test_app/tests/{filters => lib/abstract_models}/__init__.py (100%) rename test_app/tests/{common/models => lib/abstract_models}/test_common.py (100%) rename test_app/tests/{common/models => lib/abstract_models}/test_organization.py (100%) rename test_app/tests/{filters/rest_framework => lib/channels}/__init__.py (100%) rename test_app/tests/{ => lib}/channels/test_middleware.py (88%) rename test_app/tests/{settings => lib/dynamic_config}/__init__.py (100%) rename test_app/tests/{settings => lib/dynamic_config}/test_dynamic_settings.py (92%) rename test_app/tests/{common => lib}/serializers/test_common.py (94%) rename test_app/tests/{common => lib}/serializers/test_fields.py (94%) create mode 100644 test_app/tests/lib/utils/__init__.py rename test_app/tests/{common => lib}/utils/test_encryption.py (94%) rename test_app/tests/{common => lib}/utils/test_models.py (84%) rename test_app/tests/{common => lib}/utils/test_settings.py (90%) rename test_app/tests/{common => lib}/utils/test_validation.py (97%) create mode 100644 test_app/tests/rest_filters/__init__.py create mode 100644 test_app/tests/rest_filters/rest_framework/__init__.py rename test_app/tests/{filters => rest_filters}/rest_framework/test_field_lookup_backend.py (98%) rename test_app/tests/{filters => rest_filters}/rest_framework/test_order_backend.py (94%) rename test_app/tests/{filters => rest_filters}/rest_framework/test_type_filter_backend.py (91%) rename test_app/tests/{filters => rest_filters}/test_utils.py (83%) diff --git a/ansible_base/README.md b/ansible_base/README.md deleted file mode 100644 index b509f1b40..000000000 --- a/ansible_base/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# Installation - -TODO - -# Usage - -TODO - -# Writing an Authenticator - -## Social Auth - -Social Auth backends can be turned into authenticators by subclassing `SocialAuthMixin` and `AbstractAuthenticatorPlugin` -as shown in the example bellow (note that the `SocialAuthMixin` MUST come before KeycloakOauth2 so that the backend's name -gets set correctly): - -```python -import logging - -from django.utils.translation import gettext_lazy as _ -from rest_framework import serializers -from social_core.backends.keycloak import KeycloakOAuth2 - -from ansible_base.authentication.utils.authenticator_lib import AbstractAuthenticatorPlugin, BaseAuthenticatorConfiguration -from ansible_base.authentication.social_auth import SocialAuthMixin -from ansible_base.common.serializers.fields import URLField - -logger = logging.getLogger('ansible_base.authentication.authenticator_plugins.keycloak') - - -class KeycloakConfiguration(BaseAuthenticatorConfiguration): - documentation_url = "https://python-social-auth.readthedocs.io/en/latest/backends/keycloak.html" - - ACCESS_TOKEN_URL = URLField( - help_text=_("Location where this app can fetch the user's token from."), - default="https://keycloak.example.com/auth/realms//protocol/openid-connect/token", - allow_null=False, - ) - AUTHORIZATION_URL = URLField( - help_text=_("Location to redirect the user to during the login flow."), - default="https://keycloak.example.com/auth/realms//protocol/openid-connect/auth", - allow_null=False, - ) - KEY = serializers.CharField(help_text=_("Keycloak Client ID."), allow_null=False) - PUBLIC_KEY = serializers.CharField(help_text=_("RS256 public key provided by your Keycloak ream."), allow_null=False) - SECRET = serializers.CharField(help_text=_("Keycloak Client secret."), allow_null=True) - - -class AuthenticatorPlugin(SocialAuthMixin, KeycloakOAuth2, AbstractAuthenticatorPlugin): - configuration_class = KeycloakConfiguration - type = "keycloak" - logger = logger - - def get_user_groups(self): - return [] -``` - -In addition to the base classes, each social authenticator must: -- Define a `configuration_class` that subclasses `BaseAuthenticatorConfiguration`. This is a modified DRF serializer - object is defined in in teh same way. -- Define a plugin type. -- Define `get_user_groups()` (optional): authenticators can add extra logic here to return a list of groups based on - the attributes returned by their IDP. - -## Custom - -TODO: diff --git a/ansible_base/channels/__init__.py b/ansible_base/api_documentation/__init__.py similarity index 100% rename from ansible_base/channels/__init__.py rename to ansible_base/api_documentation/__init__.py diff --git a/ansible_base/api_documentation/apps.py b/ansible_base/api_documentation/apps.py new file mode 100644 index 000000000..3d0b98a2c --- /dev/null +++ b/ansible_base/api_documentation/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ApiDocumentationConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'ansible_base.api_documentation' diff --git a/ansible_base/common/utils/__init__.py b/ansible_base/api_documentation/migrations/__init__.py similarity index 100% rename from ansible_base/common/utils/__init__.py rename to ansible_base/api_documentation/migrations/__init__.py diff --git a/ansible_base/api_documentation/urls.py b/ansible_base/api_documentation/urls.py new file mode 100644 index 000000000..9e512fcdf --- /dev/null +++ b/ansible_base/api_documentation/urls.py @@ -0,0 +1,8 @@ +from django.urls import path +from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView + +urlpatterns = [ + path('docs/schema/', SpectacularAPIView.as_view(), name='schema'), + path('docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), + path('docs/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), +] diff --git a/ansible_base/apps.py b/ansible_base/apps.py deleted file mode 100644 index a53026254..000000000 --- a/ansible_base/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.apps import AppConfig - -import ansible_base.checks # noqa: F401 - register checks - - -class AnsibleAuthConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'ansible_base' diff --git a/ansible_base/authentication/apps.py b/ansible_base/authentication/apps.py index c699e5cb8..c880632b2 100644 --- a/ansible_base/authentication/apps.py +++ b/ansible_base/authentication/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -import ansible_base.checks # noqa: F401 - register checks +import ansible_base.lib.checks # noqa: F401 - register checks class AuthenticationConfig(AppConfig): diff --git a/ansible_base/authentication/authenticator_plugins/base.py b/ansible_base/authentication/authenticator_plugins/base.py index 5f9530db5..04ee5c98f 100644 --- a/ansible_base/authentication/authenticator_plugins/base.py +++ b/ansible_base/authentication/authenticator_plugins/base.py @@ -7,7 +7,7 @@ from rest_framework.serializers import ValidationError from ansible_base.authentication.models import Authenticator -from ansible_base.common.serializers.fields import JSONField +from ansible_base.lib.serializers.fields import JSONField logger = logging.getLogger('ansible_base.authentication.authenticator_plugins.base') diff --git a/ansible_base/authentication/authenticator_plugins/keycloak.py b/ansible_base/authentication/authenticator_plugins/keycloak.py index 86856e756..0be344478 100644 --- a/ansible_base/authentication/authenticator_plugins/keycloak.py +++ b/ansible_base/authentication/authenticator_plugins/keycloak.py @@ -5,7 +5,7 @@ from ansible_base.authentication.authenticator_plugins.base import AbstractAuthenticatorPlugin, BaseAuthenticatorConfiguration from ansible_base.authentication.social_auth import SocialAuthMixin -from ansible_base.common.serializers.fields import CharField, URLField +from ansible_base.lib.serializers.fields import CharField, URLField logger = logging.getLogger('ansible_base.authentication.authenticator_plugins.keycloak') diff --git a/ansible_base/authentication/authenticator_plugins/ldap.py b/ansible_base/authentication/authenticator_plugins/ldap.py index d7aad07e2..f51605ba1 100644 --- a/ansible_base/authentication/authenticator_plugins/ldap.py +++ b/ansible_base/authentication/authenticator_plugins/ldap.py @@ -14,8 +14,8 @@ from ansible_base.authentication.authenticator_plugins.base import AbstractAuthenticatorPlugin, Authenticator, BaseAuthenticatorConfiguration from ansible_base.authentication.utils.claims import get_or_create_authenticator_user, update_user_claims -from ansible_base.common.serializers.fields import BooleanField, CharField, ChoiceField, DictField, ListField, URLListField, UserAttrMap -from ansible_base.common.utils.validation import VALID_STRING +from ansible_base.lib.serializers.fields import BooleanField, CharField, ChoiceField, DictField, ListField, URLListField, UserAttrMap +from ansible_base.lib.utils.validation import VALID_STRING logger = logging.getLogger('ansible_base.authentication.authenticator_plugins.ldap') diff --git a/ansible_base/authentication/authenticator_plugins/saml.py b/ansible_base/authentication/authenticator_plugins/saml.py index fbb3e049c..cf3ec3c8b 100644 --- a/ansible_base/authentication/authenticator_plugins/saml.py +++ b/ansible_base/authentication/authenticator_plugins/saml.py @@ -14,9 +14,9 @@ from ansible_base.authentication.authenticator_plugins.utils import generate_authenticator_slug, get_authenticator_plugin from ansible_base.authentication.models import Authenticator from ansible_base.authentication.social_auth import AuthenticatorConfigTestStrategy, AuthenticatorStorage, AuthenticatorStrategy, SocialAuthMixin -from ansible_base.common.serializers.fields import CharField, JSONField, ListField, PrivateKey, PublicCert, URLField -from ansible_base.common.utils.encryption import ENCRYPTED_STRING -from ansible_base.common.utils.validation import validate_cert_with_key +from ansible_base.lib.serializers.fields import CharField, JSONField, ListField, PrivateKey, PublicCert, URLField +from ansible_base.lib.utils.encryption import ENCRYPTED_STRING +from ansible_base.lib.utils.validation import validate_cert_with_key logger = logging.getLogger('ansible_base.authentication.authenticator_plugins.saml') diff --git a/ansible_base/authentication/models/authenticator.py b/ansible_base/authentication/models/authenticator.py index f21c6ae66..5ad20fdcd 100644 --- a/ansible_base/authentication/models/authenticator.py +++ b/ansible_base/authentication/models/authenticator.py @@ -2,8 +2,8 @@ from django.db.models import JSONField, ManyToManyField, fields from ansible_base.authentication.authenticator_plugins.utils import generate_authenticator_slug, get_authenticator_plugin -from ansible_base.common.models.common import UniqueNamedCommonModel -from ansible_base.common.utils.models import prevent_search +from ansible_base.lib.abstract_models.common import UniqueNamedCommonModel +from ansible_base.lib.utils.models import prevent_search class Authenticator(UniqueNamedCommonModel): @@ -35,7 +35,7 @@ class Authenticator(UniqueNamedCommonModel): reverse_foreign_key_fields = ['authenticator-map'] def save(self, *args, **kwargs): - from ansible_base.common.utils.encryption import ansible_encryption + from ansible_base.lib.utils.encryption import ansible_encryption # Here we are going to allow an exception to raise because what else can we do at this point? authenticator = get_authenticator_plugin(self.type) @@ -58,7 +58,7 @@ def __str__(self): @classmethod def from_db(cls, db, field_names, values): - from ansible_base.common.utils.encryption import ENCRYPTED_STRING, ansible_encryption + from ansible_base.lib.utils.encryption import ENCRYPTED_STRING, ansible_encryption instance = super().from_db(db, field_names, values) diff --git a/ansible_base/authentication/models/authenticator_map.py b/ansible_base/authentication/models/authenticator_map.py index 5734b40e6..7173979a3 100644 --- a/ansible_base/authentication/models/authenticator_map.py +++ b/ansible_base/authentication/models/authenticator_map.py @@ -1,6 +1,6 @@ from django.db import models -from ansible_base.common.models.common import NamedCommonModel +from ansible_base.lib.abstract_models.common import NamedCommonModel from .authenticator import Authenticator diff --git a/ansible_base/authentication/serializers/authenticator.py b/ansible_base/authentication/serializers/authenticator.py index aba239dcc..593bf4ba1 100644 --- a/ansible_base/authentication/serializers/authenticator.py +++ b/ansible_base/authentication/serializers/authenticator.py @@ -5,8 +5,8 @@ from ansible_base.authentication.authenticator_plugins.utils import get_authenticator_plugin, get_authenticator_plugins from ansible_base.authentication.models import Authenticator -from ansible_base.common.serializers.common import NamedCommonModelSerializer -from ansible_base.common.utils.encryption import ENCRYPTED_STRING +from ansible_base.lib.serializers.common import NamedCommonModelSerializer +from ansible_base.lib.utils.encryption import ENCRYPTED_STRING logger = logging.getLogger('ansible_base.authentication.serializers.authenticator') diff --git a/ansible_base/authentication/serializers/authenticator_map.py b/ansible_base/authentication/serializers/authenticator_map.py index 45a480177..847fc982e 100644 --- a/ansible_base/authentication/serializers/authenticator_map.py +++ b/ansible_base/authentication/serializers/authenticator_map.py @@ -2,7 +2,7 @@ from ansible_base.authentication.models import AuthenticatorMap from ansible_base.authentication.utils.trigger_definition import TRIGGER_DEFINITION -from ansible_base.common.serializers.common import NamedCommonModelSerializer +from ansible_base.lib.serializers.common import NamedCommonModelSerializer class AuthenticatorMapSerializer(NamedCommonModelSerializer): diff --git a/ansible_base/authentication/views/ui_auth.py b/ansible_base/authentication/views/ui_auth.py index ae69afbe8..e97102eef 100644 --- a/ansible_base/authentication/views/ui_auth.py +++ b/ansible_base/authentication/views/ui_auth.py @@ -5,8 +5,8 @@ from rest_framework.views import APIView from ansible_base.authentication.models import Authenticator -from ansible_base.common.utils.settings import get_setting -from ansible_base.common.utils.validation import validate_image_data, validate_url +from ansible_base.lib.utils.settings import get_setting +from ansible_base.lib.utils.validation import validate_image_data, validate_url logger = logging.getLogger('ansible_base.authentication.views.ui_auth') diff --git a/ansible_base/common/models/__init__.py b/ansible_base/lib/abstract_models/__init__.py similarity index 100% rename from ansible_base/common/models/__init__.py rename to ansible_base/lib/abstract_models/__init__.py diff --git a/ansible_base/common/models/common.py b/ansible_base/lib/abstract_models/common.py similarity index 96% rename from ansible_base/common/models/common.py rename to ansible_base/lib/abstract_models/common.py index e8ba7cbf7..39ee22f91 100644 --- a/ansible_base/common/models/common.py +++ b/ansible_base/lib/abstract_models/common.py @@ -8,7 +8,7 @@ from inflection import underscore from rest_framework.reverse import reverse -logger = logging.getLogger('ansible_base.common.models.common') +logger = logging.getLogger('ansible_base.lib.abstract_models.common') class CommonModel(models.Model): @@ -71,7 +71,7 @@ def save(self, *args, **kwargs): update_fields.append('modified_by') # Encrypt any fields - from ansible_base.common.utils.encryption import ansible_encryption + from ansible_base.lib.utils.encryption import ansible_encryption for field in self.encrypted_fields: field_value = getattr(self, field, None) @@ -84,7 +84,7 @@ def save(self, *args, **kwargs): def from_db(self, db, field_names, values): instance = super().from_db(db, field_names, values) - from ansible_base.common.utils.encryption import ENCRYPTED_STRING, ansible_encryption + from ansible_base.lib.utils.encryption import ENCRYPTED_STRING, ansible_encryption for field in self.encrypted_fields: field_value = getattr(instance, field, None) diff --git a/ansible_base/common/models/organization.py b/ansible_base/lib/abstract_models/organization.py similarity index 100% rename from ansible_base/common/models/organization.py rename to ansible_base/lib/abstract_models/organization.py diff --git a/ansible_base/filters/__init__.py b/ansible_base/lib/channels/__init__.py similarity index 100% rename from ansible_base/filters/__init__.py rename to ansible_base/lib/channels/__init__.py diff --git a/ansible_base/channels/middleware.py b/ansible_base/lib/channels/middleware.py similarity index 96% rename from ansible_base/channels/middleware.py rename to ansible_base/lib/channels/middleware.py index ee54b89b5..52238e9e9 100644 --- a/ansible_base/channels/middleware.py +++ b/ansible_base/lib/channels/middleware.py @@ -11,7 +11,7 @@ from rest_framework.request import Request from rest_framework.settings import api_settings -logger = logging.getLogger('ansible_base.channels.middleware') +logger = logging.getLogger('ansible_base.lib.channels.middleware') User = get_user_model() diff --git a/ansible_base/checks.py b/ansible_base/lib/checks.py similarity index 100% rename from ansible_base/checks.py rename to ansible_base/lib/checks.py diff --git a/ansible_base/filters/rest_framework/__init__.py b/ansible_base/lib/dynamic_config/__init__.py similarity index 100% rename from ansible_base/filters/rest_framework/__init__.py rename to ansible_base/lib/dynamic_config/__init__.py diff --git a/ansible_base/settings/dynamic_settings.py b/ansible_base/lib/dynamic_config/dynamic_settings.py similarity index 74% rename from ansible_base/settings/dynamic_settings.py rename to ansible_base/lib/dynamic_config/dynamic_settings.py index 098002efd..cd902bf61 100644 --- a/ansible_base/settings/dynamic_settings.py +++ b/ansible_base/lib/dynamic_config/dynamic_settings.py @@ -5,29 +5,42 @@ # try: - ANSIBLE_BASE_FEATURES # noqa: F821 + INSTALLED_APPS # noqa: F821 except NameError: - ANSIBLE_BASE_FEATURES = {} + INSTALLED_APPS = [] try: - INSTALLED_APPS # noqa: F821 + REST_FRAMEWORK # noqa: F821 except NameError: - INSTALLED_APPS = [] + REST_FRAMEWORK = {} -if ANSIBLE_BASE_FEATURES.get('SWAGGER', False): # noqa: F821 - if 'drf_spectacular' not in INSTALLED_APPS: # noqa: F821 - INSTALLED_APPS.append('drf_spectacular') # noqa: F821 +if 'ansible_base.api_documentation' in INSTALLED_APPS: + if 'drf_spectacular' not in INSTALLED_APPS: + INSTALLED_APPS.append('drf_spectacular') + + try: + SPECTACULAR_SETTINGS # noqa: F821 + except NameError: + SPECTACULAR_SETTINGS = { + 'TITLE': 'AAP Open API', + 'DESCRIPTION': 'AAP Open API', + 'VERSION': 'v1', + 'SCHEMA_PATH_PREFIX': '/api/v1/', + } + + if 'DEFAULT_SCHEMA_CLASS' not in REST_FRAMEWORK: + REST_FRAMEWORK['DEFAULT_SCHEMA_CLASS'] = 'drf_spectacular.openapi.AutoSchema' -if ANSIBLE_BASE_FEATURES.get('FILTERING', False): # noqa: F821 - REST_FRAMEWORK.update( # noqa: F821 +if 'ansible_base.rest_filters' in INSTALLED_APPS: + REST_FRAMEWORK.update( { 'DEFAULT_FILTER_BACKENDS': ( - 'ansible_base.filters.rest_framework.type_filter_backend.TypeFilterBackend', - 'ansible_base.filters.rest_framework.field_lookup_backend.FieldLookupBackend', + 'ansible_base.rest_filters.rest_framework.type_filter_backend.TypeFilterBackend', + 'ansible_base.rest_filters.rest_framework.field_lookup_backend.FieldLookupBackend', 'rest_framework.filters.SearchFilter', - 'ansible_base.filters.rest_framework.order_backend.OrderByBackend', + 'ansible_base.rest_filters.rest_framework.order_backend.OrderByBackend', ) } ) @@ -51,11 +64,6 @@ except NameError: MIDDLEWARE = [middleware_class] - try: - REST_FRAMEWORK # noqa: F821 - except NameError: - REST_FRAMEWORK = {} - drf_authentication_class = 'ansible_base.authentication.session.SessionAuthentication' if 'DEFAULT_AUTHENTICATION_CLASSES' not in REST_FRAMEWORK: # noqa: F821 REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] = [] # noqa: F821 diff --git a/ansible_base/urls.py b/ansible_base/lib/dynamic_config/dynamic_urls.py similarity index 86% rename from ansible_base/urls.py rename to ansible_base/lib/dynamic_config/dynamic_urls.py index 591b9d95c..2750000d4 100644 --- a/ansible_base/urls.py +++ b/ansible_base/lib/dynamic_config/dynamic_urls.py @@ -3,7 +3,7 @@ from django.conf import settings from django.urls import include, path -logger = logging.getLogger('ansible_base.urls') +logger = logging.getLogger('ansible_base.lib.dynamic_config.dynamic_urls') list_actions = {'get': 'list', 'post': 'create'} detail_actions = {'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'} diff --git a/ansible_base/common/serializers/common.py b/ansible_base/lib/serializers/common.py similarity index 95% rename from ansible_base/common/serializers/common.py rename to ansible_base/lib/serializers/common.py index 59f814705..26fc6d974 100644 --- a/ansible_base/common/serializers/common.py +++ b/ansible_base/lib/serializers/common.py @@ -5,9 +5,9 @@ from rest_framework.fields import empty from rest_framework.reverse import reverse_lazy -from ansible_base.common.utils.encryption import ENCRYPTED_STRING +from ansible_base.lib.utils.encryption import ENCRYPTED_STRING -logger = logging.getLogger('ansible_base.common.serializers.common') +logger = logging.getLogger('ansible_base.lib.serializers.common') class CommonModelSerializer(serializers.ModelSerializer): diff --git a/ansible_base/common/serializers/fields.py b/ansible_base/lib/serializers/fields.py similarity index 96% rename from ansible_base/common/serializers/fields.py rename to ansible_base/lib/serializers/fields.py index 7a3415964..13626e9b6 100644 --- a/ansible_base/common/serializers/fields.py +++ b/ansible_base/lib/serializers/fields.py @@ -3,8 +3,8 @@ from django.contrib.auth import get_user_model from rest_framework import serializers -from ansible_base.common.utils.encryption import ENCRYPTED_STRING -from ansible_base.common.utils.validation import validate_url, validate_url_list +from ansible_base.lib.utils.encryption import ENCRYPTED_STRING +from ansible_base.lib.utils.validation import validate_url, validate_url_list User = get_user_model() diff --git a/ansible_base/settings/__init__.py b/ansible_base/lib/utils/__init__.py similarity index 100% rename from ansible_base/settings/__init__.py rename to ansible_base/lib/utils/__init__.py diff --git a/ansible_base/common/utils/encryption.py b/ansible_base/lib/utils/encryption.py similarity index 100% rename from ansible_base/common/utils/encryption.py rename to ansible_base/lib/utils/encryption.py diff --git a/ansible_base/common/utils/models.py b/ansible_base/lib/utils/models.py similarity index 91% rename from ansible_base/common/utils/models.py rename to ansible_base/lib/utils/models.py index 7c12bd260..5841e0ae9 100644 --- a/ansible_base/common/utils/models.py +++ b/ansible_base/lib/utils/models.py @@ -37,7 +37,7 @@ class AuthToken(BaseModel): sensitive_data = prevent_search(models.CharField(...)) The flag set by this function is used by - `ansible_base.filters.rest_framework.field_lookup_backend.FieldLookupBackend` to block fields and relations that + `ansible_base.rest_filters.rest_framework.field_lookup_backend.FieldLookupBackend` to block fields and relations that should not be searchable/filterable via search query params """ setattr(relation, '__prevent_search__', True) diff --git a/ansible_base/common/utils/settings.py b/ansible_base/lib/utils/settings.py similarity index 94% rename from ansible_base/common/utils/settings.py rename to ansible_base/lib/utils/settings.py index ebedc6d8c..214ed9f46 100644 --- a/ansible_base/common/utils/settings.py +++ b/ansible_base/lib/utils/settings.py @@ -4,7 +4,7 @@ from django.conf import settings -logger = logging.getLogger('ansible_base.common.utils.settings') +logger = logging.getLogger('ansible_base.lib.utils.settings') class SettingNotSetException(Exception): diff --git a/ansible_base/common/utils/validation.py b/ansible_base/lib/utils/validation.py similarity index 100% rename from ansible_base/common/utils/validation.py rename to ansible_base/lib/utils/validation.py diff --git a/test_app/tests/channels/__init__.py b/ansible_base/rest_filters/__init__.py similarity index 100% rename from test_app/tests/channels/__init__.py rename to ansible_base/rest_filters/__init__.py diff --git a/ansible_base/rest_filters/apps.py b/ansible_base/rest_filters/apps.py new file mode 100644 index 000000000..d393878d7 --- /dev/null +++ b/ansible_base/rest_filters/apps.py @@ -0,0 +1,8 @@ +from django.apps import AppConfig + +import ansible_base.lib.checks # noqa: F401 - register checks + + +class AuthenticationConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'ansible_base.rest_filters' diff --git a/test_app/tests/common/__init__.py b/ansible_base/rest_filters/migrations/__init__.py similarity index 100% rename from test_app/tests/common/__init__.py rename to ansible_base/rest_filters/migrations/__init__.py diff --git a/test_app/tests/common/models/__init__.py b/ansible_base/rest_filters/rest_framework/__init__.py similarity index 100% rename from test_app/tests/common/models/__init__.py rename to ansible_base/rest_filters/rest_framework/__init__.py diff --git a/ansible_base/filters/rest_framework/field_lookup_backend.py b/ansible_base/rest_filters/rest_framework/field_lookup_backend.py similarity index 98% rename from ansible_base/filters/rest_framework/field_lookup_backend.py rename to ansible_base/rest_filters/rest_framework/field_lookup_backend.py index 0234634bc..df9acc1a2 100644 --- a/ansible_base/filters/rest_framework/field_lookup_backend.py +++ b/ansible_base/rest_filters/rest_framework/field_lookup_backend.py @@ -13,8 +13,8 @@ from rest_framework.exceptions import ParseError from rest_framework.filters import BaseFilterBackend -from ansible_base.common.utils.validation import to_python_boolean -from ansible_base.filters.utils import get_fields_from_path +from ansible_base.lib.utils.validation import to_python_boolean +from ansible_base.rest_filters.utils import get_fields_from_path class FieldLookupBackend(BaseFilterBackend): diff --git a/ansible_base/filters/rest_framework/order_backend.py b/ansible_base/rest_filters/rest_framework/order_backend.py similarity index 97% rename from ansible_base/filters/rest_framework/order_backend.py rename to ansible_base/rest_filters/rest_framework/order_backend.py index 73bf28a4a..e2ede0433 100644 --- a/ansible_base/filters/rest_framework/order_backend.py +++ b/ansible_base/rest_filters/rest_framework/order_backend.py @@ -2,7 +2,7 @@ from rest_framework.exceptions import ParseError from rest_framework.filters import BaseFilterBackend -from ansible_base.filters.utils import get_all_field_names, get_field_from_path +from ansible_base.rest_filters.utils import get_all_field_names, get_field_from_path class OrderByBackend(BaseFilterBackend): diff --git a/ansible_base/filters/rest_framework/type_filter_backend.py b/ansible_base/rest_filters/rest_framework/type_filter_backend.py similarity index 95% rename from ansible_base/filters/rest_framework/type_filter_backend.py rename to ansible_base/rest_filters/rest_framework/type_filter_backend.py index fd819a997..223627a42 100644 --- a/ansible_base/filters/rest_framework/type_filter_backend.py +++ b/ansible_base/rest_filters/rest_framework/type_filter_backend.py @@ -4,7 +4,7 @@ from rest_framework.exceptions import ParseError from rest_framework.filters import BaseFilterBackend -from ansible_base.common.utils.models import get_all_field_names, get_type_for_model +from ansible_base.lib.utils.models import get_all_field_names, get_type_for_model class TypeFilterBackend(BaseFilterBackend): diff --git a/ansible_base/rest_filters/urls.py b/ansible_base/rest_filters/urls.py new file mode 100644 index 000000000..a3cc6e69d --- /dev/null +++ b/ansible_base/rest_filters/urls.py @@ -0,0 +1,3 @@ +# This feature does not require any urls + +urlpatterns = [] diff --git a/ansible_base/filters/utils.py b/ansible_base/rest_filters/utils.py similarity index 97% rename from ansible_base/filters/utils.py rename to ansible_base/rest_filters/utils.py index 00de6562f..9f20034fe 100644 --- a/ansible_base/filters/utils.py +++ b/ansible_base/rest_filters/utils.py @@ -2,7 +2,7 @@ from django.utils.translation import gettext_lazy as _ from rest_framework.exceptions import ParseError, PermissionDenied -from ansible_base.common.utils.models import get_all_field_names +from ansible_base.lib.utils.models import get_all_field_names def get_fields_from_path(model, path): diff --git a/docs/Installation.md b/docs/Installation.md index b0dd0bbdf..61e2c91a3 100644 --- a/docs/Installation.md +++ b/docs/Installation.md @@ -9,48 +9,38 @@ This will install django-ansible-base as well as all its optional dependencies. These can be found in `requirements/requirements_all.in` If there are features you are not going to use you can tell pip to only install required packages for the features you will use. -As of this writing there are three features: +As of this writing there are three django application features: + * api_documentation * authentication - * swagger - * filtering + * rest_filters So if you only wanted api_docs and filtering you could install the library like: ``` -pip install git+https://github.com/ansible/django-ansible-base.git[api_docs,filtering] +pip install git+https://github.com/ansible/django-ansible-base.git[api_documentation,rest_filters] ``` # Configuration -Once the library is installed you will need to add it to your installed apps in settings.py: -``` -INSTALLED_APPS = [ - ... - 'ansible_base', -] -``` -Some features in django-ansible-base are also applications that need to be added to installed_apps. -For example, if you want to use authentication you would need to add it like: +## INSTALLED_APPS +For any django app features you will need to add them to your `INSTALLED_APPS` like: ``` INSTALLED_APPS = [ - ... - 'ansible_base', - 'ansible_base.authentication', + 'ansible_base.rest_filters', ] ``` -Next we can turn on various feature of django-ansible base in your settings file: -``` -ANSIBLE_BASE_FEATURES = { - 'AUTHENTICATION': True, - 'FILTERING': False -} -``` - -Finally, we can let django-ansible-base add the settings it needs to function: +## settings.py +Next, we can let django-ansible-base add the settings it needs to function: ``` -from ansible_base import settings -settings_file = os.path.join(os.path.dirname(settings.__file__), 'dynamic_settings.py') -include(settings_file) +from ansible_base.lib import dynamic_config +dab_settings = os.path.join(os.path.dirname(dynamic_config.__file__), 'dynamic_settings.py') +include(dab_settings) ``` Please read the various sections of this documentation for what django-ansible-base will do to your settings. + +## urls.py + +Finally, we can include the urls that django-ansible-base provides + +Please read the various sections of this documentation for what urls django-ansible-base will add to your application. diff --git a/docs/api_documentation.md b/docs/api_documentation.md deleted file mode 100644 index e92d6cc5a..000000000 --- a/docs/api_documentation.md +++ /dev/null @@ -1,35 +0,0 @@ -# Open API and Swagger documentation - -django-ansible-base uses django-spectacular to auto-generate both Open API and Swagger documentation of the API. - -If you enable the `SWAGGER` feature of django-ansible-base we will auto-append `drf_spectacular` to your `INSTALLED_APPS` if its not already present: -``` -INSTALLED_APPS = [ - ... - 'drf_spectacular', - ... -] -``` - -You will need to add the following `SPECTACULAR_SETTINGS`: -``` -SPECTACULAR_SETTINGS = { - 'TITLE': 'AAP API', - 'DESCRIPTION': 'AAP API', - 'VERSION': 'v', - 'SCHEMA_PATH_PREFIX': '/api//v/', -} -``` - -Now add the following to your urls.py: -``` -from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView - -urlpatterns = [ - ... - path('api/v1/docs/schema/', SpectacularAPIView.as_view(), name='schema'), - path('api/v1/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), - path('api/v1/docs/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), - ... -] -``` diff --git a/docs/apps/api_documentation.md b/docs/apps/api_documentation.md new file mode 100644 index 000000000..d8a85c1e2 --- /dev/null +++ b/docs/apps/api_documentation.md @@ -0,0 +1,63 @@ +# Open API and Swagger documentation + +django-ansible-base uses django-spectacular to auto-generate both Open API and Swagger documentation of the API. + +## Settings + +Add `ansible_base.api_documentation` to your installed apps: + +``` +INSTALLED_APPS = [ + ... + 'ansible_base.api_documentation', +] +``` + +### Additional Settings +Additional settings are required to enable api_documentation. +This will happen automatically if using [dynamic_settings](./dyanmic_settings.md) + +First, you need to add `drf_spectacular` to your `INSTALLED_APPS`: +``` +INSTALLED_APPS = [ + ... + 'drf_spectacular', + ... +] +``` + +Additionally, we create a `SPECTACULAR_SETTINGS` entry if its not already present: +``` +SPECTACULAR_SETTINGS = { + 'TITLE': 'AAP Open API', + 'DESCRIPTION': 'AAP Open API', + 'VERSION': 'v1', + 'SCHEMA_PATH_PREFIX': '/api/v1/', +} +``` + +Finally, add a `DEFAULT_SCHEMA_CLASS` to your `REST_FRAMEWORK` setting: +``` +REST_FRAMEWORK = { + ... + 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', + ... +} +``` + +## URLS + +This feature includes URLs which you will get if you are using [dynamic urls](./dyanmic_config.md) + +If you want to manually add the urls without dynamic urls add the following to your urls.py: +``` +from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView + +urlpatterns = [ + ... + path('api/v1/docs/schema/', SpectacularAPIView.as_view(), name='schema'), + path('api/v1/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), + path('api/v1/docs/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), + ... +] +``` diff --git a/docs/authentication.md b/docs/apps/authentication/authentication.md similarity index 88% rename from docs/authentication.md rename to docs/apps/authentication/authentication.md index 15349489c..5e3e7e132 100644 --- a/docs/authentication.md +++ b/docs/apps/authentication/authentication.md @@ -5,9 +5,22 @@ django-ansible-base has a plugable authentication setup allowing you to add logi ## Settings -First you need to enable the `AUTHENTICATION` feature of django-ansible-base. +Add `ansible_base.authentication` to your installed apps: -### AUTHENTICATION_BACKENDS +``` +INSTALLED_APPS = [ + ... + 'ansible_base.authentication', +] +``` + +### Additional Settings +Additional settings are required to enable authentication on your rest endpoints. +This will happen automatically if using [dynamic_settings](./dyanmic_config.md) + +To manually enable authentication without dynamic settings the following items need to be included in your settings: + +#### AUTHENTICATION_BACKENDS django-ansible-base will automatically set the AUTHENTICATION_BACKENDS as follows unless you explicitly have an `AUTHENTICATION_BACKENDS` in your settings.py: ``` AUTHENTICATION_BACKENDS = [ @@ -17,7 +30,7 @@ AUTHENTICATION_BACKENDS = [ If you have other backends in there please consider whether or not you need them. If you do can you make a plugin for django-ansible-base? -### MIDDLEWARE +#### MIDDLEWARE django-ansible-base will automatically insert it's middleware class into your MIDDLEWARE array if you have not already added it. If `django.contrib.auth.middleware.AuthenticationMiddleware` is not in your middleware the django-ansible-base class will be appended as the last item in your MIDDLEWARE. If `django.contrib.auth.middleware.AuthenticationMiddleware` is in your MIDDLEWARE the django-ansible-base class will be inserted before that. ``` MIDDLEWARE = [ @@ -31,7 +44,7 @@ MIDDLEWARE = [ Note: this must come before django.contrib.auth.middleware.AuthenticationMiddlware in order to have precedence over it. Otherwise a local user will be authenticated even if the user was destined for LDAP/Tacacs+/Radius/etc. -### ANSIBLE_BASE_AUTHENTICATOR_CLASS_PREFIXES +#### ANSIBLE_BASE_AUTHENTICATOR_CLASS_PREFIXES By default, django-ansible-base will look in the class `ansible_base.authentication.authenticator_plugins` for the available authenticator plugins. If you would like to provide additional or custom paths you can set the following setting: ``` ANSIBLE_BASE_AUTHENTICATOR_CLASS_PREFIXES = ["ansible_base.authentication.authenticator_plugins"] @@ -39,8 +52,7 @@ ANSIBLE_BASE_AUTHENTICATOR_CLASS_PREFIXES = ["ansible_base.authentication.authen If you are going to create a different class to hold the plugins you can change or add to this as needed. -### REST_FRAMEWORK - +#### REST_FRAMEWORK If you are using DRF and enable django-ansible-base authentication we prepend our authentication class to your REST_FRAMEWORK settings if our class is not already present: ``` REST_FRAMEWORK = { @@ -52,10 +64,12 @@ REST_FRAMEWORK = { 'ansible_base.authentication.session.SessionAuthentication', ... ], + ... +} ``` -### Social Auth Settings +#### Social Auth Settings django-ansible-base will add the following social auth settings: ``` SOCIAL_AUTH_PIPELINE = ( @@ -96,13 +110,12 @@ Any additional settings supplied by this function will be applied to out default ## URLS -For generic authentication we need to import the urls from `ansible_base` like the following: -``` -from ansible_base.urls import urls as base_urls -``` +This feature includes URLs which you will get if you are using [dynamic urls](./dyanmic_config.md) + +If you want to manually add the urls without dynamic urls add the following to your urls.py: -Then we will include the ansible_base endpoints as follows: ``` +from ansible_base.lib.dynamic_config.dynamic_urls import urls as base_urls urlpatterns = [ ... path('api/v1/', include(base_urls)), diff --git a/docs/authenticator_plugins.md b/docs/apps/authentication/authenticator_plugins.md similarity index 76% rename from docs/authenticator_plugins.md rename to docs/apps/authentication/authenticator_plugins.md index 0070ce90c..2de5ec954 100644 --- a/docs/authenticator_plugins.md +++ b/docs/apps/authentication/authenticator_plugins.md @@ -38,7 +38,7 @@ The Configuration Class tells the plugin system what attributes are needed for t Your configuration class should first override the documentation_url property. This is a URL to point a user towards the documentation for the library you are implementing. This can be used to reference the general architecture of the backend and how the configuration classes' fields are used by any supporting libraries. #### Serializer Fields -Your configuration class can then specify 0 or more configuration fields. These fields are serializer fields but should come from `ansible_base.common.serializers.fields`. There we subclass serializer fields to add the ui_field_label and any additional fields we might require in the future. +Your configuration class can then specify 0 or more configuration fields. These fields are serializer fields but should come from `ansible_base.lib.serializers.fields`. There we subclass serializer fields to add the ui_field_label and any additional fields we might require in the future. Here is an example of a serializer field from the SAML authenticator: ``` @@ -78,3 +78,57 @@ Currently the only way to specify additional python requirements which may be ne django-auth-ldap python-ldap ``` + +## Social Auth + +Social Auth backends can be turned into authenticators by subclassing `SocialAuthMixin` and `AbstractAuthenticatorPlugin` +as shown in the example bellow (note that the `SocialAuthMixin` MUST come before KeycloakOauth2 so that the backend's name +gets set correctly): + +```python +import logging + +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers +from social_core.backends.keycloak import KeycloakOAuth2 + +from ansible_base.authentication.utils.authenticator_lib import AbstractAuthenticatorPlugin, BaseAuthenticatorConfiguration +from ansible_base.authentication.social_auth import SocialAuthMixin +from ansible_base.lib.serializers.fields import URLField + +logger = logging.getLogger('ansible_base.authentication.authenticator_plugins.keycloak') + + +class KeycloakConfiguration(BaseAuthenticatorConfiguration): + documentation_url = "https://python-social-auth.readthedocs.io/en/latest/backends/keycloak.html" + + ACCESS_TOKEN_URL = URLField( + help_text=_("Location where this app can fetch the user's token from."), + default="https://keycloak.example.com/auth/realms//protocol/openid-connect/token", + allow_null=False, + ) + AUTHORIZATION_URL = URLField( + help_text=_("Location to redirect the user to during the login flow."), + default="https://keycloak.example.com/auth/realms//protocol/openid-connect/auth", + allow_null=False, + ) + KEY = serializers.CharField(help_text=_("Keycloak Client ID."), allow_null=False) + PUBLIC_KEY = serializers.CharField(help_text=_("RS256 public key provided by your Keycloak ream."), allow_null=False) + SECRET = serializers.CharField(help_text=_("Keycloak Client secret."), allow_null=True) + + +class AuthenticatorPlugin(SocialAuthMixin, KeycloakOAuth2, AbstractAuthenticatorPlugin): + configuration_class = KeycloakConfiguration + type = "keycloak" + logger = logger + + def get_user_groups(self): + return [] +``` + +In addition to the base classes, each social authenticator must: +- Define a `configuration_class` that subclasses `BaseAuthenticatorConfiguration`. This is a modified DRF serializer + object is defined in in the same way. +- Define a plugin type. +- Define `get_user_groups()` (optional): authenticators can add extra logic here to return a list of groups based on + the attributes returned by their IDP. diff --git a/docs/management_commands.md b/docs/apps/authentication/management_commands.md similarity index 100% rename from docs/management_commands.md rename to docs/apps/authentication/management_commands.md diff --git a/docs/filtering_and_sorting.md b/docs/apps/rest_filters.md similarity index 67% rename from docs/filtering_and_sorting.md rename to docs/apps/rest_filters.md index 88945ec55..00854a158 100644 --- a/docs/filtering_and_sorting.md +++ b/docs/apps/rest_filters.md @@ -2,15 +2,30 @@ django-ansible-base has a built in mechanism for filtering and sorting query sets based on django-rest-framework Filters. -To enable filtering on your rest endpoints edit your settings file and modify `REST_FRAMEWORK` with the following entry: +## Settings + +Add `ansible_base.rest_filters` to your installed apps: + +``` +INSTALLED_APPS = [ + ... + 'ansible_base.rest_filters', +] +``` + +### Additional Settings +Additional settings are required to enable filtering on your rest endpoints. +This will happen automatically if using [dynamic_settings](./dyanmic_config.md) + +To manually enable filtering without dynamic settings the following items need to be included in your settings: ``` REST_FRAMEWORK = { ... 'DEFAULT_FILTER_BACKENDS': ( - 'ansible_base.filters.rest_framework.type_filter_backend.TypeFilterBackend', - 'ansible_base.filters.rest_framework.field_lookup_backend.FieldLookupBackend', + 'ansible_base.rest_filters.rest_framework.type_filter_backend.TypeFilterBackend', + 'ansible_base.rest_filters.rest_framework.field_lookup_backend.FieldLookupBackend', 'rest_framework.filters.SearchFilter', - 'ansible_base.filters.rest_framework.order_backend.OrderByBackend', + 'ansible_base.rest_filters.rest_framework.order_backend.OrderByBackend', ), ... } @@ -23,7 +38,7 @@ REST_FRAMEWORK = { Sensitive fields like passwords should be excluded from being searched. To do there is there a function called `prevent_search` which can wrap your model fields like: ``` -from ansible_base.common.utils.models import prevent_search +from ansible_base.lib.utils.models import prevent_search class Authenticator(UniqueNamedCommonModel): ... diff --git a/docs/apps_or_lib.md b/docs/apps_or_lib.md new file mode 100644 index 000000000..a07f38ca4 --- /dev/null +++ b/docs/apps_or_lib.md @@ -0,0 +1,66 @@ +# django-ansible-base Features +django-ansible-base provides features as combination of django applications and python libraries. + +All top level folders in ansible_base are django applications for features except for the lib folder. + +## Determining if a new feature should be an app or a lib + +To determine if a feature should be a library or a django-application please consider the following criteria. + +If any of the following are true, you should create your new feature as a django app: +1) Does the feature require models +2) Does the feature require urls +3) Does the feature provide management commands +4) Does the feature require settings + +If all of these are false, your feature should be a library under the `libs` folder. + +## Initializing a new application + +### Selecting a name + +Please try to choose a concise descriptive name that is not an overloaded term. + +For example, we chose `rest_filters` instead of using a generic term like `filters`. + +### Initializing an application + +To have django initialize your application run the following commands: + +``` +cd ansible_base +python ../manage.py startapp +``` + +This will automatically create a folder and files for you with the name of your application in ansible_base. + +You can leave the `__init__.py` and the `migrations` folder alone. + +The `admin.py` file should be deleted. + +In the `apps.py` file be sure to change the `name` of the application to be prefixed with `ansible_base.`. For example, if your called your app `testing` the generated line would be: + +``` +name = 'testing' +``` + +But we need to change it to: +``` +name = 'ansible_base.testing' +``` + +If you plan to have models you can either add them to `models.py` or, if you have multiple you can remove `models.py` and create a `models` folder (be sure to add an `__init__.py` into the models folder). If you don't plan to have models `models.py` can be deleted. + +You can remove `tests.py` as we don't put tests in ansible_base. Additionally, you should create a folder matching your application name in `test_app/tests` and put all of your test files in there. + +If you have views you can add them to `views.py` or, like models, remove `views.py` and create a `views` folder. If you don't plan to have views `views.py` can be deleted. + +Finally, be sure to add `urls.py`. If your application does not require any URLS you can simply leave it blank like: + +``` +# This feature does not require any urls + +urlpatterns = [] +``` + +We need to do this for the dynamic url loader. diff --git a/docs/default_models.md b/docs/default_models.md deleted file mode 100644 index 678a27127..000000000 --- a/docs/default_models.md +++ /dev/null @@ -1,14 +0,0 @@ -# Common Models - -django-ansible-base can house common models available to all ansible django apps. - -Models include: - - -`ansible_base.common.models.common.CommonModel` This model has built in fields for created/modified tracking. It also has provisions for setting up related and summary fields from the models themselves. Related fields are auto-discovered through foreign keys. Summary fields starts here with just `id`. - -`ansible_base.common.models.common.NamedCommonModel` Extends CommonModel with a unique name and appends the `name` to the summary fields. - - -If you are using either of these models as a base class you can use their cooresponding default serializers as well: -`ansible_base.common.serializers.common.CommonModelSerializer` or `ansible_base.common.serializers.common.NamedCommonModelSerializer` diff --git a/docs/channels_authentication.md b/docs/lib/channels_authentication.md similarity index 74% rename from docs/channels_authentication.md rename to docs/lib/channels_authentication.md index 6ab74cb6c..87b4a6a1a 100644 --- a/docs/channels_authentication.md +++ b/docs/lib/channels_authentication.md @@ -4,10 +4,10 @@ Django channels claims to support standard Django authentication out-of-the box django-ansible-base provides a channels middleware to support Django authentication backed by AUTHENTICATION_BACKENDS in settings.py. ## Settings -There is no requirement to alter any configuration in settings.py. You only need to import ansible_base.channels.middleware into your project. It however looks for settings.AUTHENTICATION_BACKENDS for possible authenticators to authenticate the websocet connection. +There is no requirement to alter any configuration in settings.py. You only need to import ansible_base.lib.channels.middleware into your project. It however looks for settings.AUTHENTICATION_BACKENDS for possible authenticators to authenticate the websocket connection. ## Usage -`ansible_base.channels.middleware` has `DrfAuthMiddleware` which expands `channels.auth.AuthMiddleware` functionalities to support sessions and other authentication methods. It requires `CookieMiddleware` and `SessionMiddleware`. For conveninence `DrfAuthMiddlewareStack` is provided to inclued all three. +`ansible_base.lib.channels.middleware` has `DrfAuthMiddleware` which expands `channels.auth.AuthMiddleware` functionalities to support sessions and other authentication methods. It requires `CookieMiddleware` and `SessionMiddleware`. For convenience `DrfAuthMiddlewareStack` is provided to included all three. To use the middleware, wrap it around the appropriate level of consumer in your `asgi.py`: @@ -17,7 +17,7 @@ from django.urls import re_path from channels.routing import ProtocolTypeRouter, URLRouter from channels.security.websocket import AllowedHostsOriginValidator -from ansible_base.channels.middleware import DrfAuthMiddlewareStack +from ansible_base.lib.channels.middleware import DrfAuthMiddlewareStack from myapp import consumers diff --git a/docs/lib/default_models.md b/docs/lib/default_models.md new file mode 100644 index 000000000..66c44b39a --- /dev/null +++ b/docs/lib/default_models.md @@ -0,0 +1,14 @@ +# Common Models + +django-ansible-base can house common models available to all ansible django apps. + +Models include: + + +`ansible_base.lib.abstract_models.common.CommonModel` This model has built in fields for created/modified tracking. It also has provisions for setting up related and summary fields from the models themselves. Related fields are auto-discovered through foreign keys. Summary fields starts here with just `id`. + +`ansible_base.lib.abstract_models.common.NamedCommonModel` Extends CommonModel with a unique name and appends the `name` to the summary fields. + + +If you are using either of these models as a base class you can use their corresponding default serializers as well: +`ansible_base.lib.serializers.common.CommonModelSerializer` or `ansible_base.lib.serializers.common.NamedCommonModelSerializer` diff --git a/docs/encryption.md b/docs/lib/encryption.md similarity index 79% rename from docs/encryption.md rename to docs/lib/encryption.md index 7860a24de..6d18d0ab4 100644 --- a/docs/encryption.md +++ b/docs/lib/encryption.md @@ -2,10 +2,10 @@ django-ansible-base has a built in method for encrypting and decryption of strings as well as a constant which can be displayed in an API to indicate that a string is encrypted. -These come from `ansible_base.common.utils.encryption` like: +These come from `ansible_base.lib.utils.encryption` like: ``` -from ansible_base.common.utils.encryption import ENCRYPTED_STRING, ansible_encryption +from ansible_base.lib.utils.encryption import ENCRYPTED_STRING, ansible_encryption ``` You could then have code like: diff --git a/docs/organizations.md b/docs/lib/organizations.md similarity index 81% rename from docs/organizations.md rename to docs/lib/organizations.md index 370d1efaf..ec683f64e 100644 --- a/docs/organizations.md +++ b/docs/lib/organizations.md @@ -1,7 +1,7 @@ # Organizations The django-ansible-base project provides the -`ansible_base.common.models.organization.AbstractOrganization` base class. Projects that implement +`ansible_base.lib.abstract_models.organization.AbstractOrganization` base class. Projects that implement organizations MUST inherit this model. The `AbstractOrganization` has the following fields: @@ -11,7 +11,7 @@ The `AbstractOrganization` has the following fields: * `users` – A many to many relationship to the user model (defined by the `AUTH_USER_MODEL` setting), * `teams` – A many to many relationship to the team model (defined by the `ANSIBLE_BASE_TEAM_MODEL` setting), -for the list of remaining fields see `ansible_base.common.models.common.CommonModel`. +for the list of remaining fields see `ansible_base.lib.abstract_models.common.CommonModel`. The user and the team models will receive an additional field named `organizations`, that references related organizations of a respective model. diff --git a/docs/lib/serializers.md b/docs/lib/serializers.md new file mode 100644 index 000000000..98d90a1d7 --- /dev/null +++ b/docs/lib/serializers.md @@ -0,0 +1,5 @@ +# Serializers + +django-ansible-base can house common serializer fields. These are in `ansible_base.lib.serializers.fields`. + +`ansible_base.lib.serializers.fields.URLField` can handle doing URL validation (see validation.md). diff --git a/docs/lib/validation.md b/docs/lib/validation.md new file mode 100644 index 000000000..33c7dd47f --- /dev/null +++ b/docs/lib/validation.md @@ -0,0 +1,13 @@ +# Data validation + +django-ansible-base provides some basic validation tools. These reside in `ansible_base.lib.utils.validation`. +The following items are available from the validation library: + +`ansible_base.lib.utils.validation.VALID_STRING` this is a common string which says say: +``` +Must be a valid string +``` + +`ansible_base.lib.utils.validation.validate_url` this is similar to the validate_url in django but has a parameter for `allow_plain_hostname: bool = False` which means you can have a url like `https://something:443/testing`. + +`ansible_base.lib.utils.validation.validate_url_list` this is a convince method which takes an array of urls and validates each of them using its own validate_url method. diff --git a/docs/serializers.md b/docs/serializers.md deleted file mode 100644 index 3d0761106..000000000 --- a/docs/serializers.md +++ /dev/null @@ -1,5 +0,0 @@ -# Serializers - -django-ansible-base can house common serializer fields. These are in `ansible_base.common.serializers.fields`. - -`ansible_base.common.serializers.fields.URLField` can handle doing URL validation (see validation.md). diff --git a/docs/validation.md b/docs/validation.md deleted file mode 100644 index 146e2a545..000000000 --- a/docs/validation.md +++ /dev/null @@ -1,13 +0,0 @@ -# Data validation - -django-ansible-base provides some basic validation tools. These reside in `ansible_base.common.utils.validation`. -The following items are available from the validation library: - -`ansible_base.common.utils.validation.VALID_STRING` this is a common string which says say: -``` -Must be a valid string -``` - -`ansible_base.common.utils.validation.validate_url` this is similar to the validate_url in django but has a parameter for `allow_plain_hostname: bool = False` which means you can have a url like `https://something:443/testing`. - -`ansible_base.common.utils.validation.validate_url_list` this is a convince method which takes an array of urls and validates each of them using its own validate_url method. diff --git a/pyproject.toml b/pyproject.toml index 1619cac87..525d9ae9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,13 +36,13 @@ packages =["test_app", "ansible_base"] dependencies = {file = ["requirements/requirements.in"]} optional-dependencies.all = { file = [ "requirements/requirements_authentication.in", - "requirements/requirements_swagger.in", - "requirements/requirements_filtering.in", + "requirements/requirements_api_documentation.in", + "requirements/requirements_rest_filters.in", "requirements/requirements_channel_auth.in", ] } optional-dependencies.authentication = { file = [ "requirements/requirements_authentication.in" ] } -optional-dependencies.swagger = { file = [ "requirements/requirements_swagger.in" ] } -optional-dependencies.filtering = { file = [ "requirements/requirements_filtering.in" ] } +optional-dependencies.swagger = { file = [ "requirements/requirements_api_documentation.in" ] } +optional-dependencies.filtering = { file = [ "requirements/requirements_rest_filters.in" ] } optional-dependencies.channel_auth = { file = [ "requirements/requirements_channel_auth.in" ] } [build-system] diff --git a/requirements/requirements_swagger.in b/requirements/requirements_api_documentation.in similarity index 100% rename from requirements/requirements_swagger.in rename to requirements/requirements_api_documentation.in diff --git a/requirements/requirements_filters.in b/requirements/requirements_rest_filters.in similarity index 100% rename from requirements/requirements_filters.in rename to requirements/requirements_rest_filters.in diff --git a/test_app/models.py b/test_app/models.py index 74ad6b1b1..ad271e8ce 100644 --- a/test_app/models.py +++ b/test_app/models.py @@ -1,7 +1,7 @@ from django.db import models -from ansible_base.common.models import AbstractOrganization -from ansible_base.common.models.common import NamedCommonModel +from ansible_base.lib.abstract_models import AbstractOrganization +from ansible_base.lib.abstract_models.common import NamedCommonModel class EncryptionModel(NamedCommonModel): diff --git a/test_app/serializers.py b/test_app/serializers.py index 56cbab5d1..b08683485 100644 --- a/test_app/serializers.py +++ b/test_app/serializers.py @@ -1,4 +1,4 @@ -from ansible_base.common.serializers.common import NamedCommonModelSerializer +from ansible_base.lib.serializers.common import NamedCommonModelSerializer from test_app.models import EncryptionModel diff --git a/test_app/settings.py b/test_app/settings.py index 48001759d..1a3ff56b8 100644 --- a/test_app/settings.py +++ b/test_app/settings.py @@ -49,8 +49,9 @@ 'django.contrib.staticfiles', 'rest_framework', 'social_django', - 'ansible_base', + 'ansible_base.api_documentation', 'ansible_base.authentication', + 'ansible_base.rest_filters', 'test_app', ] @@ -59,8 +60,6 @@ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', - # must come before django.contrib.auth.middleware.AuthenticationMiddleware - 'ansible_base.authentication.middleware.AuthenticatorBackendMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', @@ -69,26 +68,6 @@ ROOT_URLCONF = 'test_app.urls' -SOCIAL_AUTH_PIPELINE = ( - 'social_core.pipeline.social_auth.social_details', - 'social_core.pipeline.social_auth.social_uid', - 'social_core.pipeline.social_auth.auth_allowed', - 'social_core.pipeline.social_auth.social_user', - 'social_core.pipeline.user.get_username', - 'social_core.pipeline.user.create_user', - 'social_core.pipeline.social_auth.associate_user', - 'social_core.pipeline.social_auth.load_extra_data', - 'social_core.pipeline.user.user_details', - 'ansible_base.authentication.social_auth.create_user_claims_pipeline', -) -SOCIAL_AUTH_STORAGE = "ansible_base.authentication.social_auth.AuthenticatorStorage" -SOCIAL_AUTH_STRATEGY = "ansible_base.authentication.social_auth.AuthenticatorStrategy" -SOCIAL_AUTH_LOGIN_REDIRECT_URL = "/api/v1/me" - -AUTHENTICATION_BACKENDS = [ - "ansible_base.authentication.backend.AnsibleBaseAuth", -] - ANSIBLE_BASE_AUTHENTICATOR_CLASS_PREFIXES = ['ansible_base.authentication.authenticator_plugins'] TEMPLATES = [ @@ -114,7 +93,7 @@ AUTH_USER_MODEL = 'auth.User' -from ansible_base import settings # noqa: E402 +from ansible_base.lib import dynamic_config # noqa: E402 -settings_file = os.path.join(os.path.dirname(settings.__file__), 'dynamic_settings.py') +settings_file = os.path.join(os.path.dirname(dynamic_config.__file__), 'dynamic_settings.py') include(settings_file) diff --git a/test_app/tests/authentication/authenticator_plugins/test_ldap.py b/test_app/tests/authentication/authenticator_plugins/test_ldap.py index c44f17531..d66d80038 100644 --- a/test_app/tests/authentication/authenticator_plugins/test_ldap.py +++ b/test_app/tests/authentication/authenticator_plugins/test_ldap.py @@ -10,7 +10,7 @@ from ansible_base.authentication.authenticator_plugins.ldap import AuthenticatorPlugin, LDAPSettings, validate_ldap_filter from ansible_base.authentication.models import Authenticator from ansible_base.authentication.session import SessionAuthentication -from ansible_base.common.utils.encryption import ENCRYPTED_STRING +from ansible_base.lib.utils.encryption import ENCRYPTED_STRING authenticated_test_page = "authenticator-list" diff --git a/test_app/tests/authentication/authenticator_plugins/test_saml.py b/test_app/tests/authentication/authenticator_plugins/test_saml.py index 4d3c29887..bbaadbecc 100644 --- a/test_app/tests/authentication/authenticator_plugins/test_saml.py +++ b/test_app/tests/authentication/authenticator_plugins/test_saml.py @@ -4,7 +4,7 @@ from django.urls import reverse from ansible_base.authentication.session import SessionAuthentication -from ansible_base.common.utils.encryption import ENCRYPTED_STRING +from ansible_base.lib.utils.encryption import ENCRYPTED_STRING authenticated_test_page = "authenticator-list" diff --git a/test_app/tests/common/utils/__init__.py b/test_app/tests/lib/__init__.py similarity index 100% rename from test_app/tests/common/utils/__init__.py rename to test_app/tests/lib/__init__.py diff --git a/test_app/tests/filters/__init__.py b/test_app/tests/lib/abstract_models/__init__.py similarity index 100% rename from test_app/tests/filters/__init__.py rename to test_app/tests/lib/abstract_models/__init__.py diff --git a/test_app/tests/common/models/test_common.py b/test_app/tests/lib/abstract_models/test_common.py similarity index 100% rename from test_app/tests/common/models/test_common.py rename to test_app/tests/lib/abstract_models/test_common.py diff --git a/test_app/tests/common/models/test_organization.py b/test_app/tests/lib/abstract_models/test_organization.py similarity index 100% rename from test_app/tests/common/models/test_organization.py rename to test_app/tests/lib/abstract_models/test_organization.py diff --git a/test_app/tests/filters/rest_framework/__init__.py b/test_app/tests/lib/channels/__init__.py similarity index 100% rename from test_app/tests/filters/rest_framework/__init__.py rename to test_app/tests/lib/channels/__init__.py diff --git a/test_app/tests/channels/test_middleware.py b/test_app/tests/lib/channels/test_middleware.py similarity index 88% rename from test_app/tests/channels/test_middleware.py rename to test_app/tests/lib/channels/test_middleware.py index a5ea902b4..28c92da74 100644 --- a/test_app/tests/channels/test_middleware.py +++ b/test_app/tests/lib/channels/test_middleware.py @@ -2,7 +2,7 @@ import pytest -import ansible_base.channels.middleware as middleware +import ansible_base.lib.channels.middleware as middleware @pytest.mark.django_db(transaction=True) @@ -19,7 +19,7 @@ async def test_middleware_auth_pass(local_authenticator, user): @pytest.mark.django_db(transaction=True) @pytest.mark.asyncio -@patch('ansible_base.channels.middleware.WebsocketDenier') +@patch('ansible_base.lib.channels.middleware.WebsocketDenier') async def test_middleware_auth_denied(denier_class, local_authenticator, user): denier = AsyncMock() denier_class.return_value = denier diff --git a/test_app/tests/settings/__init__.py b/test_app/tests/lib/dynamic_config/__init__.py similarity index 100% rename from test_app/tests/settings/__init__.py rename to test_app/tests/lib/dynamic_config/__init__.py diff --git a/test_app/tests/settings/test_dynamic_settings.py b/test_app/tests/lib/dynamic_config/test_dynamic_settings.py similarity index 92% rename from test_app/tests/settings/test_dynamic_settings.py rename to test_app/tests/lib/dynamic_config/test_dynamic_settings.py index c40bbde1d..43b610171 100644 --- a/test_app/tests/settings/test_dynamic_settings.py +++ b/test_app/tests/lib/dynamic_config/test_dynamic_settings.py @@ -1,11 +1,11 @@ from os import path from textwrap import dedent -from ansible_base import settings as ab_settings +from ansible_base.lib import dynamic_config def get_updated_settings(additional_config): - file_name = path.join(path.dirname(ab_settings.__file__), 'dynamic_settings.py') + file_name = path.join(path.dirname(dynamic_config.__file__), 'dynamic_settings.py') code_to_compile = '' with open(file_name, 'r') as to_compile: code_to_compile = to_compile.read() @@ -110,4 +110,4 @@ def test_filtering(): ''' ) updated_settings = get_updated_settings(additional_config) - assert 'ansible_base.filters.rest_framework.type_filter_backend.TypeFilterBackend' in updated_settings['REST_FRAMEWORK']['DEFAULT_FILTER_BACKENDS'] + assert 'ansible_base.rest_filters.rest_framework.type_filter_backend.TypeFilterBackend' in updated_settings['REST_FRAMEWORK']['DEFAULT_FILTER_BACKENDS'] diff --git a/test_app/tests/common/serializers/test_common.py b/test_app/tests/lib/serializers/test_common.py similarity index 94% rename from test_app/tests/common/serializers/test_common.py rename to test_app/tests/lib/serializers/test_common.py index 2bcf7ac9e..a75ffd99a 100644 --- a/test_app/tests/common/serializers/test_common.py +++ b/test_app/tests/lib/serializers/test_common.py @@ -1,8 +1,8 @@ import pytest from ansible_base.authentication.models import AuthenticatorMap -from ansible_base.common.serializers.common import CommonModelSerializer -from ansible_base.common.utils.encryption import ENCRYPTED_STRING +from ansible_base.lib.serializers.common import CommonModelSerializer +from ansible_base.lib.utils.encryption import ENCRYPTED_STRING from test_app.models import EncryptionModel from test_app.serializers import EncryptionTestSerializer diff --git a/test_app/tests/common/serializers/test_fields.py b/test_app/tests/lib/serializers/test_fields.py similarity index 94% rename from test_app/tests/common/serializers/test_fields.py rename to test_app/tests/lib/serializers/test_fields.py index 7cb74185f..7078f50a9 100644 --- a/test_app/tests/common/serializers/test_fields.py +++ b/test_app/tests/lib/serializers/test_fields.py @@ -1,7 +1,7 @@ import pytest from rest_framework.serializers import ValidationError -from ansible_base.common.serializers.fields import UserAttrMap +from ansible_base.lib.serializers.fields import UserAttrMap def test_check_user_attribute_map_success(): diff --git a/test_app/tests/lib/utils/__init__.py b/test_app/tests/lib/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test_app/tests/common/utils/test_encryption.py b/test_app/tests/lib/utils/test_encryption.py similarity index 94% rename from test_app/tests/common/utils/test_encryption.py rename to test_app/tests/lib/utils/test_encryption.py index 74c604fab..95f397c66 100644 --- a/test_app/tests/common/utils/test_encryption.py +++ b/test_app/tests/lib/utils/test_encryption.py @@ -1,6 +1,6 @@ import pytest -from ansible_base.common.utils.encryption import ENCRYPTION_METHOD, Fernet256 +from ansible_base.lib.utils.encryption import ENCRYPTION_METHOD, Fernet256 def test_fernet256_encrypt_is_idempotent(): diff --git a/test_app/tests/common/utils/test_models.py b/test_app/tests/lib/utils/test_models.py similarity index 84% rename from test_app/tests/common/utils/test_models.py rename to test_app/tests/lib/utils/test_models.py index 77bc1cf97..277294a32 100644 --- a/test_app/tests/common/utils/test_models.py +++ b/test_app/tests/lib/utils/test_models.py @@ -1,6 +1,6 @@ from unittest.mock import MagicMock -from ansible_base.common.utils import models +from ansible_base.lib.utils import models def test_get_type_for_model(): diff --git a/test_app/tests/common/utils/test_settings.py b/test_app/tests/lib/utils/test_settings.py similarity index 90% rename from test_app/tests/common/utils/test_settings.py rename to test_app/tests/lib/utils/test_settings.py index 26d3d467b..be1ffafae 100644 --- a/test_app/tests/common/utils/test_settings.py +++ b/test_app/tests/lib/utils/test_settings.py @@ -3,7 +3,7 @@ import pytest from django.test import override_settings -from ansible_base.common.utils.settings import SettingNotSetException, get_setting +from ansible_base.lib.utils.settings import SettingNotSetException, get_setting @pytest.mark.django_db @@ -13,7 +13,7 @@ def test_unset_setting(): assert value == default_value -@mock.patch("ansible_base.common.utils.settings.logger") +@mock.patch("ansible_base.lib.utils.settings.logger") @override_settings(ANSIBLE_BASE_SETTINGS_FUNCTION='junk') def test_invalid_settings_function(logger): default_value = 'default_value' diff --git a/test_app/tests/common/utils/test_validation.py b/test_app/tests/lib/utils/test_validation.py similarity index 97% rename from test_app/tests/common/utils/test_validation.py rename to test_app/tests/lib/utils/test_validation.py index c1ac71e25..70bf3ffb9 100644 --- a/test_app/tests/common/utils/test_validation.py +++ b/test_app/tests/lib/utils/test_validation.py @@ -1,7 +1,7 @@ import pytest from rest_framework.exceptions import ValidationError -from ansible_base.common.utils.validation import to_python_boolean, validate_cert_with_key, validate_image_data, validate_url +from ansible_base.lib.utils.validation import to_python_boolean, validate_cert_with_key, validate_image_data, validate_url @pytest.mark.parametrize( diff --git a/test_app/tests/rest_filters/__init__.py b/test_app/tests/rest_filters/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test_app/tests/rest_filters/rest_framework/__init__.py b/test_app/tests/rest_filters/rest_framework/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test_app/tests/filters/rest_framework/test_field_lookup_backend.py b/test_app/tests/rest_filters/rest_framework/test_field_lookup_backend.py similarity index 98% rename from test_app/tests/filters/rest_framework/test_field_lookup_backend.py rename to test_app/tests/rest_filters/rest_framework/test_field_lookup_backend.py index b21b05064..2a39637d5 100644 --- a/test_app/tests/filters/rest_framework/test_field_lookup_backend.py +++ b/test_app/tests/rest_filters/rest_framework/test_field_lookup_backend.py @@ -6,7 +6,7 @@ from ansible_base.authentication.models import Authenticator, AuthenticatorMap from ansible_base.authentication.views import AuthenticatorViewSet -from ansible_base.filters.rest_framework.field_lookup_backend import FieldLookupBackend +from ansible_base.rest_filters.rest_framework.field_lookup_backend import FieldLookupBackend def test_filters_related(): diff --git a/test_app/tests/filters/rest_framework/test_order_backend.py b/test_app/tests/rest_filters/rest_framework/test_order_backend.py similarity index 94% rename from test_app/tests/filters/rest_framework/test_order_backend.py rename to test_app/tests/rest_filters/rest_framework/test_order_backend.py index 246b6eb40..30fbfcef9 100644 --- a/test_app/tests/filters/rest_framework/test_order_backend.py +++ b/test_app/tests/rest_filters/rest_framework/test_order_backend.py @@ -6,7 +6,7 @@ from ansible_base.authentication.models import Authenticator from ansible_base.authentication.views import AuthenticatorViewSet -from ansible_base.filters.rest_framework.order_backend import OrderByBackend +from ansible_base.rest_filters.rest_framework.order_backend import OrderByBackend def test_get_default_ordering_no_order(): diff --git a/test_app/tests/filters/rest_framework/test_type_filter_backend.py b/test_app/tests/rest_filters/rest_framework/test_type_filter_backend.py similarity index 91% rename from test_app/tests/filters/rest_framework/test_type_filter_backend.py rename to test_app/tests/rest_filters/rest_framework/test_type_filter_backend.py index 0aa0c2bf7..8f4b6de0b 100644 --- a/test_app/tests/filters/rest_framework/test_type_filter_backend.py +++ b/test_app/tests/rest_filters/rest_framework/test_type_filter_backend.py @@ -6,7 +6,7 @@ from ansible_base.authentication.models import Authenticator from ansible_base.authentication.views import AuthenticatorViewSet -from ansible_base.filters.rest_framework.type_filter_backend import TypeFilterBackend +from ansible_base.rest_filters.rest_framework.type_filter_backend import TypeFilterBackend @pytest.mark.parametrize( diff --git a/test_app/tests/filters/test_utils.py b/test_app/tests/rest_filters/test_utils.py similarity index 83% rename from test_app/tests/filters/test_utils.py rename to test_app/tests/rest_filters/test_utils.py index 2b4b15aab..f28a66082 100644 --- a/test_app/tests/filters/test_utils.py +++ b/test_app/tests/rest_filters/test_utils.py @@ -2,7 +2,7 @@ from rest_framework.exceptions import ParseError from ansible_base.authentication.models import Authenticator -from ansible_base.filters.utils import get_field_from_path +from ansible_base.rest_filters.utils import get_field_from_path def test_invalid_field_hop(): diff --git a/test_app/tests/test_checks.py b/test_app/tests/test_checks.py index e330cd88f..5c4dd1571 100644 --- a/test_app/tests/test_checks.py +++ b/test_app/tests/test_checks.py @@ -1,7 +1,7 @@ from unittest import mock from ansible_base.authentication.models.authenticator import Authenticator -from ansible_base.checks import check_charfield_has_max_length +from ansible_base.lib.checks import check_charfield_has_max_length def test_check_charfield_has_max_length_fails(): diff --git a/test_app/urls.py b/test_app/urls.py index 803a82314..e552b5296 100644 --- a/test_app/urls.py +++ b/test_app/urls.py @@ -2,7 +2,7 @@ from django.urls import include, path, re_path urlpatterns = [ - re_path(r'api/v1/', include('ansible_base.urls')), + re_path(r'api/v1/', include('ansible_base.lib.dynamic_config.dynamic_urls')), # Social auth path('api/social/', include('social_django.urls', namespace='social')), # Admin application