From 6a063b4008c6adcaa3373d40218f30075a9b37c0 Mon Sep 17 00:00:00 2001 From: gromdimon Date: Thu, 25 Apr 2024 12:12:50 +0200 Subject: [PATCH 1/9] implement ajax views and add urls --- projectroles/urls.py | 10 ++++++++++ projectroles/views_ajax.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/projectroles/urls.py b/projectroles/urls.py index ebb29720..9db54b9c 100644 --- a/projectroles/urls.py +++ b/projectroles/urls.py @@ -179,6 +179,16 @@ view=views_ajax.ProjectStarringAjaxView.as_view(), name='ajax_star', ), + path( + route='ajax/sidebar/', + view=views_ajax.SidebarContentAjaxView.as_view(), + name='ajax_sidebar', + ), + path( + route='ajax/dropdown/', + view=views_ajax.UserDropdownContentAjaxView.as_view(), + name='ajax_user_dropdown', + ), path( route='ajax/user/current', view=views_ajax.CurrentUserRetrieveAjaxView.as_view(), diff --git a/projectroles/views_ajax.py b/projectroles/views_ajax.py index 4c5e03c5..908a477a 100644 --- a/projectroles/views_ajax.py +++ b/projectroles/views_ajax.py @@ -27,7 +27,7 @@ ROLE_RANKING, ) from projectroles.plugins import get_active_plugins -from projectroles.utils import get_display_name +from projectroles.utils import get_display_name, AppLinkContent from projectroles.views import ProjectAccessMixin, User from projectroles.views_api import ( SODARAPIProjectPermission, @@ -431,6 +431,33 @@ def post(self, request, *args, **kwargs): return Response(1 if value else 0, status=200) +class SidebarContentAjaxView(SODARBaseProjectAjaxView): + """Ajax view for delivering sidebar content for specific projects.""" + + permission_required = 'projectroles.view_project' + + def get(self, request, *args, **kwargs): + project = self.get_project() + # Get the content for the sidebar + app_link_content = AppLinkContent() + # # Get the current URL for highlighting the active link + # current_url = request.GET.get('current_url') + sidebar_links = app_link_content.get_project_app_links(request, project) + return JsonResponse({'links': sidebar_links}) + + +class UserDropdownContentAjaxView(SODARBaseAjaxView): + """Ajax view for delivering user dropdown content for the navbar.""" + + permission_classes = [IsAuthenticated] + + def get(self, request, *args, **kwargs): + # Get the content for the user dropdown + app_link_content = AppLinkContent() + user_dropdown_links = app_link_content.get_user_links(request) + return JsonResponse({'links': user_dropdown_links}) + + class CurrentUserRetrieveAjaxView( SODARBaseAjaxMixin, CurrentUserRetrieveAPIView ): From 3a97c546bc189d65ee0402f5525e6267c3cbe20c Mon Sep 17 00:00:00 2001 From: gromdimon Date: Thu, 25 Apr 2024 12:38:24 +0200 Subject: [PATCH 2/9] add tests --- projectroles/tests/test_views_ajax.py | 261 ++++++++++++++++++++++++++ projectroles/urls.py | 2 +- 2 files changed, 262 insertions(+), 1 deletion(-) diff --git a/projectroles/tests/test_views_ajax.py b/projectroles/tests/test_views_ajax.py index 87b97164..d09b5cc0 100644 --- a/projectroles/tests/test_views_ajax.py +++ b/projectroles/tests/test_views_ajax.py @@ -461,6 +461,267 @@ def test_project_star(self): self.assertEqual(star, False) +class TestSidebarContentAjaxView( + ProjectMixin, RoleAssignmentMixin, ViewTestBase +): + """Tests for SidebarContentAjaxView""" + + def setUp(self): + super().setUp() + self.category = self.make_project( + 'TestCategory', PROJECT_TYPE_CATEGORY, None + ) + self.owner_as_cat = self.make_assignment( + self.category, self.user, self.role_owner + ) + self.project = self.make_project( + 'TestProject', PROJECT_TYPE_PROJECT, self.category + ) + self.owner_as = self.make_assignment( + self.project, self.user, self.role_owner + ) + + def test_get(self): + """Test sidebar content retrieval""" + with self.login(self.user): + response = self.client.get( + reverse( + 'projectroles:ajax_sidebar', + kwargs={'project': self.project.sodar_uuid}, + ) + ) + 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-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': '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': 'project-roles', + 'url': f'/project/members/{self.project.sodar_uuid}', + 'label': 'Members', + 'icon': 'mdi:account-multiple', + 'active': False, + }, + { + 'name': 'project-update', + 'url': f'/project/project/update/{self.project.sodar_uuid}', + 'label': 'Update Project', + 'icon': 'mdi:lead-pencil', + 'active': False, + }, + ], + } + 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): + response = self.client.get( + reverse( + 'projectroles:ajax_sidebar', + kwargs={'project': self.project.sodar_uuid}, + ) + ) + self.assertEqual(response.status_code, 403) + + +class TestUserDropdownContentAjaxView(ViewTestBase): + """Tests for UserDropdownContentAjaxView""" + + def setUp(self): + super().setUp() + self.user = self.make_user('user') + self.user.is_superuser = True + self.user.save() + self.reg_user = self.make_user('reg_user') + + def test_regular_user(self): + """Test UserDropdownContentAjaxView with 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) + + def test_superuser(self): + """Test UserDropdownContentAjaxView with 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) + + class TestCurrentUserRetrieveAjaxView(SerializedObjectMixin, TestCase): """Tests for CurrentUserRetrieveAjaxView""" diff --git a/projectroles/urls.py b/projectroles/urls.py index 9db54b9c..aa0adaa7 100644 --- a/projectroles/urls.py +++ b/projectroles/urls.py @@ -185,7 +185,7 @@ name='ajax_sidebar', ), path( - route='ajax/dropdown/', + route='ajax/dropdown', view=views_ajax.UserDropdownContentAjaxView.as_view(), name='ajax_user_dropdown', ), From 6bae19e85b11e17d99214dc4c119164ce0f29f3a Mon Sep 17 00:00:00 2001 From: gromdimon Date: Thu, 25 Apr 2024 13:32:51 +0200 Subject: [PATCH 3/9] change the request to names for AppLinks utility class --- .../templatetags/projectroles_tags.py | 13 +- projectroles/tests/test_views_ajax.py | 164 ++++++++++++++++++ projectroles/utils.py | 94 ++++++---- projectroles/views_ajax.py | 14 +- 4 files changed, 241 insertions(+), 44 deletions(-) diff --git a/projectroles/templatetags/projectroles_tags.py b/projectroles/templatetags/projectroles_tags.py index e7ccdfb0..3f5708c4 100644 --- a/projectroles/templatetags/projectroles_tags.py +++ b/projectroles/templatetags/projectroles_tags.py @@ -274,10 +274,19 @@ def get_admin_warning(): @register.simple_tag def get_user_links(request): """Return user dropdown links""" - return app_links.get_user_links(request) + return app_links.get_user_links( + request.user, + app_name=request.resolver_match.app_name, + url_name=request.resolver_match.url_name, + ) @register.simple_tag def get_project_app_links(request, project=None): """Return sidebar links""" - return app_links.get_project_app_links(request, project) + return app_links.get_project_app_links( + request.user, + project, + app_name=request.resolver_match.app_name, + url_name=request.resolver_match.url_name, + ) diff --git a/projectroles/tests/test_views_ajax.py b/projectroles/tests/test_views_ajax.py index d09b5cc0..367bd8d9 100644 --- a/projectroles/tests/test_views_ajax.py +++ b/projectroles/tests/test_views_ajax.py @@ -546,6 +546,72 @@ def test_get(self): } self.assertEqual(response.json(), expected) + def test_get_app_links(self): + """Test sidebar content retrieval with specific app links""" + with self.login(self.user): + response = self.client.get( + reverse( + 'projectroles:ajax_sidebar', + kwargs={'project': self.project.sodar_uuid}, + ) + + '?app_name=filesfolders&url_name=list' + ) + 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-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': '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': 'project-roles', + 'url': f'/project/members/{self.project.sodar_uuid}', + 'label': 'Members', + 'icon': 'mdi:account-multiple', + 'active': False, + }, + { + 'name': 'project-update', + 'url': f'/project/project/update/{self.project.sodar_uuid}', + 'label': 'Update Project', + 'icon': 'mdi:lead-pencil', + 'active': False, + }, + ], + } + 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') @@ -721,6 +787,104 @@ def test_superuser(self): } self.assertEqual(response.json(), expected) + def test_superuser_app_links(self): + """Test UserDropdownContentAjaxView with superuser""" + with self.login(self.user): + response = self.client.get( + reverse('projectroles:ajax_user_dropdown') + + '?app_name=example_site_app&url_name=example' + ) + 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) + class TestCurrentUserRetrieveAjaxView(SerializedObjectMixin, TestCase): """Tests for CurrentUserRetrieveAjaxView""" diff --git a/projectroles/utils.py b/projectroles/utils.py index efb06cf1..821aafef 100644 --- a/projectroles/utils.py +++ b/projectroles/utils.py @@ -115,26 +115,28 @@ def get_app_names(): class AppLinkContent: """Class for generating application links for the UI""" - def _is_active_projectroles(self, request, link_names=None): + def _is_active_projectroles( + self, app_name=None, url_name=None, link_names=None + ): """Check if current URL is active under the projectroles app.""" + if not app_name or not url_name: + return False # HACK: Avoid circular import from projectroles.urls import urlpatterns - if request.resolver_match.app_name != 'projectroles': + if app_name != 'projectroles': return False - url_name = request.resolver_match.url_name + url_name = url_name return url_name in [u.name for u in urlpatterns] and ( not link_names or url_name in link_names ) - def _is_active_plugin(self, app_plugin, request): + def _is_active_plugin(self, app_plugin, app_name=None, url_name=None): """ Check if current URL is active for a specific app plugin. """ - if not getattr(request, 'resolver_match'): + if not app_name or not url_name: return False - app_name = request.resolver_match.app_name - url_name = request.resolver_match.url_name if app_plugin.name.startswith(app_name) and url_name in [ u.name for u in getattr(app_plugin, 'urls', []) ]: @@ -174,7 +176,9 @@ def _allow_project_creation(self): return False return True - def get_project_app_links(self, request, project=None): + def get_project_app_links( + self, user, project=None, app_name=None, url_name=None + ): """Return project app links based on the current project and user.""" ret = [] # Add project related links @@ -193,13 +197,17 @@ def get_project_app_links(self, request, project=None): if project.type == PROJECT_TYPE_CATEGORY else 'mdi:cube' ), - 'active': self._is_active_projectroles(request, ['detail']), + 'active': self._is_active_projectroles( + link_names=['detail'], + app_name=app_name, + url_name=url_name, + ), } ) # Add app plugins links app_plugins = get_active_plugins() for plugin in app_plugins: - if self._is_app_visible(plugin, project, request.user): + if self._is_app_visible(plugin, project, user): ret.append( { 'name': "app-plugin-" + plugin.name, @@ -209,13 +217,13 @@ def get_project_app_links(self, request, project=None): ), 'label': ' '.join(plugin.title.split(' ')), 'icon': plugin.icon, - 'active': self._is_active_plugin(plugin, request), + 'active': self._is_active_plugin( + plugin, app_name=app_name, url_name=url_name + ), } ) # Add role editing link - if request.user.has_perm( - 'projectroles.view_project_roles', project - ): + if user.has_perm('projectroles.view_project_roles', project): ret.append( { 'name': 'project-roles', @@ -226,12 +234,14 @@ def get_project_app_links(self, request, project=None): 'label': 'Members', 'icon': 'mdi:account-multiple', 'active': self._is_active_projectroles( - request, ROLE_URLS + link_names=ROLE_URLS, + app_name=app_name, + url_name=url_name, ), } ) # Add project update link - if request.user.has_perm('projectroles.update_project', project): + if user.has_perm('projectroles.update_project', project): ret.append( { 'name': 'project-update', @@ -242,7 +252,9 @@ def get_project_app_links(self, request, project=None): 'label': 'Update Project', 'icon': 'mdi:lead-pencil', 'active': self._is_active_projectroles( - request, ['update'] + link_names=['update'], + app_name=app_name, + url_name=url_name, ), } ) @@ -251,7 +263,7 @@ def get_project_app_links(self, request, project=None): if ( project and project.type == PROJECT_TYPE_CATEGORY - and request.user.has_perm('projectroles.create_project', project) + and user.has_perm('projectroles.create_project', project) and self._allow_project_creation() and not project.is_remote() ): @@ -264,12 +276,16 @@ def get_project_app_links(self, request, project=None): ), 'label': 'Create Project or Category', 'icon': 'mdi:plus-thick', - 'active': self._is_active_projectroles(request, ['create']), + 'active': self._is_active_projectroles( + link_names=['create'], + app_name=app_name, + url_name=url_name, + ), } ) elif ( getattr(settings, 'PROJECTROLES_DISABLE_CATEGORIES', False) - and request.user.is_superuser + and user.is_superuser ): ret.append( { @@ -277,16 +293,16 @@ def get_project_app_links(self, request, project=None): 'url': reverse('projectroles:create'), 'label': 'Create Project', 'icon': 'mdi:plus-thick', - 'active': self._is_active_projectroles(request, ['create']), + 'active': self._is_active_projectroles( + link_names=['create'], + app_name=app_name, + url_name=url_name, + ), } ) elif ( - ( - request.resolver_match.url_name == 'home' - or request.resolver_match.app_name == 'projectroles' - and not project - ) - and request.user.has_perm('projectroles.create_project') + (url_name == 'home' or app_name == 'projectroles' and not project) + and user.has_perm('projectroles.create_project') and self._allow_project_creation() ): ret.append( @@ -295,44 +311,46 @@ def get_project_app_links(self, request, project=None): 'url': reverse('projectroles:create'), 'label': 'Create Category', 'icon': 'mdi:plus-thick', - 'active': self._is_active_projectroles(request, ['create']), + 'active': self._is_active_projectroles( + link_names=['create'], + app_name=app_name, + url_name=url_name, + ), } ) return ret - def get_user_links(self, request): + def get_user_links(self, user, app_name=None, url_name=None): """Return user links for the user dropdown""" ret = [] # Add site-wide apps links site_apps = get_active_plugins('site_app') for app in site_apps: - if ( - not app.app_permission - or hasattr(request, 'user') - and request.user.has_perm(app.app_permission) - ): + if not app.app_permission or user.has_perm(app.app_permission): ret.append( { 'name': app.name, 'url': reverse(app.entry_point_url_id), 'label': app.title, 'icon': app.icon, - 'active': self._is_active_plugin(app, request), + 'active': self._is_active_plugin( + app, app_name=app_name, url_name=url_name + ), } ) # Add admin link - if hasattr(request, 'user') and request.user.is_superuser: + if user.is_superuser: ret.append( { 'name': 'admin', 'url': reverse('admin:index'), 'label': 'Django Admin', 'icon': 'mdi:cogs', - 'active': request.path == reverse('admin:index'), + 'active': False, } ) # Add log out / sign in link - if hasattr(request, 'user') and request.user.is_authenticated: + if user.is_authenticated: ret.append( { 'name': 'sign-out', diff --git a/projectroles/views_ajax.py b/projectroles/views_ajax.py index 908a477a..8e95d911 100644 --- a/projectroles/views_ajax.py +++ b/projectroles/views_ajax.py @@ -438,11 +438,13 @@ class SidebarContentAjaxView(SODARBaseProjectAjaxView): def get(self, request, *args, **kwargs): project = self.get_project() + app_name = request.GET.get('app_name') + url_name = request.GET.get('url_name') # Get the content for the sidebar app_link_content = AppLinkContent() - # # Get the current URL for highlighting the active link - # current_url = request.GET.get('current_url') - sidebar_links = app_link_content.get_project_app_links(request, project) + sidebar_links = app_link_content.get_project_app_links( + request.user, project, app_name=app_name, url_name=url_name + ) return JsonResponse({'links': sidebar_links}) @@ -452,9 +454,13 @@ class UserDropdownContentAjaxView(SODARBaseAjaxView): permission_classes = [IsAuthenticated] def get(self, request, *args, **kwargs): + app_name = request.GET.get('app_name') + url_name = request.GET.get('url_name') # Get the content for the user dropdown app_link_content = AppLinkContent() - user_dropdown_links = app_link_content.get_user_links(request) + user_dropdown_links = app_link_content.get_user_links( + request.user, app_name=app_name, url_name=url_name + ) return JsonResponse({'links': user_dropdown_links}) From b71557ba551dbf3953d75e8fa49820864c585cfc Mon Sep 17 00:00:00 2001 From: gromdimon Date: Thu, 25 Apr 2024 13:41:11 +0200 Subject: [PATCH 4/9] add docstrings for ajax methods --- projectroles/views_ajax.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/projectroles/views_ajax.py b/projectroles/views_ajax.py index 8e95d911..f21809f2 100644 --- a/projectroles/views_ajax.py +++ b/projectroles/views_ajax.py @@ -432,7 +432,13 @@ def post(self, request, *args, **kwargs): class SidebarContentAjaxView(SODARBaseProjectAjaxView): - """Ajax view for delivering sidebar content for specific projects.""" + """ + Ajax view for delivering sidebar content for specific projects. + All returned links are nor active by default. To get correct "active" + attribute for each of the links, you must provide the app_name and url_name + as GET parameters. Both of them refer to the current app and url name + (request.resolver_match.app_name and request.resolver_match.url_name). + """ permission_required = 'projectroles.view_project' @@ -449,7 +455,13 @@ def get(self, request, *args, **kwargs): class UserDropdownContentAjaxView(SODARBaseAjaxView): - """Ajax view for delivering user dropdown content for the navbar.""" + """ + Ajax view for delivering user dropdown content for the navbar. + All returned links are nor active by default. To get correct "active" + attribute for each of the links, you must provide the app_name and url_name + as GET parameters. Both of them refer to the current app and url name + (request.resolver_match.app_name and request.resolver_match.url_name). + """ permission_classes = [IsAuthenticated] From d7d7bb7ac05a654e8399f196a36f2811000f2546 Mon Sep 17 00:00:00 2001 From: gromdimon Date: Thu, 25 Apr 2024 17:53:00 +0200 Subject: [PATCH 5/9] wip --- projectroles/tests/test_views_ajax.py | 4 ++-- projectroles/utils.py | 12 ++++++------ projectroles/views_ajax.py | 18 ++++++++---------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/projectroles/tests/test_views_ajax.py b/projectroles/tests/test_views_ajax.py index 367bd8d9..e5be6514 100644 --- a/projectroles/tests/test_views_ajax.py +++ b/projectroles/tests/test_views_ajax.py @@ -554,7 +554,7 @@ def test_get_app_links(self): 'projectroles:ajax_sidebar', kwargs={'project': self.project.sodar_uuid}, ) - + '?app_name=filesfolders&url_name=list' + + '?app_name=filesfolders' ) self.assertEqual(response.status_code, 200) expected = { @@ -792,7 +792,7 @@ def test_superuser_app_links(self): with self.login(self.user): response = self.client.get( reverse('projectroles:ajax_user_dropdown') - + '?app_name=example_site_app&url_name=example' + + '?app_name=example_site_app&' ) self.assertEqual(response.status_code, 200) expected = { diff --git a/projectroles/utils.py b/projectroles/utils.py index 821aafef..2ff39bba 100644 --- a/projectroles/utils.py +++ b/projectroles/utils.py @@ -119,14 +119,13 @@ def _is_active_projectroles( self, app_name=None, url_name=None, link_names=None ): """Check if current URL is active under the projectroles app.""" - if not app_name or not url_name: + if not app_name and not url_name: return False # HACK: Avoid circular import from projectroles.urls import urlpatterns if app_name != 'projectroles': return False - url_name = url_name return url_name in [u.name for u in urlpatterns] and ( not link_names or url_name in link_names ) @@ -135,11 +134,12 @@ def _is_active_plugin(self, app_plugin, app_name=None, url_name=None): """ Check if current URL is active for a specific app plugin. """ - if not app_name or not url_name: + if not app_name and not url_name: return False - if app_plugin.name.startswith(app_name) and url_name in [ - u.name for u in getattr(app_plugin, 'urls', []) - ]: + if app_plugin.name.startswith(app_name) and ( + not url_name + or url_name in [u.name for u in getattr(app_plugin, 'urls', [])] + ): return True # HACK for remote site views, see issue #1336 if ( diff --git a/projectroles/views_ajax.py b/projectroles/views_ajax.py index f21809f2..44704fda 100644 --- a/projectroles/views_ajax.py +++ b/projectroles/views_ajax.py @@ -435,9 +435,9 @@ class SidebarContentAjaxView(SODARBaseProjectAjaxView): """ Ajax view for delivering sidebar content for specific projects. All returned links are nor active by default. To get correct "active" - attribute for each of the links, you must provide the app_name and url_name - as GET parameters. Both of them refer to the current app and url name - (request.resolver_match.app_name and request.resolver_match.url_name). + attribute for each of the links, you must provide the app_name as GET + parameter. The app_name refers to the current app name + (request.resolver_match.app_name). """ permission_required = 'projectroles.view_project' @@ -445,11 +445,10 @@ class SidebarContentAjaxView(SODARBaseProjectAjaxView): def get(self, request, *args, **kwargs): project = self.get_project() app_name = request.GET.get('app_name') - url_name = request.GET.get('url_name') # Get the content for the sidebar app_link_content = AppLinkContent() sidebar_links = app_link_content.get_project_app_links( - request.user, project, app_name=app_name, url_name=url_name + request.user, project, app_name=app_name ) return JsonResponse({'links': sidebar_links}) @@ -458,20 +457,19 @@ class UserDropdownContentAjaxView(SODARBaseAjaxView): """ Ajax view for delivering user dropdown content for the navbar. All returned links are nor active by default. To get correct "active" - attribute for each of the links, you must provide the app_name and url_name - as GET parameters. Both of them refer to the current app and url name - (request.resolver_match.app_name and request.resolver_match.url_name). + attribute for each of the links, you must provide the app_name as GET + parameter. The app_name refers to the current app name + (request.resolver_match.app_name). """ permission_classes = [IsAuthenticated] def get(self, request, *args, **kwargs): app_name = request.GET.get('app_name') - url_name = request.GET.get('url_name') # Get the content for the user dropdown app_link_content = AppLinkContent() user_dropdown_links = app_link_content.get_user_links( - request.user, app_name=app_name, url_name=url_name + request.user, app_name=app_name ) return JsonResponse({'links': user_dropdown_links}) From 0382583a9fa49cf580dbbf29da8e0bcbf3affaa9 Mon Sep 17 00:00:00 2001 From: gromdimon Date: Thu, 25 Apr 2024 17:56:10 +0200 Subject: [PATCH 6/9] clenup --- projectroles/tests/test_views_ajax.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectroles/tests/test_views_ajax.py b/projectroles/tests/test_views_ajax.py index e5be6514..6f039186 100644 --- a/projectroles/tests/test_views_ajax.py +++ b/projectroles/tests/test_views_ajax.py @@ -792,7 +792,7 @@ def test_superuser_app_links(self): with self.login(self.user): response = self.client.get( reverse('projectroles:ajax_user_dropdown') - + '?app_name=example_site_app&' + + '?app_name=example_site_app' ) self.assertEqual(response.status_code, 200) expected = { From d0d1df22009d78911472ef7a762197730d8b8600 Mon Sep 17 00:00:00 2001 From: gromdimon Date: Sat, 27 Apr 2024 18:15:39 +0200 Subject: [PATCH 7/9] permissions tests --- projectroles/tests/test_permissions_ajax.py | 96 ++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/projectroles/tests/test_permissions_ajax.py b/projectroles/tests/test_permissions_ajax.py index 0877115f..181fa628 100644 --- a/projectroles/tests/test_permissions_ajax.py +++ b/projectroles/tests/test_permissions_ajax.py @@ -6,7 +6,6 @@ from projectroles.models import SODAR_CONSTANTS from projectroles.tests.test_permissions import ProjectPermissionTestBase - # SODAR constants PROJECT_ROLE_OWNER = SODAR_CONSTANTS['PROJECT_ROLE_OWNER'] PROJECT_ROLE_DELEGATE = SODAR_CONSTANTS['PROJECT_ROLE_DELEGATE'] @@ -237,6 +236,101 @@ def test_get_category_anon(self): self.assert_response(self.url_cat, self.anonymous, 401, method='POST') +class TestSidebarContentAjaxView(ProjectPermissionTestBase): + """Tests for SidebarContentAjaxView permissions""" + + def setUp(self): + super().setUp() + self.url = reverse( + 'projectroles:ajax_sidebar', + kwargs={'project': self.project.sodar_uuid}, + ) + self.url_cat = reverse( + 'projectroles:ajax_sidebar', + kwargs={'project': self.category.sodar_uuid}, + ) + + def test_get(self): + """Test SidebarContentAjaxView GET""" + good_users = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_guest_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + self.user_guest, + ] + bad_users = [self.user_no_roles, self.user_finder_cat, self.anonymous] + self.assert_response(self.url, good_users, 200, method='GET') + self.assert_response(self.url, bad_users, 403, method='GET') + self.project.set_public() + self.assert_response(self.url, self.user_no_roles, 200, method='GET') + + @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) + def test_get_anon(self): + """Test GET with anonymous access""" + self.project.set_public() + self.assert_response(self.url, self.anonymous, 200, method='GET') + + def test_get_category(self): + """Test GET with category""" + good_users = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_guest_cat, + self.user_finder_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + self.user_guest, + ] + bad_users = [self.user_no_roles, self.anonymous] + self.assert_response(self.url_cat, good_users, 200, method='GET') + self.assert_response(self.url_cat, bad_users, 403, method='GET') + self.project.set_public() + self.assert_response( + self.url_cat, self.user_no_roles, 200, method='GET' + ) + + @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) + def test_get_category_anon(self): + """Test GET with category and anonymous access""" + self.project.set_public() + self.assert_response(self.url_cat, self.anonymous, 200, method='GET') + + +class TestUserDropdownContentAjaxView(ProjectPermissionTestBase): + """Tests for UserDropdownContentAjaxView permissions""" + + def setUp(self): + super().setUp() + self.url = reverse('projectroles:ajax_user_dropdown') + + def test_get(self): + """Test UserDropdownContentAjaxView GET""" + good_users = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_guest_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + self.user_guest, + self.user_finder_cat, + self.user_no_roles, + ] + bad_users = [self.anonymous] + self.assert_response(self.url, good_users, 200, method='GET') + self.assert_response(self.url, bad_users, 403, method='GET') + + class TestUserAjaxViews(ProjectPermissionTestBase): """Tests for user Ajax view permissions""" From ee131a749ef2711c722a7e94d36bfe01263e4afc Mon Sep 17 00:00:00 2001 From: gromdimon Date: Mon, 29 Apr 2024 14:56:33 +0200 Subject: [PATCH 8/9] final cleanup --- projectroles/tests/test_permissions_ajax.py | 40 ++++++++++++++------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/projectroles/tests/test_permissions_ajax.py b/projectroles/tests/test_permissions_ajax.py index 181fa628..8475dda1 100644 --- a/projectroles/tests/test_permissions_ajax.py +++ b/projectroles/tests/test_permissions_ajax.py @@ -264,16 +264,34 @@ def test_get(self): self.user_guest, ] bad_users = [self.user_no_roles, self.user_finder_cat, self.anonymous] - self.assert_response(self.url, good_users, 200, method='GET') - self.assert_response(self.url, bad_users, 403, method='GET') + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 403) self.project.set_public() - self.assert_response(self.url, self.user_no_roles, 200, method='GET') + self.assert_response(self.url, self.user_no_roles, 200) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) def test_get_anon(self): """Test GET with anonymous access""" self.project.set_public() - self.assert_response(self.url, self.anonymous, 200, method='GET') + self.assert_response(self.url, self.anonymous, 200) + + def test_get_archive(self): + """Test SidebarContentAjaxView GET with archived project""" + self.project.set_archive() + good_users = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_guest_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + self.user_guest, + ] + bad_users = [self.user_no_roles, self.user_finder_cat, self.anonymous] + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 403) def test_get_category(self): """Test GET with category""" @@ -290,18 +308,16 @@ def test_get_category(self): self.user_guest, ] bad_users = [self.user_no_roles, self.anonymous] - self.assert_response(self.url_cat, good_users, 200, method='GET') - self.assert_response(self.url_cat, bad_users, 403, method='GET') + self.assert_response(self.url_cat, good_users, 200) + self.assert_response(self.url_cat, bad_users, 403) self.project.set_public() - self.assert_response( - self.url_cat, self.user_no_roles, 200, method='GET' - ) + self.assert_response(self.url_cat, self.user_no_roles, 200) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) def test_get_category_anon(self): """Test GET with category and anonymous access""" self.project.set_public() - self.assert_response(self.url_cat, self.anonymous, 200, method='GET') + self.assert_response(self.url_cat, self.anonymous, 200) class TestUserDropdownContentAjaxView(ProjectPermissionTestBase): @@ -327,8 +343,8 @@ def test_get(self): self.user_no_roles, ] bad_users = [self.anonymous] - self.assert_response(self.url, good_users, 200, method='GET') - self.assert_response(self.url, bad_users, 403, method='GET') + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 403) class TestUserAjaxViews(ProjectPermissionTestBase): From 57065ed2db0f312cfa6a04949284ac52604c8fb4 Mon Sep 17 00:00:00 2001 From: gromdimon Date: Mon, 29 Apr 2024 15:25:52 +0200 Subject: [PATCH 9/9] minor --- projectroles/tests/test_permissions_ajax.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projectroles/tests/test_permissions_ajax.py b/projectroles/tests/test_permissions_ajax.py index 8475dda1..1034c0e4 100644 --- a/projectroles/tests/test_permissions_ajax.py +++ b/projectroles/tests/test_permissions_ajax.py @@ -263,7 +263,7 @@ def test_get(self): self.user_contributor, self.user_guest, ] - bad_users = [self.user_no_roles, self.user_finder_cat, self.anonymous] + bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] self.assert_response(self.url, good_users, 200) self.assert_response(self.url, bad_users, 403) self.project.set_public() @@ -289,7 +289,7 @@ def test_get_archive(self): self.user_contributor, self.user_guest, ] - bad_users = [self.user_no_roles, self.user_finder_cat, self.anonymous] + bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] self.assert_response(self.url, good_users, 200) self.assert_response(self.url, bad_users, 403)