diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0edfa544..bfa2e230 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -21,6 +21,7 @@ Changed - Truncate app setting values in ``remoteproject_sync.html`` (#1474) - JSON app setting value rendering in ``remoteproject_sync.html`` (#1472) - Change ``AppSettingAPI.compare_value()`` into public method (#1479) + - Refactor ``AppLinkContent`` (#1470, #1483) Fixed ----- diff --git a/projectroles/templatetags/projectroles_tags.py b/projectroles/templatetags/projectroles_tags.py index 160b7236..ce134f41 100644 --- a/projectroles/templatetags/projectroles_tags.py +++ b/projectroles/templatetags/projectroles_tags.py @@ -288,7 +288,7 @@ def get_project_app_links(request, project=None): """Return sidebar links""" if isinstance(request, str): return [] - return app_links.get_project_app_links( + return app_links.get_project_links( request.user, project, app_name=request.resolver_match.app_name, diff --git a/projectroles/tests/test_utils.py b/projectroles/tests/test_utils.py new file mode 100644 index 00000000..8af0eeee --- /dev/null +++ b/projectroles/tests/test_utils.py @@ -0,0 +1,517 @@ +"""Utils tests for the projectroles app""" + +from django.contrib.auth.models import AnonymousUser +from django.test import override_settings +from django.urls import reverse + +from projectroles.models import SODAR_CONSTANTS +from projectroles.tests.test_models import ProjectMixin, RoleAssignmentMixin +from projectroles.tests.test_views import ViewTestBase +from projectroles.utils import AppLinkContent + + +app_links = AppLinkContent() + + +# SODAR constants +PROJECT_TYPE_CATEGORY = SODAR_CONSTANTS['PROJECT_TYPE_CATEGORY'] +PROJECT_TYPE_PROJECT = SODAR_CONSTANTS['PROJECT_TYPE_PROJECT'] +SITE_MODE_TARGET = SODAR_CONSTANTS['SITE_MODE_TARGET'] + + +class TestAppLinkContent(ProjectMixin, RoleAssignmentMixin, ViewTestBase): + """Tests for AppLinkContent""" + + def setUp(self): + super().setUp() + self.user_owner = self.make_user('user_owner') + self.category = self.make_project( + 'TestCategory', PROJECT_TYPE_CATEGORY, None + ) + self.owner_as_cat = self.make_assignment( + self.category, self.user_owner, self.role_owner + ) + self.project = self.make_project( + 'TestProject', PROJECT_TYPE_PROJECT, self.category + ) + self.owner_as = self.make_assignment( + self.project, self.user_owner, self.role_owner + ) + + def test_get_project_links(self): + """Test get_project_links() with project""" + kw = {'project': self.project.sodar_uuid} + expected = [ + { + 'name': 'project-detail', + 'url': reverse('projectroles:detail', kwargs=kw), + 'label': 'Project Overview', + 'icon': 'mdi:cube', + 'active': False, + }, + { + 'name': 'app-plugin-filesfolders', + 'url': reverse('filesfolders:list', kwargs=kw), + 'label': 'Files', + 'icon': 'mdi:file', + 'active': False, + }, + { + 'name': 'app-plugin-timeline', + 'url': reverse('timeline:list_project', kwargs=kw), + 'label': 'Timeline', + 'icon': 'mdi:clock-time-eight', + 'active': False, + }, + { + 'name': 'app-plugin-bgjobs', + 'url': reverse('bgjobs:list', kwargs=kw), + 'label': 'Background Jobs', + 'icon': 'mdi:server', + 'active': False, + }, + { + 'name': 'app-plugin-example_project_app', + 'url': reverse('example_project_app:example', kwargs=kw), + 'label': 'Example Project App', + 'icon': 'mdi:rocket-launch', + 'active': False, + }, + { + 'name': 'project-roles', + 'url': reverse('projectroles:roles', kwargs=kw), + 'label': 'Members', + 'icon': 'mdi:account-multiple', + 'active': False, + }, + { + 'name': 'project-update', + 'url': reverse('projectroles:update', kwargs=kw), + 'label': 'Update Project', + 'icon': 'mdi:lead-pencil', + 'active': False, + }, + ] + self.assertEqual( + app_links.get_project_links(self.user_owner, self.project), expected + ) + + def test_get_project_links_app_name(self): + """Test get_project_links() with project and specific app plugin""" + kw = {'project': self.project.sodar_uuid} + expected = [ + { + 'name': 'project-detail', + 'url': reverse('projectroles:detail', kwargs=kw), + 'label': 'Project Overview', + 'icon': 'mdi:cube', + 'active': False, + }, + { + 'name': 'app-plugin-filesfolders', + 'url': reverse('filesfolders:list', kwargs=kw), + 'label': 'Files', + 'icon': 'mdi:file', + 'active': True, # This should be active + }, + { + 'name': 'app-plugin-timeline', + 'url': reverse('timeline:list_project', kwargs=kw), + 'label': 'Timeline', + 'icon': 'mdi:clock-time-eight', + 'active': False, + }, + { + 'name': 'app-plugin-bgjobs', + 'url': reverse('bgjobs:list', kwargs=kw), + 'label': 'Background Jobs', + 'icon': 'mdi:server', + 'active': False, + }, + { + 'name': 'app-plugin-example_project_app', + 'url': reverse('example_project_app:example', kwargs=kw), + 'label': 'Example Project App', + 'icon': 'mdi:rocket-launch', + 'active': False, + }, + { + 'name': 'project-roles', + 'url': reverse('projectroles:roles', kwargs=kw), + 'label': 'Members', + 'icon': 'mdi:account-multiple', + 'active': False, + }, + { + 'name': 'project-update', + 'url': reverse('projectroles:update', kwargs=kw), + 'label': 'Update Project', + 'icon': 'mdi:lead-pencil', + 'active': False, + }, + ] + self.assertEqual( + app_links.get_project_links( + self.user_owner, self.project, app_name='filesfolders' + ), + expected, + ) + + def test_get_project_links_category(self): + """Test get_project_links() with category""" + kw = {'project': self.category.sodar_uuid} + expected = [ + { + 'name': 'project-detail', + 'url': reverse('projectroles:detail', kwargs=kw), + 'label': 'Category Overview', + 'icon': 'mdi:rhombus-split', + 'active': False, + }, + { + 'name': 'app-plugin-timeline', + 'url': reverse('timeline:list_project', kwargs=kw), + 'label': 'Timeline', + 'icon': 'mdi:clock-time-eight', + 'active': False, + }, + { + 'name': 'project-roles', + 'url': reverse('projectroles:roles', kwargs=kw), + 'label': 'Members', + 'icon': 'mdi:account-multiple', + 'active': False, + }, + { + 'name': 'project-update', + 'url': reverse('projectroles:update', kwargs=kw), + 'label': 'Update Category', + 'icon': 'mdi:lead-pencil', + 'active': False, + }, + { + 'name': 'project-create', + 'url': reverse('projectroles:create', kwargs=kw), + 'label': 'Create Project or Category', + 'icon': 'mdi:plus-thick', + 'active': False, + }, + ] + self.assertEqual( + app_links.get_project_links(self.user_owner, self.category), + expected, + ) + + def test_get_project_links_home_superuser(self): + """Test get_project_links() with home URL as superuser""" + expected = [ + { + 'name': 'home-project-create', + 'url': reverse('projectroles:create'), + 'label': 'Create Category', + 'icon': 'mdi:plus-thick', + 'active': False, + }, + ] + self.assertEqual( + app_links.get_project_links(self.user, url_name='home'), + expected, + ) + + def test_get_project_links_home_regular_user(self): + """Test get_project_links() with home URL as regular user""" + self.assertEqual( + app_links.get_project_links(self.user_owner, url_name='home'), [] + ) + + def test_get_project_links_url_name_projectroles(self): + """Test get_project_links() with projectroles URL name""" + links = app_links.get_project_links( + self.user_owner, + self.project, + app_name='projectroles', + url_name='roles', + ) + self.assertEqual(len(links), 7) + for i in range(0, 6): + if i == 5: + self.assertEqual(links[i]['name'], 'project-roles') + self.assertEqual(links[i]['active'], True) + else: + self.assertEqual(links[i]['active'], False) + + def test_get_project_links_url_name_app_plugin(self): + """Test get_project_links() with app plugin URL name""" + links = app_links.get_project_links( + self.user_owner, + self.project, + app_name='filesfolders', + url_name='file_create', + ) + self.assertEqual(len(links), 7) + for i in range(0, 6): + if i == 1: + self.assertEqual(links[i]['name'], 'app-plugin-filesfolders') + self.assertEqual(links[i]['active'], True) + else: + self.assertEqual(links[i]['active'], False) + + def test_get_project_links_contributor(self): + """Test get_project_links() with project as contributor""" + user_new = self.make_user('user_new') + self.make_assignment(self.project, user_new, self.role_contributor) + links = app_links.get_project_links(user_new, self.project) + self.assertEqual(len(links), 6) + link_names = [link['name'] for link in links] + self.assertNotIn('project-update', link_names) + + def test_get_project_links_guest(self): + """Test get_project_links() with project as guest""" + user_new = self.make_user('user_new') + self.make_assignment(self.project, user_new, self.role_guest) + links = app_links.get_project_links(user_new, self.project) + self.assertEqual(len(links), 6) + link_names = [link['name'] for link in links] + self.assertNotIn('project-update', link_names) + + def test_get_project_links_category_contributor(self): + """Test get_project_links() with category as contributor""" + user_new = self.make_user('user_new') + self.make_assignment(self.category, user_new, self.role_contributor) + links = app_links.get_project_links(user_new, self.category) + self.assertEqual(len(links), 4) + link_names = [link['name'] for link in links] + self.assertNotIn('project-update', link_names) # NOTE: Can create + + def test_get_project_links_category_guest(self): + """Test get_project_links() with category as guest""" + user_new = self.make_user('user_new') + self.make_assignment(self.category, user_new, self.role_guest) + links = app_links.get_project_links(user_new, self.category) + self.assertEqual(len(links), 3) + link_names = [link['name'] for link in links] + self.assertNotIn('project-update', link_names) + self.assertNotIn('project-create', link_names) + + @override_settings(PROJECTROLES_HIDE_PROJECT_APPS=['filesfolders']) + def test_get_project_links_hidden_app(self): + """Test get_project_links() with hidden app""" + links = app_links.get_project_links(self.user, self.project) + self.assertEqual(len(links), 6) + link_names = [link['name'] for link in links] + self.assertNotIn('app-plugin-filesfolders', link_names) + + @override_settings( + PROJECTROLES_SITE_MODE=SITE_MODE_TARGET, + PROJECTROLES_TARGET_CREATE=False, + ) + def test_get_project_links_category_target_disallow(self): + """Test get_project_links() as target site with creation disallowed""" + links = app_links.get_project_links(self.user_owner, self.category) + self.assertEqual(len(links), 4) + link_names = [link['name'] for link in links] + self.assertNotIn('project-create', link_names) + + @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET) + def test_get_project_links_category_target_allow(self): + """Test get_project_links() as target site with creation sallowed""" + links = app_links.get_project_links(self.user_owner, self.category) + self.assertEqual(len(links), 5) + + @override_settings( + PROJECTROLES_SITE_MODE=SITE_MODE_TARGET, + PROJECTROLES_TARGET_CREATE=False, + ) + def test_get_project_links_category_target_disallow_superuser(self): + """Test get_project_links() as superuser on target site with creation disallowed""" + links = app_links.get_project_links(self.user, self.category) + self.assertEqual(len(links), 4) + link_names = [link['name'] for link in links] + self.assertNotIn('project-create', link_names) + + def test_get_user_links(self): + """Test get_user_links() as regular user""" + expected = [ + { + 'name': 'appalerts', + 'url': reverse('appalerts:list'), + 'label': 'App Alerts', + 'icon': 'mdi:alert-octagram', + 'active': False, + }, + { + 'name': 'example_site_app', + 'url': reverse('example_site_app:example'), + 'label': 'Example Site App', + 'icon': 'mdi:rocket-launch-outline', + 'active': False, + }, + { + 'name': 'timeline_site', + 'url': reverse('timeline:list_site'), + 'label': 'Site-Wide Events', + 'icon': 'mdi:clock-time-eight-outline', + 'active': False, + }, + { + 'name': 'tokens', + 'url': reverse('tokens:list'), + 'label': 'API Tokens', + 'icon': 'mdi:key-chain-variant', + 'active': False, + }, + { + 'name': 'userprofile', + 'url': reverse('userprofile:detail'), + 'label': 'User Profile', + 'icon': 'mdi:account-details', + 'active': False, + }, + { + 'name': 'sign-out', + 'url': reverse('logout'), + 'label': 'Logout', + 'icon': 'mdi:logout-variant', + 'active': False, + }, + ] + self.assertEqual(app_links.get_user_links(self.user_owner), expected) + + def test_get_user_links_superuser(self): + """Test get_user_links() as superuser""" + expected = [ + { + 'name': 'adminalerts', + 'url': reverse('adminalerts:list'), + 'label': 'Admin Alerts', + 'icon': 'mdi:alert', + 'active': False, + }, + { + 'name': 'appalerts', + 'url': reverse('appalerts:list'), + 'label': 'App Alerts', + 'icon': 'mdi:alert-octagram', + 'active': False, + }, + { + 'name': 'bgjobs_site', + 'url': reverse('bgjobs:site_list'), + 'label': 'Site Background Jobs', + 'icon': 'mdi:server', + 'active': False, + }, + { + 'name': 'example_site_app', + 'url': reverse('example_site_app:example'), + 'label': 'Example Site App', + 'icon': 'mdi:rocket-launch-outline', + 'active': False, + }, + { + 'name': 'remotesites', + 'url': reverse('projectroles:remote_sites'), + 'label': 'Remote Site Access', + 'icon': 'mdi:cloud-sync', + 'active': False, + }, + { + 'name': 'siteinfo', + 'url': reverse('siteinfo:info'), + 'label': 'Site Info', + 'icon': 'mdi:bar-chart', + 'active': False, + }, + { + 'name': 'timeline_site', + 'url': reverse('timeline:list_site'), + 'label': 'Site-Wide Events', + 'icon': 'mdi:clock-time-eight-outline', + 'active': False, + }, + { + 'name': 'timeline_site_admin', + 'url': reverse('timeline:list_admin'), + 'label': 'All Timeline Events', + 'icon': 'mdi:web-clock', + 'active': False, + }, + { + 'name': 'tokens', + 'url': reverse('tokens:list'), + 'label': 'API Tokens', + 'icon': 'mdi:key-chain-variant', + 'active': False, + }, + { + 'name': 'userprofile', + 'url': reverse('userprofile:detail'), + 'label': 'User Profile', + 'icon': 'mdi:account-details', + 'active': False, + }, + { + 'name': 'admin', + 'url': '/admin/', + 'label': 'Django Admin', + 'icon': 'mdi:cogs', + 'active': False, + }, + { + 'name': 'sign-out', + 'url': reverse('logout'), + 'label': 'Logout', + 'icon': 'mdi:logout-variant', + 'active': False, + }, + ] + self.assertEqual(app_links.get_user_links(self.user), expected) + + def test_get_user_links_app_name(self): + """Test get_user_links() with app plugin name""" + links = app_links.get_user_links(self.user_owner, app_name='tokens') + self.assertEqual(len(links), 6) + for i in range(0, 5): + if i == 3: + self.assertEqual(links[i]['name'], 'tokens') + self.assertEqual(links[i]['active'], True) + else: + self.assertEqual(links[i]['active'], False) + + def test_get_user_links_url_name(self): + """Test get_user_links() with URL name""" + links = app_links.get_user_links( + self.user_owner, app_name='tokens', url_name='create' + ) + self.assertEqual(len(links), 6) + for i in range(0, 5): + if i == 3: + self.assertEqual(links[i]['name'], 'tokens') + self.assertEqual(links[i]['active'], True) + else: + self.assertEqual(links[i]['active'], False) + + def test_get_user_links_url_name_remote(self): + """Test get_user_links() with remote sites URL name""" + links = app_links.get_user_links( + self.user, app_name='projectroles', url_name='remote_site_create' + ) + self.assertEqual(len(links), 12) + for i in range(0, 12): + if i == 4: + self.assertEqual(links[i]['name'], 'remotesites') + self.assertEqual(links[i]['active'], True) + else: + self.assertEqual(links[i]['active'], False) + + def test_get_user_links_anon(self): + """Test get_user_links() as anonymous user""" + links = app_links.get_user_links(AnonymousUser()) + self.assertEqual(len(links), 1) + self.assertEqual(links[0]['name'], 'sign-in') + + @override_settings(PROJECTROLES_KIOSK_MODE=True) + def test_get_user_links_anon_kiosk_mode(self): + """Test get_user_links() as anonymous user and kiosk mode""" + links = app_links.get_user_links(AnonymousUser()) + self.assertEqual(len(links), 0) diff --git a/projectroles/tests/test_views_ajax.py b/projectroles/tests/test_views_ajax.py index 5a56a72a..c51f9bea 100644 --- a/projectroles/tests/test_views_ajax.py +++ b/projectroles/tests/test_views_ajax.py @@ -22,8 +22,10 @@ PROJECT_TYPE_PROJECT, ) from projectroles.tests.test_views_api import SerializedObjectMixin +from projectroles.utils import AppLinkContent +app_links = AppLinkContent() app_settings = AppSettingAPI() @@ -607,7 +609,7 @@ def setUp(self): ) def test_get(self): - """Test sidebar content retrieval""" + """Test SidebarContentAjaxView GET""" with self.login(self.user): response = self.client.get( reverse( @@ -617,62 +619,27 @@ def test_get(self): ) self.assertEqual(response.status_code, 200) expected = { - 'links': [ - { - 'name': 'project-detail', - 'url': f'/project/{self.project.sodar_uuid}', - 'label': 'Project Overview', - 'icon': 'mdi:cube', - 'active': False, - }, - { - 'name': 'app-plugin-filesfolders', - 'url': f'/files/{self.project.sodar_uuid}', - 'label': 'Files', - 'icon': 'mdi:file', - 'active': False, - }, - { - 'name': 'app-plugin-timeline', - 'url': f'/timeline/{self.project.sodar_uuid}', - 'label': 'Timeline', - 'icon': 'mdi:clock-time-eight', - 'active': False, - }, - { - 'name': 'app-plugin-bgjobs', - 'url': f'/bgjobs/list/{self.project.sodar_uuid}', - 'label': 'Background Jobs', - 'icon': 'mdi:server', - 'active': False, - }, - { - 'name': 'app-plugin-example_project_app', - 'url': f'/examples/project/{self.project.sodar_uuid}', - 'label': 'Example Project App', - 'icon': 'mdi:rocket-launch', - 'active': False, - }, - { - 'name': 'project-roles', - 'url': f'/project/members/{self.project.sodar_uuid}', - 'label': 'Members', - 'icon': 'mdi:account-multiple', - 'active': False, - }, - { - 'name': 'project-update', - 'url': f'/project/update/{self.project.sodar_uuid}', - 'label': 'Update Project', - 'icon': 'mdi:lead-pencil', - 'active': False, - }, - ], + 'links': app_links.get_project_links(self.user, self.project) + } + self.assertEqual(response.json(), expected) + + def test_get_category(self): + """Test GET with category""" + with self.login(self.user): + response = self.client.get( + reverse( + 'projectroles:ajax_sidebar', + kwargs={'project': self.category.sodar_uuid}, + ) + ) + self.assertEqual(response.status_code, 200) + expected = { + 'links': app_links.get_project_links(self.user, self.category) } self.assertEqual(response.json(), expected) - def test_get_app_links(self): - """Test sidebar content retrieval with specific app links""" + def test_get_app_link(self): + """Test GET with app plugin link""" with self.login(self.user): response = self.client.get( reverse( @@ -683,71 +650,32 @@ def test_get_app_links(self): ) self.assertEqual(response.status_code, 200) expected = { - 'links': [ - { - 'name': 'project-detail', - 'url': f'/project/{self.project.sodar_uuid}', - 'label': 'Project Overview', - 'icon': 'mdi:cube', - 'active': False, - }, - { - 'name': 'app-plugin-filesfolders', - 'url': f'/files/{self.project.sodar_uuid}', - 'label': 'Files', - 'icon': 'mdi:file', - 'active': True, - }, - { - 'name': 'app-plugin-timeline', - 'url': f'/timeline/{self.project.sodar_uuid}', - 'label': 'Timeline', - 'icon': 'mdi:clock-time-eight', - 'active': False, - }, - { - 'name': 'app-plugin-bgjobs', - 'url': f'/bgjobs/list/{self.project.sodar_uuid}', - 'label': 'Background Jobs', - 'icon': 'mdi:server', - 'active': False, - }, - { - 'name': 'app-plugin-example_project_app', - 'url': f'/examples/project/{self.project.sodar_uuid}', - 'label': 'Example Project App', - 'icon': 'mdi:rocket-launch', - 'active': False, - }, - { - 'name': 'project-roles', - 'url': f'/project/members/{self.project.sodar_uuid}', - 'label': 'Members', - 'icon': 'mdi:account-multiple', - 'active': False, - }, - { - 'name': 'project-update', - 'url': f'/project/update/{self.project.sodar_uuid}', - 'label': 'Update Project', - 'icon': 'mdi:lead-pencil', - 'active': False, - }, - ], + 'links': app_links.get_project_links( + self.user, self.project, app_name='filesfolders' + ) } self.assertEqual(response.json(), expected) - def test_get_no_access(self): - """Test sidebar content retrieval with no access""" - new_user = self.make_user('new_user') - with self.login(new_user): + def test_get_url_name(self): + """Test GET with URL name""" + with self.login(self.user): response = self.client.get( reverse( 'projectroles:ajax_sidebar', kwargs={'project': self.project.sodar_uuid}, ) + + '?app_name=filesfolders&url_name=file_create' ) - self.assertEqual(response.status_code, 403) + self.assertEqual(response.status_code, 200) + expected = { + 'links': app_links.get_project_links( + self.user, + self.project, + app_name='filesfolders', + url_name='file_create', + ) + } + self.assertEqual(response.json(), expected) class TestUserDropdownContentAjaxView(ViewTestBase): @@ -760,255 +688,60 @@ def setUp(self): self.user.save() self.reg_user = self.make_user('reg_user') - def test_regular_user(self): - """Test UserDropdownContentAjaxView with regular user""" + def test_get(self): + """Test UserDropdownContentAjaxView GET as regular user""" with self.login(self.reg_user): response = self.client.get( reverse('projectroles:ajax_user_dropdown') ) self.assertEqual(response.status_code, 200) - expected = { - 'links': [ - { - 'name': 'appalerts', - 'url': '/alerts/app/list', - 'label': 'App Alerts', - 'icon': 'mdi:alert-octagram', - 'active': False, - }, - { - 'name': 'example_site_app', - 'url': '/examples/site/example', - 'label': 'Example Site App', - 'icon': 'mdi:rocket-launch-outline', - 'active': False, - }, - { - 'name': 'timeline_site', - 'url': '/timeline/site', - 'label': 'Site-Wide Events', - 'icon': 'mdi:clock-time-eight-outline', - 'active': False, - }, - { - 'name': 'tokens', - 'url': '/tokens/', - 'label': 'API Tokens', - 'icon': 'mdi:key-chain-variant', - 'active': False, - }, - { - 'name': 'userprofile', - 'url': '/user/profile', - 'label': 'User Profile', - 'icon': 'mdi:account-details', - 'active': False, - }, - { - 'name': 'sign-out', - 'url': '/logout/', - 'label': 'Logout', - 'icon': 'mdi:logout-variant', - 'active': False, - }, - ] - } - self.assertEqual(response.json(), expected) + self.assertEqual( + response.json(), {'links': app_links.get_user_links(self.reg_user)} + ) - def test_superuser(self): - """Test UserDropdownContentAjaxView with superuser""" + def test_get_superuser(self): + """Test GET as superuser""" with self.login(self.user): response = self.client.get( reverse('projectroles:ajax_user_dropdown') ) self.assertEqual(response.status_code, 200) - expected = { - 'links': [ - { - 'name': 'adminalerts', - 'url': '/alerts/adm/list', - 'label': 'Admin Alerts', - 'icon': 'mdi:alert', - 'active': False, - }, - { - 'name': 'appalerts', - 'url': '/alerts/app/list', - 'label': 'App Alerts', - 'icon': 'mdi:alert-octagram', - 'active': False, - }, - { - 'name': 'bgjobs_site', - 'url': '/bgjobs/list', - 'label': 'Site Background Jobs', - 'icon': 'mdi:server', - 'active': False, - }, - { - 'name': 'example_site_app', - 'url': '/examples/site/example', - 'label': 'Example Site App', - 'icon': 'mdi:rocket-launch-outline', - 'active': False, - }, - { - 'name': 'remotesites', - 'url': '/project/remote/sites', - 'label': 'Remote Site Access', - 'icon': 'mdi:cloud-sync', - 'active': False, - }, - { - 'name': 'siteinfo', - 'url': '/siteinfo/info', - 'label': 'Site Info', - 'icon': 'mdi:bar-chart', - 'active': False, - }, - { - 'name': 'timeline_site', - 'url': '/timeline/site', - 'label': 'Site-Wide Events', - 'icon': 'mdi:clock-time-eight-outline', - 'active': False, - }, - { - 'name': 'timeline_site_admin', - 'url': '/timeline/site/all', - 'label': 'All Timeline Events', - 'icon': 'mdi:web-clock', - 'active': False, - }, - { - 'name': 'tokens', - 'url': '/tokens/', - 'label': 'API Tokens', - 'icon': 'mdi:key-chain-variant', - 'active': False, - }, - { - 'name': 'userprofile', - 'url': '/user/profile', - 'label': 'User Profile', - 'icon': 'mdi:account-details', - 'active': False, - }, - { - 'name': 'admin', - 'url': '/admin/', - 'label': 'Django Admin', - 'icon': 'mdi:cogs', - 'active': False, - }, - { - 'name': 'sign-out', - 'url': '/logout/', - 'label': 'Logout', - 'icon': 'mdi:logout-variant', - 'active': False, - }, - ] - } - self.assertEqual(response.json(), expected) + self.assertEqual( + response.json(), {'links': app_links.get_user_links(self.user)} + ) - def test_superuser_app_links(self): - """Test UserDropdownContentAjaxView with superuser""" - with self.login(self.user): + def test_get_app_name(self): + """Test GET with app plugin name""" + with self.login(self.reg_user): + response = self.client.get( + reverse('projectroles:ajax_user_dropdown') + '?app_name=tokens' + ) + self.assertEqual(response.status_code, 200) + self.assertEqual( + response.json(), + { + 'links': app_links.get_user_links( + self.reg_user, app_name='tokens' + ) + }, + ) + + def test_get_url_name(self): + """Test GET with URL name""" + with self.login(self.reg_user): response = self.client.get( reverse('projectroles:ajax_user_dropdown') - + '?app_name=example_site_app' + + '?app_name=tokens&url_name=create' ) self.assertEqual(response.status_code, 200) - expected = { - 'links': [ - { - 'name': 'adminalerts', - 'url': '/alerts/adm/list', - 'label': 'Admin Alerts', - 'icon': 'mdi:alert', - 'active': False, - }, - { - 'name': 'appalerts', - 'url': '/alerts/app/list', - 'label': 'App Alerts', - 'icon': 'mdi:alert-octagram', - 'active': False, - }, - { - 'name': 'bgjobs_site', - 'url': '/bgjobs/list', - 'label': 'Site Background Jobs', - 'icon': 'mdi:server', - 'active': False, - }, - { - 'name': 'example_site_app', - 'url': '/examples/site/example', - 'label': 'Example Site App', - 'icon': 'mdi:rocket-launch-outline', - 'active': True, - }, - { - 'name': 'remotesites', - 'url': '/project/remote/sites', - 'label': 'Remote Site Access', - 'icon': 'mdi:cloud-sync', - 'active': False, - }, - { - 'name': 'siteinfo', - 'url': '/siteinfo/info', - 'label': 'Site Info', - 'icon': 'mdi:bar-chart', - 'active': False, - }, - { - 'name': 'timeline_site', - 'url': '/timeline/site', - 'label': 'Site-Wide Events', - 'icon': 'mdi:clock-time-eight-outline', - 'active': False, - }, - { - 'name': 'timeline_site_admin', - 'url': '/timeline/site/all', - 'label': 'All Timeline Events', - 'icon': 'mdi:web-clock', - 'active': False, - }, - { - 'name': 'tokens', - 'url': '/tokens/', - 'label': 'API Tokens', - 'icon': 'mdi:key-chain-variant', - 'active': False, - }, - { - 'name': 'userprofile', - 'url': '/user/profile', - 'label': 'User Profile', - 'icon': 'mdi:account-details', - 'active': False, - }, - { - 'name': 'admin', - 'url': '/admin/', - 'label': 'Django Admin', - 'icon': 'mdi:cogs', - 'active': False, - }, - { - 'name': 'sign-out', - 'url': '/logout/', - 'label': 'Logout', - 'icon': 'mdi:logout-variant', - 'active': False, - }, - ] - } - self.assertEqual(response.json(), expected) + self.assertEqual( + response.json(), + { + 'links': app_links.get_user_links( + self.reg_user, app_name='tokens', url_name='create' + ) + }, + ) class TestCurrentUserRetrieveAjaxView(SerializedObjectMixin, TestCase): diff --git a/projectroles/utils.py b/projectroles/utils.py index 0ad4e959..950737b2 100644 --- a/projectroles/utils.py +++ b/projectroles/utils.py @@ -115,10 +115,11 @@ def get_app_names(): class AppLinkContent: """Class for generating application links for the UI""" + @classmethod def _is_active_projectroles( - self, app_name=None, url_name=None, link_names=None + cls, app_name=None, url_name=None, link_names=None ): - """Check if current URL is active under the projectroles app.""" + """Check if current URL is active under the projectroles app""" if not app_name and not url_name: return False # HACK: Avoid circular import @@ -130,10 +131,9 @@ def _is_active_projectroles( not link_names or url_name in link_names ) - def _is_active_plugin(self, app_plugin, app_name=None, url_name=None): - """ - Check if current URL is active for a specific app plugin. - """ + @classmethod + def _is_active_plugin(cls, app_plugin, app_name=None, url_name=None): + """Check if current URL is active for a specific app plugin""" if not app_name and not url_name: return False if app_plugin.name.startswith(app_name) and ( @@ -150,8 +150,9 @@ def _is_active_plugin(self, app_plugin, app_name=None, url_name=None): return True return False - def _is_app_visible(self, plugin, project, user): - """Check if app should be visible for user in a specific project.""" + @classmethod + def _is_app_visible(cls, plugin, project, user): + """Check if app should be visible for user in a specific project""" can_view_app = user.has_perm(plugin.app_permission, project) app_hidden = False if plugin.name in getattr( @@ -166,8 +167,9 @@ def _is_app_visible(self, plugin, project, user): return True return False - def _allow_project_creation(self): - """Check whether creating a project is allowed on the site.""" + @classmethod + def _allow_project_creation(cls): + """Check whether creating a project is allowed on the site""" if ( settings.PROJECTROLES_SITE_MODE == SODAR_CONSTANTS['SITE_MODE_TARGET'] @@ -176,14 +178,17 @@ def _allow_project_creation(self): return False return True - def get_project_app_links( + def get_project_links( self, user, project=None, app_name=None, url_name=None ): - """Return project app links based on the current project and user.""" + """Return project links based on the current project and user""" ret = [] + pr_display = get_display_name(PROJECT_TYPE_PROJECT, title=True) + cat_display = get_display_name(PROJECT_TYPE_CATEGORY, title=True) + # Add project related links if project: - project_display_name = get_display_name(project.type, title=True) + current_display = get_display_name(project.type, title=True) # Add project overview link ret.append( { @@ -192,7 +197,7 @@ def get_project_app_links( 'projectroles:detail', kwargs={'project': project.sodar_uuid}, ), - 'label': f'{project_display_name} Overview', + 'label': f'{current_display} Overview', 'icon': ( 'mdi:rhombus-split' if project.type == PROJECT_TYPE_CATEGORY @@ -250,7 +255,7 @@ def get_project_app_links( 'projectroles:update', kwargs={'project': project.sodar_uuid}, ), - 'label': f'Update {project_display_name}', + 'label': f'Update {current_display}', 'icon': 'mdi:lead-pencil', 'active': self._is_active_projectroles( link_names=['update'], @@ -260,69 +265,45 @@ def get_project_app_links( } ) - # Add project and category creation links + # Add project/category creation link + allow_create = self._allow_project_creation() + create_active = self._is_active_projectroles( + link_names=['create'], app_name=app_name, url_name=url_name + ) + link = { + 'name': 'project-create', + 'icon': 'mdi:plus-thick', + 'active': create_active, + } if ( project and project.type == PROJECT_TYPE_CATEGORY and user.has_perm('projectroles.create_project', project) - and self._allow_project_creation() + and allow_create and not project.is_remote() ): - ret.append( - { - 'name': 'project-create', - 'url': reverse( - 'projectroles:create', - kwargs={'project': project.sodar_uuid}, - ), - 'label': 'Create ' - f'{get_display_name(PROJECT_TYPE_PROJECT, title=True)} ' - f'or {get_display_name(PROJECT_TYPE_CATEGORY, title=True)}', - 'icon': 'mdi:plus-thick', - 'active': self._is_active_projectroles( - link_names=['create'], - app_name=app_name, - url_name=url_name, - ), - } - ) - elif ( - getattr(settings, 'PROJECTROLES_DISABLE_CATEGORIES', False) - and user.is_superuser - ): - ret.append( - { - 'name': 'project-create', - 'url': reverse('projectroles:create'), - 'label': 'Create ' - f'{get_display_name(PROJECT_TYPE_PROJECT, title=True)}', - 'icon': 'mdi:plus-thick', - 'active': self._is_active_projectroles( - link_names=['create'], - app_name=app_name, - url_name=url_name, - ), - } + link['url'] = reverse( + 'projectroles:create', + kwargs={'project': project.sodar_uuid}, ) + link['label'] = f'Create {pr_display} or {cat_display}' + ret.append(link) elif ( (url_name == 'home' or app_name == 'projectroles' and not project) and user.has_perm('projectroles.create_project') - and self._allow_project_creation() + and allow_create ): - ret.append( - { - 'name': 'home-project-create', - 'url': reverse('projectroles:create'), - 'label': 'Create ' - f'{get_display_name(PROJECT_TYPE_CATEGORY, title=True)}', - 'icon': 'mdi:plus-thick', - 'active': self._is_active_projectroles( - link_names=['create'], - app_name=app_name, - url_name=url_name, - ), - } - ) + link['name'] = 'home-project-create' + link['url'] = reverse('projectroles:create') + link['label'] = f'Create {cat_display}' + ret.append(link) + elif ( + getattr(settings, 'PROJECTROLES_DISABLE_CATEGORIES', False) + and user.is_superuser + ): + link['url'] = reverse('projectroles:create') + link['label'] = f'Create {pr_display}' + ret.append(link) return ret def get_user_links(self, user, app_name=None, url_name=None): diff --git a/projectroles/views_ajax.py b/projectroles/views_ajax.py index adbe6c57..5268b393 100644 --- a/projectroles/views_ajax.py +++ b/projectroles/views_ajax.py @@ -484,8 +484,8 @@ def get(self, request, *args, **kwargs): project = self.get_project() app_name = request.GET.get('app_name') # Get the content for the sidebar - app_link_content = AppLinkContent() - sidebar_links = app_link_content.get_project_app_links( + app_links = AppLinkContent() + sidebar_links = app_links.get_project_links( request.user, project, app_name=app_name ) return JsonResponse({'links': sidebar_links}) @@ -515,8 +515,8 @@ class UserDropdownContentAjaxView(SODARBaseAjaxView): def get(self, request, *args, **kwargs): app_name = request.GET.get('app_name') # Get the content for the user dropdown - app_link_content = AppLinkContent() - user_dropdown_links = app_link_content.get_user_links( + app_links = AppLinkContent() + user_dropdown_links = app_links.get_user_links( request.user, app_name=app_name ) return JsonResponse({'links': user_dropdown_links})