diff --git a/CHANGES/2733.bugfix b/CHANGES/2733.bugfix new file mode 100644 index 0000000000..bc6f0f59c2 --- /dev/null +++ b/CHANGES/2733.bugfix @@ -0,0 +1 @@ +Fixed server error 500 on ``/api/v1/namespaces`` if browsable api is enabled diff --git a/galaxy_ng/app/api/v1/viewsets/roles.py b/galaxy_ng/app/api/v1/viewsets/roles.py index 09aa914104..0992f593ee 100644 --- a/galaxy_ng/app/api/v1/viewsets/roles.py +++ b/galaxy_ng/app/api/v1/viewsets/roles.py @@ -159,6 +159,7 @@ class LegacyRoleVersionsViewSet(viewsets.GenericViewSet, mixins.RetrieveModelMix permission_classes = [LegacyAccessPolicy] authentication_classes = GALAXY_AUTHENTICATION_CLASSES serializer_class = LegacyRoleVersionsSerializer + queryset = LegacyRole.objects.all() def get_object(self): return get_object_or_404(LegacyRole, id=self.kwargs["pk"]) diff --git a/galaxy_ng/app/dynaconf_hooks.py b/galaxy_ng/app/dynaconf_hooks.py index 5455793cca..abf92c87db 100755 --- a/galaxy_ng/app/dynaconf_hooks.py +++ b/galaxy_ng/app/dynaconf_hooks.py @@ -2,6 +2,7 @@ import ldap import pkg_resources import os +import re from typing import Any, Dict, List from django_auth_ldap.config import LDAPSearch from dynaconf import Dynaconf, Validator @@ -29,6 +30,7 @@ def post(settings: Dynaconf) -> Dict[str, Any]: data.update(configure_cors(settings)) data.update(configure_pulp_ansible(settings)) data.update(configure_authentication_backends(settings)) + data.update(configure_renderers(settings)) data.update(configure_password_validators(settings)) data.update(configure_api_base_path(settings)) data.update(configure_legacy_roles(settings)) @@ -533,6 +535,20 @@ def configure_authentication_backends(settings: Dynaconf) -> Dict[str, Any]: return data +def configure_renderers(settings) -> Dict[str, Any]: + """ + Add CustomBrowsableAPI only for community (galaxy.ansible.com, galaxy-stage, galaxy-dev)" + """ + if re.search( + r'galaxy(-dev|-stage)*.ansible.com', settings.get('CONTENT_ORIGIN', "") + ): + value = settings.get("REST_FRAMEWORK__DEFAULT_RENDERER_CLASSES", []) + value.append('galaxy_ng.app.renderers.CustomBrowsableAPIRenderer') + return {"REST_FRAMEWORK__DEFAULT_RENDERER_CLASSES": value} + + return {} + + def configure_legacy_roles(settings: Dynaconf) -> Dict[str, Any]: """Set the feature flag for legacy roles from the setting""" data = {} diff --git a/galaxy_ng/app/renderers.py b/galaxy_ng/app/renderers.py new file mode 100644 index 0000000000..be85e000b3 --- /dev/null +++ b/galaxy_ng/app/renderers.py @@ -0,0 +1,11 @@ +from rest_framework.renderers import BrowsableAPIRenderer + + +class CustomBrowsableAPIRenderer(BrowsableAPIRenderer): + """Overrides the standard DRF Browsable API renderer.""" + + def show_form_for_method(self, view, method, request, obj): + """Display forms only for superuser.""" + if request.user.is_superuser: + return super().show_form_for_method(view, method, request, obj) + return False diff --git a/galaxy_ng/app/settings.py b/galaxy_ng/app/settings.py index 1afaf24895..533ac75801 100644 --- a/galaxy_ng/app/settings.py +++ b/galaxy_ng/app/settings.py @@ -64,6 +64,11 @@ "galaxy_ng.app.access_control.access_policy.AccessPolicyBase", ) +REST_FRAMEWORK__DEFAULT_RENDERER_CLASSES = [ + 'rest_framework.renderers.JSONRenderer', + 'galaxy_ng.app.renderers.CustomBrowsableAPIRenderer' +] + # Settings for insights mode # GALAXY_AUTHENTICATION_CLASSES = ["galaxy_ng.app.auth.auth.RHIdentityAuthentication"] diff --git a/galaxy_ng/tests/integration/api/test_community.py b/galaxy_ng/tests/integration/api/test_community.py index 9aceb58f3c..6b80625400 100644 --- a/galaxy_ng/tests/integration/api/test_community.py +++ b/galaxy_ng/tests/integration/api/test_community.py @@ -5,6 +5,8 @@ import pytest import subprocess +from ansible.errors import AnsibleError + from urllib.parse import urlparse from ..utils import ( @@ -768,3 +770,42 @@ def test_legacy_roles_ordering(ansible_config): resp = api_client('/api/v1/roles/?order_by=-name') sorted_names = [r["name"] for r in resp["results"]] assert sorted_names == sorted(names, reverse=True) + + +@pytest.mark.deployment_community +def test_v1_role_versions(ansible_config): + """ + Test that role versions endpoint doesn't fail on missing queryset + with enabled browsable api format. + """ + + config = ansible_config("admin") + api_client = get_client( + config=config, + request_token=False, + require_auth=True + ) + + # clean all roles + clean_all_roles(ansible_config) + + # start the sync + pargs = json.dumps({"github_user": "geerlingguy", "role_name": "ansible"}).encode('utf-8') + resp = api_client('/api/v1/sync/', method='POST', args=pargs) + assert isinstance(resp, dict) + assert resp.get('task') is not None + wait_for_v1_task(resp=resp, api_client=api_client) + + resp = api_client('/api/v1/roles/?username=geerlingguy&name=ansible') + assert resp['count'] == 1 + + id = resp["results"][0]["id"] + versions = resp["results"][0]["summary_fields"]["versions"] + + resp = api_client(f'/api/v1/roles/{id}/versions') + assert len(versions) == resp["count"] + + with pytest.raises(AnsibleError) as html: + api_client(f"v1/roles/{id}/versions", headers={"Accept": "text/html"}) + assert not isinstance(html.value, dict) + assert "results" in str(html.value) diff --git a/galaxy_ng/tests/integration/community/test_v1_api.py b/galaxy_ng/tests/integration/community/test_v1_api.py index 50a38b9b9b..dac5e2c5d9 100644 --- a/galaxy_ng/tests/integration/community/test_v1_api.py +++ b/galaxy_ng/tests/integration/community/test_v1_api.py @@ -3,6 +3,8 @@ import pytest +from ansible.errors import AnsibleError + from ..utils import ( ansible_galaxy, get_client, @@ -12,7 +14,6 @@ cleanup_social_user, ) - pytestmark = pytest.mark.qa # noqa: F821 @@ -118,3 +119,50 @@ def test_v1_users_filter(ansible_config): assert resp["count"] == 0 cleanup_social_user(github_user, ansible_config) + + +@pytest.mark.deployment_community +def test_custom_browsable_format(ansible_config): + """" Test endpoints works with enabled browsable api """ + + # test as a admin + config = ansible_config("admin") + api_client = get_client( + config=config, + request_token=False, + require_auth=True, + ) + + resp = api_client("v1/namespaces") + assert isinstance(resp, dict) + assert "results" in resp + + resp = api_client("v1/namespaces?format=json") + assert isinstance(resp, dict) + assert "results" in resp + + with pytest.raises(AnsibleError) as html: + api_client("v1/namespaces", headers={"Accept": "text/html"}) + assert not isinstance(html.value, dict) + assert "results" in str(html.value) + + # test as a basic user + config = ansible_config("basic_user") + api_client = get_client( + config=config, + request_token=False, + require_auth=True, + ) + + resp = api_client("v1/namespaces") + assert isinstance(resp, dict) + assert "results" in resp + + resp = api_client("v1/namespaces?format=json") + assert isinstance(resp, dict) + assert "results" in resp + + with pytest.raises(AnsibleError) as html: + api_client("v1/namespaces", headers={"Accept": "text/html"}) + assert not isinstance(html.value, dict) + assert "results" in str(html.value)