diff --git a/src/aap_eda/api/filters/__init__.py b/src/aap_eda/api/filters/__init__.py deleted file mode 100644 index d881c198f..000000000 --- a/src/aap_eda/api/filters/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2023 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from .activation import ( - ActivationFilter, - ActivationInstanceFilter, - ActivationInstanceLogFilter, -) -from .credential_type import CredentialTypeFilter -from .decision_environment import DecisionEnvironmentFilter -from .eda_credential import EdaCredentialFilter -from .event_stream import EventStreamFilter -from .organization import OrganizationFilter -from .project import ProjectFilter -from .rulebook import RulebookFilter -from .team import OrganizationTeamFilter, TeamFilter -from .user import UserFilter - -__all__ = ( - # project - "ProjectFilter", - # rulebook - "RulebookFilter", - # credential type - "CredentialTypeFilter", - "EdaCredentialFilter", - # decision_environment - "DecisionEnvironmentFilter", - # activation instance - "ActivationInstanceFilter", - "ActivationFilter", - "ActivationInstanceLogFilter", - # user - "UserFilter", - # organization - "OrganizationFilter", - # team - "TeamFilter", - "OrganizationTeamFilter", - # EventStream - "EventStreamFilter", -) diff --git a/src/aap_eda/api/filters/activation.py b/src/aap_eda/api/filters/activation.py deleted file mode 100644 index 512fde2a6..000000000 --- a/src/aap_eda/api/filters/activation.py +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright 2023 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import django_filters - -from aap_eda.core import models - - -class ActivationFilter(django_filters.FilterSet): - name = django_filters.CharFilter( - field_name="name", - lookup_expr="icontains", - label="Filter by activation name.", - ) - status = django_filters.CharFilter( - field_name="status", - lookup_expr="istartswith", - label="Filter by activation status.", - ) - decision_environment_id = django_filters.NumberFilter( - field_name="decision_environment_id", - lookup_expr="exact", - label="Filter by Decision Environment ID.", - ) - - class Meta: - model = models.Activation - fields = ["name", "status", "decision_environment_id"] - - -class ActivationInstanceFilter(django_filters.FilterSet): - name = django_filters.CharFilter( - field_name="name", - lookup_expr="icontains", - label="Filter by activation instance name.", - ) - status = django_filters.CharFilter( - field_name="status", - lookup_expr="istartswith", - label="Filter by activation instance status.", - ) - - class Meta: - model = models.RulebookProcess - fields = ["name", "status"] - - -class ActivationInstanceLogFilter(django_filters.FilterSet): - log = django_filters.CharFilter( - field_name="log", - lookup_expr="icontains", - label="Filter by activation instance log.", - ) - - class Meta: - model = models.RulebookProcessLog - fields = ["log"] diff --git a/src/aap_eda/api/filters/credential_type.py b/src/aap_eda/api/filters/credential_type.py deleted file mode 100644 index 7719cb39a..000000000 --- a/src/aap_eda/api/filters/credential_type.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2024 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import django_filters - -from aap_eda.core import models - - -class CredentialTypeFilter(django_filters.FilterSet): - name = django_filters.CharFilter( - field_name="name", - lookup_expr="istartswith", - label="Filter by credential type name.", - ) - namespace = django_filters.CharFilter( - field_name="namespace", - lookup_expr="istartswith", - label="Filter by credential type namespace.", - ) - - class Meta: - model = models.CredentialType - fields = ["name", "namespace"] diff --git a/src/aap_eda/api/filters/decision_environment.py b/src/aap_eda/api/filters/decision_environment.py deleted file mode 100644 index 115d7e00a..000000000 --- a/src/aap_eda/api/filters/decision_environment.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2023 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import django_filters - -from aap_eda.core import models - - -class DecisionEnvironmentFilter(django_filters.FilterSet): - name = django_filters.CharFilter( - field_name="name", - lookup_expr="istartswith", - label="Filter by decision environment name.", - ) - - class Meta: - model = models.DecisionEnvironment - fields = ["name"] diff --git a/src/aap_eda/api/filters/eda_credential.py b/src/aap_eda/api/filters/eda_credential.py deleted file mode 100644 index 0b9b74323..000000000 --- a/src/aap_eda/api/filters/eda_credential.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2024 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import django_filters - -from aap_eda.core import models - - -class EdaCredentialFilter(django_filters.FilterSet): - name = django_filters.CharFilter( - field_name="name", - lookup_expr="istartswith", - label="Filter by EDA credential name.", - ) - credential_type_id = django_filters.NumberFilter( - field_name="credential_type_id", - lookup_expr="exact", - label="Filter by Credential Type ID.", - ) - - class Meta: - model = models.EdaCredential - fields = ["name", "credential_type_id"] diff --git a/src/aap_eda/api/filters/event_stream.py b/src/aap_eda/api/filters/event_stream.py deleted file mode 100644 index c79af9bd9..000000000 --- a/src/aap_eda/api/filters/event_stream.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2024 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import django_filters - -from aap_eda.core import models - - -class EventStreamFilter(django_filters.FilterSet): - name = django_filters.CharFilter( - field_name="name", - lookup_expr="istartswith", - label="Filter by event stream name.", - ) - test_mode = django_filters.BooleanFilter( - field_name="test_mode", - label="Filter by the test_mode being true or false", - ) - - class Meta: - model = models.EventStream - fields = ["name"] diff --git a/src/aap_eda/api/filters/organization.py b/src/aap_eda/api/filters/organization.py deleted file mode 100644 index 48783a319..000000000 --- a/src/aap_eda/api/filters/organization.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2024 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import django_filters - -from aap_eda.core.models import Organization - - -class OrganizationFilter(django_filters.FilterSet): - name = django_filters.CharFilter( - field_name="name", - lookup_expr="istartswith", - label="Filter by organization name.", - ) - description = django_filters.CharFilter( - field_name="description", - lookup_expr="icontains", - label="Filter by organization description.", - ) - resource__ansible_id = django_filters.CharFilter( - field_name="resource__ansible_id", - lookup_expr="iexact", - label="Filter by resource ansible ID.", - ) - - class Meta: - model = Organization - fields = ["name", "description", "resource__ansible_id"] diff --git a/src/aap_eda/api/filters/project.py b/src/aap_eda/api/filters/project.py deleted file mode 100644 index afd557146..000000000 --- a/src/aap_eda/api/filters/project.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2023 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import django_filters - -from aap_eda.core import models - - -class ProjectFilter(django_filters.FilterSet): - name = django_filters.CharFilter( - field_name="name", - lookup_expr="istartswith", - label="Filter by project name.", - ) - url = django_filters.CharFilter( - field_name="url", - lookup_expr="icontains", - label="Filter by project url.", - ) - - class Meta: - model = models.Project - fields = ["name", "url"] diff --git a/src/aap_eda/api/filters/rulebook.py b/src/aap_eda/api/filters/rulebook.py deleted file mode 100644 index f721a06b8..000000000 --- a/src/aap_eda/api/filters/rulebook.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2023 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import django_filters - -from aap_eda.core import models - - -class RulebookFilter(django_filters.FilterSet): - name = django_filters.CharFilter( - field_name="name", - lookup_expr="icontains", - label="Filter by rulebook name.", - ) - - project_id = django_filters.NumberFilter( - field_name="project_id", - lookup_expr="exact", - label="Filter by rulebook's project id.", - ) - - class Meta: - model = models.Rulebook - fields = ["name", "project_id"] diff --git a/src/aap_eda/api/filters/team.py b/src/aap_eda/api/filters/team.py deleted file mode 100644 index c7a7a3293..000000000 --- a/src/aap_eda/api/filters/team.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright 2024 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import django_filters - -from aap_eda.core.models import Team - - -class TeamFilter(django_filters.FilterSet): - name = django_filters.CharFilter( - field_name="name", - lookup_expr="istartswith", - label="Filter by team name.", - ) - organization_id = django_filters.NumberFilter( - field_name="organization_id", - lookup_expr="exact", - label="Filter by organization ID.", - ) - description = django_filters.CharFilter( - field_name="description", - lookup_expr="icontains", - label="Filter by team description.", - ) - resource__ansible_id = django_filters.CharFilter( - field_name="resource__ansible_id", - lookup_expr="iexact", - label="Filter by resource ansible ID.", - ) - - class Meta: - model = Team - fields = [ - "name", - "organization_id", - "description", - "resource__ansible_id", - ] - - -class OrganizationTeamFilter(django_filters.FilterSet): - name = django_filters.CharFilter( - field_name="name", - lookup_expr="istartswith", - label="Filter by team name.", - ) - description = django_filters.CharFilter( - field_name="description", - lookup_expr="icontains", - label="Filter by team description.", - ) - resource__ansible_id = django_filters.CharFilter( - field_name="resource__ansible_id", - lookup_expr="iexact", - label="Filter by resource ansible ID.", - ) - - class Meta: - model = Team - fields = ["name", "description", "resource__ansible_id"] diff --git a/src/aap_eda/api/filters/user.py b/src/aap_eda/api/filters/user.py deleted file mode 100644 index 01aadd90b..000000000 --- a/src/aap_eda/api/filters/user.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2023 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import django_filters - -from aap_eda.core import models - - -class UserFilter(django_filters.FilterSet): - name = django_filters.CharFilter( - field_name="username", - lookup_expr="istartswith", - label="Filter by Username.", - ) - resource__ansible_id = django_filters.CharFilter( - field_name="resource__ansible_id", - lookup_expr="iexact", - label="Filter by resource ansible ID.", - ) - is_superuser = django_filters.BooleanFilter( - field_name="is_superuser", - label="Filter by superuser status.", - ) - - class Meta: - model = models.User - fields = ["username", "resource__ansible_id", "is_superuser"] diff --git a/src/aap_eda/api/views/activation.py b/src/aap_eda/api/views/activation.py index 87b9e0721..848a0a189 100644 --- a/src/aap_eda/api/views/activation.py +++ b/src/aap_eda/api/views/activation.py @@ -18,7 +18,6 @@ from ansible_base.rbac.models import RoleDefinition from django.db import transaction from django.forms import model_to_dict -from django_filters import rest_framework as defaultfilters from drf_spectacular.utils import ( OpenApiParameter, OpenApiResponse, @@ -29,7 +28,7 @@ from rest_framework.decorators import action from rest_framework.response import Response -from aap_eda.api import exceptions as api_exc, filters, serializers +from aap_eda.api import exceptions as api_exc, serializers from aap_eda.api.serializers.activation import is_activation_valid from aap_eda.core import models from aap_eda.core.enums import Action, ActivationStatus, ProcessParentType @@ -40,6 +39,7 @@ start_rulebook_process, stop_rulebook_process, ) +from aap_eda.utils.openapi import generate_query_params from .mixins import RedisDependencyMixin @@ -54,8 +54,6 @@ class ActivationViewSet( RedisDependencyMixin, ): queryset = models.Activation.objects.all() - filter_backends = (defaultfilters.DjangoFilterBackend,) - filterset_class = filters.ActivationFilter rbac_action = None @@ -269,6 +267,9 @@ def retrieve(self, request, pk: int): description="Return a list of Activations.", ), }, + parameters=generate_query_params( + serializers.ActivationListSerializer() + ), ) def list(self, request): activations = self.filter_queryset(self.get_queryset()) @@ -304,12 +305,12 @@ def list(self, request): location=OpenApiParameter.PATH, description="A unique integer value identifying this rulebook.", # noqa: E501 ) - ], + ] + + generate_query_params(serializers.ActivationInstanceSerializer()), ) @action( detail=False, queryset=models.RulebookProcess.objects.order_by("id"), - filterset_class=filters.ActivationInstanceFilter, rbac_action=Action.READ, url_path="(?P[^/.]+)/instances", ) @@ -588,6 +589,9 @@ def _check_deleting(self, activation): serializers.ActivationInstanceSerializer ), }, + parameters=generate_query_params( + serializers.ActivationInstanceSerializer() + ), ), ) class ActivationInstanceViewSet(viewsets.ReadOnlyModelViewSet): @@ -595,8 +599,6 @@ class ActivationInstanceViewSet(viewsets.ReadOnlyModelViewSet): "rulebookprocessqueue", ) serializer_class = serializers.ActivationInstanceSerializer - filter_backends = (defaultfilters.DjangoFilterBackend,) - filterset_class = filters.ActivationInstanceFilter rbac_action = None def filter_queryset(self, queryset): @@ -621,12 +623,12 @@ def filter_queryset(self, queryset): location=OpenApiParameter.PATH, description="A unique integer value identifying this Activation Instance.", # noqa: E501 ) - ], + ] + + generate_query_params(serializers.ActivationInstanceLogSerializer()), ) @action( detail=False, queryset=models.RulebookProcessLog.objects.order_by("id"), - filterset_class=filters.ActivationInstanceLogFilter, rbac_action=Action.READ, url_path="(?P[^/.]+)/logs", ) diff --git a/src/aap_eda/api/views/credential_type.py b/src/aap_eda/api/views/credential_type.py index 48bbff5cd..c601316ed 100644 --- a/src/aap_eda/api/views/credential_type.py +++ b/src/aap_eda/api/views/credential_type.py @@ -19,7 +19,6 @@ from ansible_base.rbac.models import RoleDefinition from django.db import transaction from django.forms import model_to_dict -from django_filters import rest_framework as defaultfilters from drf_spectacular.utils import ( OpenApiResponse, extend_schema, @@ -28,9 +27,10 @@ from rest_framework import mixins, status, viewsets from rest_framework.response import Response -from aap_eda.api import filters, serializers +from aap_eda.api import serializers from aap_eda.core import models from aap_eda.core.enums import ResourceType +from aap_eda.utils.openapi import generate_query_params from .mixins import ( CreateModelMixin, @@ -59,6 +59,9 @@ description="Return a list of credential types.", ), }, + parameters=generate_query_params( + serializers.CredentialTypeSerializer() + ), ), ) class CredentialTypeViewSet( @@ -73,8 +76,6 @@ class CredentialTypeViewSet( permission_classes = (AuthenticatedReadAdminChange,) queryset = models.CredentialType.objects.all() serializer_class = serializers.CredentialTypeSerializer - filter_backends = (defaultfilters.DjangoFilterBackend,) - filterset_class = filters.CredentialTypeFilter ordering_fields = ["name"] rbac_resource_type = ResourceType.CREDENTIAL_TYPE rbac_action = None diff --git a/src/aap_eda/api/views/decision_environment.py b/src/aap_eda/api/views/decision_environment.py index 12416aef0..3e5c6de6f 100644 --- a/src/aap_eda/api/views/decision_environment.py +++ b/src/aap_eda/api/views/decision_environment.py @@ -13,7 +13,6 @@ # limitations under the License. import logging -from django_filters import rest_framework as defaultfilters from drf_spectacular.utils import ( OpenApiParameter, OpenApiResponse, @@ -23,10 +22,11 @@ from rest_framework import mixins, status, viewsets from rest_framework.response import Response -from aap_eda.api import exceptions as api_exc, filters, serializers +from aap_eda.api import exceptions as api_exc, serializers from aap_eda.core import models from aap_eda.core.utils import logging_utils from aap_eda.utils import str_to_bool +from aap_eda.utils.openapi import generate_query_params from .mixins import ( CreateModelMixin, @@ -82,6 +82,9 @@ def partial_update(self, request, *args, **kwargs): description="Return a list of decision environment.", ), }, + parameters=generate_query_params( + serializers.DecisionEnvironmentSerializer() + ), ), create=extend_schema( description="Create a new decision environment.", @@ -113,8 +116,6 @@ class DecisionEnvironmentViewSet( viewsets.GenericViewSet, ): queryset = models.DecisionEnvironment.objects.order_by("id") - filter_backends = (defaultfilters.DjangoFilterBackend,) - filterset_class = filters.DecisionEnvironmentFilter def filter_queryset(self, queryset): return super().filter_queryset( diff --git a/src/aap_eda/api/views/eda_credential.py b/src/aap_eda/api/views/eda_credential.py index 88f0ffdb4..c80b30894 100644 --- a/src/aap_eda/api/views/eda_credential.py +++ b/src/aap_eda/api/views/eda_credential.py @@ -18,7 +18,6 @@ from ansible_base.rbac.models import RoleDefinition from django.db import transaction from django.forms import model_to_dict -from django_filters import rest_framework as defaultfilters from drf_spectacular.utils import ( OpenApiParameter, OpenApiResponse, @@ -29,7 +28,7 @@ from rest_framework.filters import BaseFilterBackend from rest_framework.response import Response -from aap_eda.api import exceptions, filters, serializers +from aap_eda.api import exceptions, serializers from aap_eda.api.serializers.eda_credential import get_references from aap_eda.core import models from aap_eda.core.enums import Action @@ -39,6 +38,7 @@ inputs_to_store_dict, ) from aap_eda.utils import str_to_bool +from aap_eda.utils.openapi import generate_query_params from .mixins import ( CreateModelMixin, @@ -74,11 +74,6 @@ class EdaCredentialViewSet( ): queryset = models.EdaCredential.objects.all() serializer_class = serializers.EdaCredentialSerializer - filter_backends = ( - KindFilterBackend, - defaultfilters.DjangoFilterBackend, - ) - filterset_class = filters.EdaCredentialFilter ordering_fields = ["name"] rbac_action = None @@ -142,9 +137,15 @@ def create(self, request): OpenApiParameter( "credential_type__kind", type=str, - description="Kind of CredentialType", + description="Filter by credential_type__kind", ), - ], + OpenApiParameter( + "credential_type_id", + type=int, + description="Filter by credential_type_id", + ), + ] + + generate_query_params(serializers.EdaCredentialSerializer()), responses={ status.HTTP_200_OK: OpenApiResponse( serializers.EdaCredentialSerializer(many=True), diff --git a/src/aap_eda/api/views/event_stream.py b/src/aap_eda/api/views/event_stream.py index 993f6488b..e8e555908 100644 --- a/src/aap_eda/api/views/event_stream.py +++ b/src/aap_eda/api/views/event_stream.py @@ -18,7 +18,6 @@ from ansible_base.rbac.models import RoleDefinition from django.db import transaction from django.forms import model_to_dict -from django_filters import rest_framework as defaultfilters from drf_spectacular.utils import ( OpenApiParameter, OpenApiResponse, @@ -28,10 +27,11 @@ from rest_framework.decorators import action from rest_framework.response import Response -from aap_eda.api import exceptions as api_exc, filters, serializers +from aap_eda.api import exceptions as api_exc, serializers from aap_eda.core import models from aap_eda.core.enums import ResourceType from aap_eda.core.utils import logging_utils +from aap_eda.utils.openapi import generate_query_params logger = logging.getLogger(__name__) @@ -44,8 +44,6 @@ class EventStreamViewSet( mixins.DestroyModelMixin, ): queryset = models.EventStream.objects.order_by("-created_at") - filter_backends = (defaultfilters.DjangoFilterBackend,) - filterset_class = filters.EventStreamFilter rbac_resource_type = ResourceType.EVENT_STREAM def get_serializer_class(self): @@ -132,6 +130,9 @@ def destroy(self, request, *args, **kwargs): description="Return a list of eventstreams.", ), }, + parameters=generate_query_params( + serializers.EventStreamOutSerializer() + ), ) def list(self, request, *args, **kwargs): event_streams = models.EventStream.objects.all() @@ -271,12 +272,12 @@ def partial_update(self, request, *args, **kwargs): location=OpenApiParameter.PATH, description="A unique integer value identifying this event stream.", # noqa: E501 ) - ], + ] + + generate_query_params(serializers.ActivationListSerializer()), ) @action( detail=False, queryset=models.Activation.objects.order_by("id"), - filterset_class=filters.ActivationFilter, url_path="(?P[^/.]+)/activations", ) def activations(self, request, id): diff --git a/src/aap_eda/api/views/organization.py b/src/aap_eda/api/views/organization.py index 0516fdadb..08a1f71b9 100644 --- a/src/aap_eda/api/views/organization.py +++ b/src/aap_eda/api/views/organization.py @@ -13,10 +13,10 @@ # limitations under the License. from django.conf import settings -from django_filters import rest_framework as defaultfilters from drf_spectacular.utils import ( OpenApiParameter, OpenApiResponse, + OpenApiTypes, extend_schema, extend_schema_view, ) @@ -24,9 +24,10 @@ from rest_framework.decorators import action from rest_framework.response import Response -from aap_eda.api import exceptions as api_exc, filters, serializers +from aap_eda.api import exceptions as api_exc, serializers from aap_eda.core import models from aap_eda.core.enums import Action +from aap_eda.utils.openapi import generate_query_params from .mixins import PartialUpdateOnlyModelMixin, SharedResourceViewMixin @@ -40,6 +41,14 @@ description="Return a list of organizations.", ), }, + parameters=[ + OpenApiParameter( + "resource__ansible_id", + type=OpenApiTypes.UUID, + description="Filter by resource__ansible_id", + ), + ] + + generate_query_params(serializers.OrganizationSerializer()), ), create=extend_schema( exclude=not settings.ALLOW_LOCAL_RESOURCE_MANAGEMENT, @@ -80,8 +89,6 @@ class OrganizationViewSet( SharedResourceViewMixin, ): queryset = models.Organization.objects.order_by("id") - filter_backends = (defaultfilters.DjangoFilterBackend,) - filterset_class = filters.OrganizationFilter rbac_action = None def filter_queryset(self, queryset): @@ -135,13 +142,13 @@ def destroy(self, request, *args, **kwargs): location=OpenApiParameter.PATH, description="A unique integer value identifying this organization.", # noqa: E501 ) - ], + ] + + generate_query_params(serializers.TeamSerializer()), ) @action( detail=False, methods=["get"], queryset=models.Team.objects.order_by("id"), - filterset_class=filters.OrganizationTeamFilter, rbac_action=Action.READ, url_path="(?P[^/.]+)/teams", ) diff --git a/src/aap_eda/api/views/project.py b/src/aap_eda/api/views/project.py index 04e613706..f91a75a81 100644 --- a/src/aap_eda/api/views/project.py +++ b/src/aap_eda/api/views/project.py @@ -18,7 +18,6 @@ from ansible_base.rbac.models import RoleDefinition from django.db import transaction from django.forms import model_to_dict -from django_filters.rest_framework import DjangoFilterBackend from drf_spectacular.utils import ( OpenApiResponse, extend_schema, @@ -29,10 +28,11 @@ from rest_framework.response import Response from aap_eda import tasks -from aap_eda.api import exceptions as api_exc, filters, serializers +from aap_eda.api import exceptions as api_exc, serializers from aap_eda.core import models from aap_eda.core.enums import Action from aap_eda.core.utils import logging_utils +from aap_eda.utils.openapi import generate_query_params from .mixins import RedisDependencyMixin, ResponseSerializerMixin @@ -69,6 +69,7 @@ def destroy(self, request, *args, **kwargs): description="Return a list of projects.", ), }, + parameters=generate_query_params(serializers.ProjectSerializer()), ), destroy=extend_schema( description="Delete a project by id", @@ -90,8 +91,6 @@ class ProjectViewSet( ): queryset = models.Project.objects.order_by("id") serializer_class = serializers.ProjectSerializer - filter_backends = (DjangoFilterBackend,) - filterset_class = filters.ProjectFilter rbac_action = None diff --git a/src/aap_eda/api/views/rulebook.py b/src/aap_eda/api/views/rulebook.py index f0ab81da4..7f41076c1 100644 --- a/src/aap_eda/api/views/rulebook.py +++ b/src/aap_eda/api/views/rulebook.py @@ -15,7 +15,6 @@ import yaml from django.http import JsonResponse from django.shortcuts import get_object_or_404 -from django_filters import rest_framework as defaultfilters from drf_spectacular.utils import ( OpenApiParameter, OpenApiResponse, @@ -26,7 +25,7 @@ from rest_framework.decorators import action from rest_framework.response import Response -from aap_eda.api import exceptions as api_exc, filters, serializers +from aap_eda.api import exceptions as api_exc, serializers from aap_eda.core import models from aap_eda.core.enums import Action from aap_eda.core.exceptions import ParseError @@ -52,6 +51,7 @@ description="Return a list of rulebooks.", ), }, + parameters=generate_query_params(serializers.RulebookSerializer()), ), ) class RulebookViewSet( @@ -59,8 +59,6 @@ class RulebookViewSet( ): queryset = models.Rulebook.objects.order_by("id") serializer_class = serializers.RulebookSerializer - filter_backends = (defaultfilters.DjangoFilterBackend,) - filterset_class = filters.RulebookFilter rbac_action = None diff --git a/src/aap_eda/api/views/team.py b/src/aap_eda/api/views/team.py index 22f97bfd1..17981b6c9 100644 --- a/src/aap_eda/api/views/team.py +++ b/src/aap_eda/api/views/team.py @@ -13,16 +13,16 @@ # limitations under the License. from django.conf import settings -from django_filters import rest_framework as defaultfilters from drf_spectacular.utils import ( + OpenApiParameter, OpenApiResponse, + OpenApiTypes, extend_schema, extend_schema_view, ) from rest_framework import mixins, status, viewsets from rest_framework.response import Response -from aap_eda.api.filters import TeamFilter from aap_eda.api.serializers import ( TeamCreateSerializer, TeamDetailSerializer, @@ -30,6 +30,7 @@ TeamUpdateSerializer, ) from aap_eda.core import models +from aap_eda.utils.openapi import generate_query_params from .mixins import ( CreateModelMixin, @@ -47,6 +48,14 @@ description="Return a list of teams.", ), }, + parameters=[ + OpenApiParameter( + "resource__ansible_id", + type=OpenApiTypes.UUID, + description="Filter by resource__ansible_id", + ), + ] + + generate_query_params(TeamSerializer()), ), create=extend_schema( exclude=not settings.ALLOW_LOCAL_RESOURCE_MANAGEMENT, @@ -95,8 +104,6 @@ class TeamViewSet( SharedResourceViewMixin, ): queryset = models.Team.objects.order_by("id") - filter_backends = (defaultfilters.DjangoFilterBackend,) - filterset_class = TeamFilter def filter_queryset(self, queryset): return super().filter_queryset( diff --git a/src/aap_eda/api/views/user.py b/src/aap_eda/api/views/user.py index e23d00def..fe3f83c22 100644 --- a/src/aap_eda/api/views/user.py +++ b/src/aap_eda/api/views/user.py @@ -14,7 +14,6 @@ from ansible_base.rbac.api.permissions import AnsibleBaseUserPermissions from ansible_base.rbac.policies import visible_users from django.conf import settings -from django_filters.rest_framework import DjangoFilterBackend from drf_spectacular.utils import ( OpenApiParameter, OpenApiResponse, @@ -26,8 +25,9 @@ from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet -from aap_eda.api import filters, serializers +from aap_eda.api import serializers from aap_eda.core import models +from aap_eda.utils.openapi import generate_query_params from .mixins import ( CreateModelMixin, @@ -185,6 +185,7 @@ def perform_create(self, serializer): description="Return a list of users.", ), }, + parameters=generate_query_params(serializers.UserSerializer()), ), retrieve=extend_schema( description="Retrieve a user by their id", @@ -236,9 +237,7 @@ class UserViewSet( queryset = models.User.objects.filter(is_service_account=False).order_by( "id" ) - filter_backends = (DjangoFilterBackend,) permission_classes = [AnsibleBaseUserPermissions] - filterset_class = filters.UserFilter def filter_queryset(self, qs): qs = visible_users(self.request.user, queryset=qs) diff --git a/src/aap_eda/core/models/user.py b/src/aap_eda/core/models/user.py index fb3271129..a66bbd91f 100644 --- a/src/aap_eda/core/models/user.py +++ b/src/aap_eda/core/models/user.py @@ -30,6 +30,9 @@ class User(AbstractUser): Refer to https://docs.djangoproject.com/en/4.1/topics/auth/customizing/#substituting-a-custom-user-model """ # noqa: E501 + # this blocks dab rest_filters attempting to filter over password + PASSWORD_FIELDS = ["password"] + modified_at = models.DateTimeField(auto_now=True, null=False) is_service_account = models.BooleanField(default=False) resource = AnsibleResourceField(primary_key_field="id") @@ -46,6 +49,8 @@ def summary_fields(self): class AwxToken(models.Model): + PASSWORD_FIELDS = ["token"] + user = models.ForeignKey(User, on_delete=models.CASCADE, null=False) name = models.TextField(null=False, blank=False) description = models.TextField(null=False, blank=True, default="") diff --git a/src/aap_eda/settings/core.py b/src/aap_eda/settings/core.py index 6586a5d5d..768ea5368 100644 --- a/src/aap_eda/settings/core.py +++ b/src/aap_eda/settings/core.py @@ -175,6 +175,11 @@ ANSIBLE_BASE_TEAM_MODEL = "core.Team" ANSIBLE_BASE_ORGANIZATION_MODEL = "core.Organization" +# --------------------------------------------------------- +# DJANGO ANSIBLE BASE REST FILTERS SETTINGS +# --------------------------------------------------------- +ANSIBLE_BASE_REST_FILTERS_RESERVED_NAMES = "@merge force,refs" + # Organization and object roles will come from create_initial_data ANSIBLE_BASE_ROLE_PRECREATE = {} diff --git a/src/aap_eda/utils/openapi.py b/src/aap_eda/utils/openapi.py index 41e6cf7ad..c6ef9cce6 100644 --- a/src/aap_eda/utils/openapi.py +++ b/src/aap_eda/utils/openapi.py @@ -43,8 +43,9 @@ def generate_query_params(serializer: Serializer) -> list[OpenApiParameter]: type=( OpenApiTypes.STR if isinstance(field, models.CharField) - else OpenApiTypes.NUMBER + else OpenApiTypes.INT if isinstance(field, models.IntegerField) + or "_".join([field.name, "id"]) in field_names else OpenApiTypes.DATETIME if isinstance(field, models.DateField) else OpenApiTypes.BOOL diff --git a/tests/integration/api/test_activation.py b/tests/integration/api/test_activation.py index f146eff7d..4748d3849 100644 --- a/tests/integration/api/test_activation.py +++ b/tests/integration/api/test_activation.py @@ -371,7 +371,7 @@ def test_list_activations_filter_name( activations = [default_activation, new_activation] response = admin_client.get( - f"{api_url_v1}/activations/?name={filter_name}" + f"{api_url_v1}/activations/?name__contains={filter_name}" ) assert response.status_code == status.HTTP_200_OK response_data = response.data["results"] @@ -834,7 +834,7 @@ def test_list_activation_instances_filter_name( ): instances = default_activation_instances - filter_name = "instance-1" + filter_name = default_activation_instances[0].name response = admin_client.get( f"{api_url_v1}/activations/{default_activation.id}" f"/instances/?name={filter_name}" diff --git a/tests/integration/api/test_activation_instance.py b/tests/integration/api/test_activation_instance.py index 07562e09a..f0c96dd25 100644 --- a/tests/integration/api/test_activation_instance.py +++ b/tests/integration/api/test_activation_instance.py @@ -126,7 +126,7 @@ def test_list_activation_instance_logs_filter( filter_log = "log-1" response = admin_client.get( f"{api_url_v1}/activation-instances/{instance.id}" - f"/logs/?log={filter_log}" + f"/logs/?log__contains={filter_log}" ) assert response.status_code == status.HTTP_200_OK assert len(response.data["results"]) == 1 diff --git a/tests/integration/api/test_eda_credential.py b/tests/integration/api/test_eda_credential.py index 885aeea75..b24736eda 100644 --- a/tests/integration/api/test_eda_credential.py +++ b/tests/integration/api/test_eda_credential.py @@ -410,27 +410,20 @@ def test_list_eda_credentials_with_kind_filter( assert len(response.data["results"]) == 0 response = admin_client.get( - f"{api_url_v1}/eda-credentials/?credential_type__kind=scm" - "&credential_type__kind=vault", + f"{api_url_v1}/eda-credentials/?credential_type__kind__in=scm,vault", ) assert len(response.data["results"]) == 1 - response = admin_client.get( - f"{api_url_v1}/eda-credentials/?credential_type__kind=scm" - "&credential_type__kind=registry", - ) - assert len(response.data["results"]) == 2 - response = admin_client.get( f"{api_url_v1}/eda-credentials/?" "credential_type__kind__in=scm,registry", ) assert len(response.data["results"]) == 2 - name_prefix = default_registry_credential.name[0] + name_prefix = default_registry_credential.name.split("-")[0] response = admin_client.get( - f"{api_url_v1}/eda-credentials/?credential_type__kind=scm" - f"&credential_type__kind=registry&name={name_prefix}", + f"{api_url_v1}/eda-credentials/?credential_type__kind__in=scm,registry" + f"&name__contains={name_prefix}", ) assert len(response.data["results"]) == 1 diff --git a/tests/integration/api/test_event_stream.py b/tests/integration/api/test_event_stream.py index b9fccfa62..8e0b8e41b 100644 --- a/tests/integration/api/test_event_stream.py +++ b/tests/integration/api/test_event_stream.py @@ -62,7 +62,9 @@ def test_list_event_streams_filter_name( default_event_streams: List[models.EventStream], default_vault_credential, ): - response = admin_client.get(f"{api_url_v1}/event-streams/?name=another") + response = admin_client.get( + f"{api_url_v1}/event-streams/?name__contains=another" + ) assert response.status_code == status.HTTP_200_OK assert len(response.data["results"]) == 1 assert response.data["results"][0]["name"].startswith("another") diff --git a/tests/integration/api/test_organization.py b/tests/integration/api/test_organization.py index 6c83d3345..ec9dfc398 100644 --- a/tests/integration/api/test_organization.py +++ b/tests/integration/api/test_organization.py @@ -91,8 +91,9 @@ def test_list_organizations_filter_by_ansible_id( result = response.data["results"][0] assert_organization_data(result, new_organization) + fake_ansible_id = "918b16e3-82b9-4487-8e23-df0ff50afee8" response = superuser_client.get( - f"{api_url_v1}/organizations/?resource__ansible_id=non-existent-org" + f"{api_url_v1}/organizations/?resource__ansible_id={fake_ansible_id}" ) assert response.status_code == status.HTTP_200_OK assert len(response.data["results"]) == 0 diff --git a/tests/integration/api/test_team.py b/tests/integration/api/test_team.py index 3549f085e..864103eb1 100644 --- a/tests/integration/api/test_team.py +++ b/tests/integration/api/test_team.py @@ -79,8 +79,9 @@ def test_list_teams_filter_by_ansible_id( result = response.data["results"][0] assert_team_data(result, new_team) + fake_ansible_id = "918b16e3-82b9-4487-8e23-df0ff50afee8" response = admin_client.get( - f"{api_url_v1}/teams/?resource__ansible_id=non-existent-org" + f"{api_url_v1}/teams/?resource__ansible_id={fake_ansible_id}" ) assert response.status_code == status.HTTP_200_OK assert len(response.data["results"]) == 0 diff --git a/tests/integration/api/test_user.py b/tests/integration/api/test_user.py index 2fa7cf767..d18cb5b25 100644 --- a/tests/integration/api/test_user.py +++ b/tests/integration/api/test_user.py @@ -565,8 +565,9 @@ def test_list_users_filter_by_ansible_id( }, } + fake_ansible_id = "918b16e3-82b9-4487-8e23-df0ff50afee8" response = admin_client.get( - f"{api_url_v1}/users/?resource__ansible_id=non-existent-org" + f"{api_url_v1}/users/?resource__ansible_id={fake_ansible_id}" ) assert response.status_code == status.HTTP_200_OK assert len(response.data["results"]) == 0 @@ -633,3 +634,17 @@ def test_resources_remain_after_user_delete( ) assert response.status_code == 200 assert response.data["owner"] == "" + + +@pytest.mark.django_db +def test_list_users_filter_by_password( + default_user: models.User, admin_client: APIClient +): + response = admin_client.get( + f"{api_url_v1}/users/?password__icontains={default_user.password}" + ) + assert response.status_code == status.HTTP_403_FORBIDDEN + assert ( + "Filtering on field password is not allowed." + in response.data["detail"] + ) diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 4ef22afde..751548ef7 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -130,7 +130,7 @@ class Meta: name="id", description="Filter by id", required=False, - type=OpenApiTypes.NUMBER, + type=OpenApiTypes.INT, ), OpenApiParameter( name="name", @@ -142,7 +142,7 @@ class Meta: name="age", description="Filter by age", required=False, - type=OpenApiTypes.NUMBER, + type=OpenApiTypes.INT, ), OpenApiParameter( name="created_at", @@ -160,7 +160,7 @@ class Meta: name="another_model_id", description="Filter by another_model_id", required=False, - type=OpenApiTypes.STR, + type=OpenApiTypes.INT, ), ], ),