From 5969122d198d664aeaa4bb6f3ae62389b494003c Mon Sep 17 00:00:00 2001 From: Mikko Nieminen Date: Wed, 22 Nov 2023 15:09:13 +0100 Subject: [PATCH] cleanup for v0.13.3 release (#1296) --- .github/ISSUE_TEMPLATE/release_cleanup.md | 2 +- CHANGELOG.rst | 1 + adminalerts/plugins.py | 4 +- appalerts/plugins.py | 6 +- bgjobs/plugins.py | 9 +- config/settings/base.py | 3 + docs/source/app_projectroles_settings.rst | 37 +- docs/source/app_projectroles_usage.rst | 8 + docs/source/dev_core_guide.rst | 31 + docs/source/dev_resource.rst | 43 +- docs/source/major_changes.rst | 1 + example_backend_app/plugins.py | 2 +- example_project_app/plugins.py | 4 +- example_site_app/plugins.py | 4 +- filesfolders/forms.py | 19 - filesfolders/plugins.py | 6 +- filesfolders/views.py | 88 +- projectroles/app_settings.py | 6 +- projectroles/plugins.py | 34 +- projectroles/tests/test_views.py | 1953 +++++++-------------- projectroles/views.py | 8 +- siteinfo/plugins.py | 4 +- sodarcache/plugins.py | 2 +- timeline/plugins.py | 14 +- tokens/plugins.py | 5 + userprofile/plugins.py | 4 +- 26 files changed, 806 insertions(+), 1492 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release_cleanup.md b/.github/ISSUE_TEMPLATE/release_cleanup.md index 61b13a3e..63cf1b86 100644 --- a/.github/ISSUE_TEMPLATE/release_cleanup.md +++ b/.github/ISSUE_TEMPLATE/release_cleanup.md @@ -11,7 +11,7 @@ assignees: 'mikkonie' TBA -## Issues to add to CHANGELOG +## Issues to Add in CHANGELOG TBA diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f897403f..26b499e1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,7 @@ Added - **General** - LDAP settings for TLS and user filter (#1340) + - ``LDAP_DEBUG`` Django setting - **Projectroles** - ``_project_badge.html`` template (#1300) - ``InvalidFormMixin`` helper mixin (#1310) diff --git a/adminalerts/plugins.py b/adminalerts/plugins.py index 08a20ddd..22d941e8 100644 --- a/adminalerts/plugins.py +++ b/adminalerts/plugins.py @@ -13,13 +13,13 @@ class SiteAppPlugin(SiteAppPluginPoint): """Projectroles plugin for registering the app""" - #: Name (slug-safe, used in URLs) + #: Name (used as plugin ID) name = 'adminalerts' #: Title (used in templates) title = 'Admin Alerts' - #: App URLs (will be included in settings by djangoplugins) + #: UI URLs urls = urlpatterns #: Iconify icon diff --git a/appalerts/plugins.py b/appalerts/plugins.py index 16bc2c91..643fa304 100644 --- a/appalerts/plugins.py +++ b/appalerts/plugins.py @@ -11,13 +11,13 @@ class SiteAppPlugin(SiteAppPluginPoint): """Site plugin for application alerts""" - #: Name (slug-safe, used in URLs) + #: Name (used as plugin ID) name = 'appalerts' #: Title (used in templates) title = 'App Alerts' - #: App URLs (will be included in settings by djangoplugins) + #: UI URLs urls = urlpatterns #: Iconify icon @@ -36,7 +36,7 @@ class SiteAppPlugin(SiteAppPluginPoint): class BackendPlugin(BackendPluginPoint): """Backend plugin for application alerts""" - #: Name (slug-safe, used in URLs) + #: Name (used as plugin ID) name = 'appalerts_backend' #: Title (used in templates) diff --git a/bgjobs/plugins.py b/bgjobs/plugins.py index 05a4f81b..fd5569e0 100644 --- a/bgjobs/plugins.py +++ b/bgjobs/plugins.py @@ -11,8 +11,13 @@ class ProjectAppPlugin(ProjectAppPluginPoint): """Plugin for registering app with Projectroles""" + #: Name (used as plugin ID) name = 'bgjobs' + + #: Title (used in templates) title = 'Background Jobs' + + #: UI URLs urls = urls_ui_project #: Iconify icon @@ -61,13 +66,13 @@ class BackgroundJobsPluginPoint(PluginPoint): class SiteAppPlugin(SiteAppPluginPoint): """Projectroles plugin for registering the app""" - #: Name (slug-safe, used in URLs) + #: Name (used as plugin ID) name = 'bgjobs_site' #: Title (used in templates) title = 'Site Background Jobs' - #: App URLs (will be included in settings by djangoplugins) + #: UI URLs urls = urls_ui_site #: Iconify icon diff --git a/config/settings/base.py b/config/settings/base.py index 9e7eebfb..4210ab1b 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -339,6 +339,7 @@ # Enable LDAP if configured ENABLE_LDAP = env.bool('ENABLE_LDAP', False) ENABLE_LDAP_SECONDARY = env.bool('ENABLE_LDAP_SECONDARY', False) +LDAP_DEBUG = env.bool('LDAP_DEBUG', False) # Alternative domains for detecting LDAP access by email address LDAP_ALT_DOMAINS = env.list('LDAP_ALT_DOMAINS', None, []) @@ -348,6 +349,8 @@ import ldap from django_auth_ldap.config import LDAPSearch + if LDAP_DEBUG: + ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255) # Default values LDAP_DEFAULT_CONN_OPTIONS = {ldap.OPT_REFERRALS: 0} LDAP_DEFAULT_ATTR_MAP = { diff --git a/docs/source/app_projectroles_settings.rst b/docs/source/app_projectroles_settings.rst index ecb300f2..27341534 100644 --- a/docs/source/app_projectroles_settings.rst +++ b/docs/source/app_projectroles_settings.rst @@ -379,19 +379,26 @@ This part of the setup is **optional**. If only using one LDAP/AD server, you can leave the "secondary LDAP server" values unset. +.. hint:: + + To help debug possible connection problems with your LDAP server(s), set + ``LDAP_DEBUG=1`` in your environment variables. + .. code-block:: python ENABLE_LDAP = env.bool('ENABLE_LDAP', False) ENABLE_LDAP_SECONDARY = env.bool('ENABLE_LDAP_SECONDARY', False) + LDAP_DEBUG = env.bool('LDAP_DEBUG', False) if ENABLE_LDAP: import itertools import ldap from django_auth_ldap.config import LDAPSearch + if LDAP_DEBUG: + ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255) # Default values LDAP_DEFAULT_CONN_OPTIONS = {ldap.OPT_REFERRALS: 0} - LDAP_DEFAULT_FILTERSTR = '(sAMAccountName=%(user)s)' LDAP_DEFAULT_ATTR_MAP = { 'first_name': 'givenName', 'last_name': 'sn', @@ -402,12 +409,22 @@ This part of the setup is **optional**. AUTH_LDAP_SERVER_URI = env.str('AUTH_LDAP_SERVER_URI', None) AUTH_LDAP_BIND_DN = env.str('AUTH_LDAP_BIND_DN', None) AUTH_LDAP_BIND_PASSWORD = env.str('AUTH_LDAP_BIND_PASSWORD', None) - AUTH_LDAP_CONNECTION_OPTIONS = LDAP_DEFAULT_CONN_OPTIONS + AUTH_LDAP_START_TLS = env.str('AUTH_LDAP_START_TLS', False) + AUTH_LDAP_CA_CERT_FILE = env.str('AUTH_LDAP_CA_CERT_FILE', None) + AUTH_LDAP_CONNECTION_OPTIONS = {**LDAP_DEFAULT_CONN_OPTIONS} + if AUTH_LDAP_CA_CERT_FILE is not None: + AUTH_LDAP_CONNECTION_OPTIONS[ + ldap.OPT_X_TLS_CACERTFILE + ] = AUTH_LDAP_CA_CERT_FILE + AUTH_LDAP_CONNECTION_OPTIONS[ldap.OPT_X_TLS_NEWCTX] = 0 + AUTH_LDAP_USER_FILTER = env.str( + 'AUTH_LDAP_USER_FILTER', '(sAMAccountName=%(user)s)' + ) AUTH_LDAP_USER_SEARCH = LDAPSearch( env.str('AUTH_LDAP_USER_SEARCH_BASE', None), ldap.SCOPE_SUBTREE, - LDAP_DEFAULT_FILTERSTR, + AUTH_LDAP_USER_FILTER, ) AUTH_LDAP_USER_ATTR_MAP = LDAP_DEFAULT_ATTR_MAP AUTH_LDAP_USERNAME_DOMAIN = env.str('AUTH_LDAP_USERNAME_DOMAIN', None) @@ -427,12 +444,22 @@ This part of the setup is **optional**. AUTH_LDAP2_SERVER_URI = env.str('AUTH_LDAP2_SERVER_URI', None) AUTH_LDAP2_BIND_DN = env.str('AUTH_LDAP2_BIND_DN', None) AUTH_LDAP2_BIND_PASSWORD = env.str('AUTH_LDAP2_BIND_PASSWORD', None) - AUTH_LDAP2_CONNECTION_OPTIONS = LDAP_DEFAULT_CONN_OPTIONS + AUTH_LDAP2_START_TLS = env.str('AUTH_LDAP2_START_TLS', False) + AUTH_LDAP2_CA_CERT_FILE = env.str('AUTH_LDAP2_CA_CERT_FILE', None) + AUTH_LDAP2_CONNECTION_OPTIONS = {**LDAP_DEFAULT_CONN_OPTIONS} + if AUTH_LDAP2_CA_CERT_FILE is not None: + AUTH_LDAP2_CONNECTION_OPTIONS[ + ldap.OPT_X_TLS_CACERTFILE + ] = AUTH_LDAP2_CA_CERT_FILE + AUTH_LDAP2_CONNECTION_OPTIONS[ldap.OPT_X_TLS_NEWCTX] = 0 + AUTH_LDAP2_USER_FILTER = env.str( + 'AUTH_LDAP2_USER_FILTER', '(sAMAccountName=%(user)s)' + ) AUTH_LDAP2_USER_SEARCH = LDAPSearch( env.str('AUTH_LDAP2_USER_SEARCH_BASE', None), ldap.SCOPE_SUBTREE, - LDAP_DEFAULT_FILTERSTR, + AUTH_LDAP2_USER_FILTER, ) AUTH_LDAP2_USER_ATTR_MAP = LDAP_DEFAULT_ATTR_MAP AUTH_LDAP2_USERNAME_DOMAIN = env.str('AUTH_LDAP2_USERNAME_DOMAIN') diff --git a/docs/source/app_projectroles_usage.rst b/docs/source/app_projectroles_usage.rst index 78eaeca5..592a8e64 100644 --- a/docs/source/app_projectroles_usage.rst +++ b/docs/source/app_projectroles_usage.rst @@ -397,6 +397,14 @@ Revoked Access reading project metadata (title, description..) without member roles are implemented in the data model and backend, but currently disabled in the UI. +.. note:: + + If synchronizing a project with *public guest access* enabled, this setting + is **not** automatically set on the target site. If you wish to make the + project also publicly accessible on the target site, it needs to be + explicitly set by the project owner, delegate or a superuser in the + :guilabel:`Update Project` form. + Once desired access to specific projects has been granted and confirmed, the target site will sync the data by sending a request to the source site. diff --git a/docs/source/dev_core_guide.rst b/docs/source/dev_core_guide.rst index 2903fd27..3d52db1b 100644 --- a/docs/source/dev_core_guide.rst +++ b/docs/source/dev_core_guide.rst @@ -56,6 +56,11 @@ your commit(s). Code Conventions ================ +This section lists code conventions when contributing to the SODAR Core project. + +General Conventions +------------------- + The following conventions should be adhered to in SODAR Core development: - Limit line length to 80 characters. @@ -70,6 +75,32 @@ The following conventions should be adhered to in SODAR Core development: * Possibility to expand the entire project into using type hints will be looked into. +Module Imports +-------------- + +Import order of python modules is as follows. Use alphabetical order within each +of the groups. + +- Full imports of Python standard library or general purpose packages + * E.g. ``import io`` +- Submodule imports of Python standard library or general purpose packages + * E.g. ``from math import ceil`` +- Django imports + * E.g. ``from django.conf import settings`` +- Imports from packages extending Django + * Such as ``rules``, ``django-rest-framework`` or Django testing specific + requirements + * E.g. ``from rest_framework import serializers`` +- SODAR Core ``projectroles`` imports + * E.g. ``from projectroles.plugins import get_backend_api`` + * Prefix these with ``# Projectroles dependency`` + * Hardcoded imports from other SODAR Core apps should be avoided, use + ``get_backend_api()`` instead. +- Imports from within the current app + * E.g. ``from yourapp.forms import YourForm`` + * For consistency and clarity, give the full module path for imports. E.g. + ``from yourapp.views import x`` instead of ``from views import x`` + .. _dev_core_guide_template: diff --git a/docs/source/dev_resource.rst b/docs/source/dev_resource.rst index dcad6358..c0b1e8fe 100644 --- a/docs/source/dev_resource.rst +++ b/docs/source/dev_resource.rst @@ -80,15 +80,22 @@ arguments in its init function, with the exception of ``queryset``, The init function also takes new arguments which are specified below: -- ``scope``: Scope of users to include (string) - * ``all``: All users on the site - * ``project``: Limit search to users in given project - * ``project_exclude`` Exclude existing users of given project -- ``project``: Project object or project UUID string (optional) -- ``exclude``: List of User objects or User UUIDs to exclude (optional) -- ``forward``: Parameters to forward to autocomplete view (optional) -- ``url``: Autocomplete ajax class override (optional) -- ``widget_class``: Widget class override (optional) +``scope`` + Scope of users to include (string). Options: + + - ``all``: All users on the site + - ``project``: Limit search to users in given project + - ``project_exclude`` Exclude existing users of given project +``project`` + Project object or project UUID string (optional) +``exclude`` + List of User objects or User UUIDs to exclude (optional) +``forward`` + Parameters to forward to autocomplete view (optional) +``url`` + Autocomplete ajax class override (optional) +``widget_class`` + Widget class override (optional) Below is an example of the classes usage. Note that you can also define the field as a form class member, but the ``project`` or ``exclude`` values are @@ -477,13 +484,17 @@ UI test classes found in ``projectroles.tests.test_ui``. Default values for these settings can be found in ``config/settings/test.py``. The settings are as follows: -- ``PROJECTROLES_TEST_UI_CHROME_OPTIONS``: Options for Chrome through Selenium. - Can be used to e.g. enable/disable headless testing mode. -- ``PROJECTROLES_TEST_UI_WINDOW_SIZE``: Custom browser window size. -- ``PROJECTROLES_TEST_UI_WAIT_TIME``: Maximum wait time for UI test operations -- ``PROJECTROLES_TEST_UI_LEGACY_LOGIN``: If set ``True``, use the legacy UI - login and redirect function for testing with different users. This can be used - if e.g. issues with cookie-based logins are encountered. +``PROJECTROLES_TEST_UI_CHROME_OPTIONS`` + Options for Chrome through Selenium. Can be used to e.g. enable/disable + headless testing mode. +``PROJECTROLES_TEST_UI_WINDOW_SIZE`` + Custom browser window size. +``PROJECTROLES_TEST_UI_WAIT_TIME`` + Maximum wait time for UI test operations +``PROJECTROLES_TEST_UI_LEGACY_LOGIN`` + If set ``True``, use the legacy UI login and redirect function for testing + with different users. This can be used if e.g. issues with cookie-based + logins are encountered. Base Test Classes and Helpers ----------------------------- diff --git a/docs/source/major_changes.rst b/docs/source/major_changes.rst index 857202c2..9e6aa3c7 100644 --- a/docs/source/major_changes.rst +++ b/docs/source/major_changes.rst @@ -22,6 +22,7 @@ v0.13.3 (WIP) - Fix hidden JSON project setting reset on non-superuser project update - Fix custom app setting validation calls in forms - Fix multiple remote sync app settings updating issues +- Fix request object not provided to perform_project_modify() on create - General bug fixes and minor updates diff --git a/example_backend_app/plugins.py b/example_backend_app/plugins.py index a4081500..3355dbf9 100644 --- a/example_backend_app/plugins.py +++ b/example_backend_app/plugins.py @@ -7,7 +7,7 @@ class BackendPlugin(BackendPluginPoint): """Plugin for registering backend app with Projectroles""" - #: Name (slug-safe, used in URLs) + #: Name (used as plugin ID) name = 'example_backend_app' #: Title (used in templates) diff --git a/example_project_app/plugins.py b/example_project_app/plugins.py index 050d45c3..749a6608 100644 --- a/example_project_app/plugins.py +++ b/example_project_app/plugins.py @@ -36,13 +36,13 @@ class ProjectAppPlugin(ProjectModifyPluginMixin, ProjectAppPluginPoint): # Properties required by django-plugins ------------------------------ - #: Name (slug-safe) + #: Name (used as plugin ID) name = 'example_project_app' #: Title (used in templates) title = 'Example Project App' - #: App URLs (will be included in settings by djangoplugins) + #: UI URLs urls = urlpatterns # Properties defined in ProjectAppPluginPoint ----------------------- diff --git a/example_site_app/plugins.py b/example_site_app/plugins.py index 293ac79c..53018259 100644 --- a/example_site_app/plugins.py +++ b/example_site_app/plugins.py @@ -7,13 +7,13 @@ class SiteAppPlugin(SiteAppPluginPoint): """Projectroles plugin for registering the app""" - #: Name (slug-safe, used in URLs) + #: Name (used as plugin ID) name = 'example_site_app' #: Title (used in templates) title = 'Example Site App' - #: App URLs (will be included in settings by djangoplugins) + #: UI URLs urls = urlpatterns #: Iconify icon diff --git a/filesfolders/forms.py b/filesfolders/forms.py index 9d180517..e62f8b58 100644 --- a/filesfolders/forms.py +++ b/filesfolders/forms.py @@ -36,11 +36,9 @@ def __init__( ): """Override for form initialization""" super().__init__(*args, **kwargs) - self.current_user = None self.project = None self.folder = None - # Get current user for checking permissions for form items if current_user: self.current_user = current_user @@ -49,7 +47,6 @@ def __init__( self.project = self.folder.project elif project: self.project = Project.objects.get(sodar_uuid=project) - # Modify ModelChoiceFields to use sodar_uuid self.fields['folder'].to_field_name = 'sodar_uuid' @@ -122,7 +119,6 @@ def clean(self): old_folder = Folder.objects.get(pk=self.instance.pk) except Folder.DoesNotExist: pass - if old_folder and ( old_folder.name != self.cleaned_data['name'] or old_folder.folder != self.cleaned_data['folder'] @@ -136,7 +132,6 @@ def clean(self): self.add_error('name', 'Folder already exists') except Folder.DoesNotExist: pass - return self.cleaned_data def save(self, *args, **kwargs): @@ -275,13 +270,11 @@ def clean(self): # Ensure max archive size is not exceeded if not self._check_size(size, MAX_ARCHIVE_SIZE): return self.cleaned_data - try: zip_file = ZipFile(file) except Exception as ex: self.add_error('file', 'Unable to open zip file: {}'.format(ex)) return self.cleaned_data - archive_files = [f for f in zip_file.infolist() if not f.is_dir()] if len(archive_files) == 0: self.add_error( @@ -296,13 +289,11 @@ def clean(self): # Check if any of the files exist path_split = f.filename.split('/') check_folder = folder - for p in path_split[:-1]: # Advance in path check_folder = Folder.objects.filter( name=p, folder=check_folder ).first() - # Once reached the correct path, check if file exists if File.objects.filter( name=path_split[-1], folder=check_folder @@ -331,7 +322,6 @@ def clean(self): folder=self.instance.folder, name=self.instance.name, ).first() - if ( old_file and self.instance.name != str(file) @@ -340,7 +330,6 @@ def clean(self): ).first() ): self.add_error('file', 'File already exists') - # Moving: # If moving, ensure an identical file doesn't exist in the # target folder @@ -357,13 +346,11 @@ def clean(self): 'folder', 'File with identical name already exists in folder', ) - return self.cleaned_data def save(self, *args, **kwargs): """Override of form saving function""" obj = super().save(commit=False) - # Creation if not self.instance.pk: obj.name = obj.file.name @@ -372,7 +359,6 @@ def save(self, *args, **kwargs): if self.folder: obj.folder = self.folder obj.secret = build_secret() # Secret string created here - # Updating else: old_file = File.objects.get(pk=self.instance.pk) @@ -392,7 +378,6 @@ def save(self, *args, **kwargs): obj.public_url = self.instance.public_url else: obj.public_url = False - obj.save() return obj @@ -415,7 +400,6 @@ def __init__( *args, **kwargs ) - # Creation if not self.instance.pk: # Don't allow changing folder if we are creating a new object @@ -448,7 +432,6 @@ def clean(self): self.add_error('name', 'Link already exists') except HyperLink.DoesNotExist: pass - # Updating else: # Ensure a link with the same name does not exist in the location @@ -457,7 +440,6 @@ def clean(self): old_link = HyperLink.objects.get(pk=self.instance.pk) except HyperLink.DoesNotExist: pass - if old_link and ( old_link.name != self.cleaned_data['name'] or old_link.folder != self.cleaned_data['folder'] @@ -471,7 +453,6 @@ def clean(self): self.add_error('name', 'Link already exists') except HyperLink.DoesNotExist: pass - return self.cleaned_data def save(self, *args, **kwargs): diff --git a/filesfolders/plugins.py b/filesfolders/plugins.py index c6911f62..26167e08 100644 --- a/filesfolders/plugins.py +++ b/filesfolders/plugins.py @@ -9,7 +9,7 @@ from filesfolders.urls import urlpatterns -# SODAR Constants +# SODAR constants APP_SETTING_SCOPE_PROJECT = SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT'] # Local constants @@ -21,13 +21,13 @@ class ProjectAppPlugin(ProjectAppPluginPoint): # Properties required by django-plugins ------------------------------ - #: Name (slug-safe, used in URLs) + #: Name (used as plugin ID) name = 'filesfolders' #: Title (used in templates) title = 'Files' - #: App URLs (will be included in settings by djangoplugins) + #: UI URLs urls = urlpatterns # Properties defined in ProjectAppPluginPoint ----------------------- diff --git a/filesfolders/views.py b/filesfolders/views.py index 11c65987..55e6bea2 100644 --- a/filesfolders/views.py +++ b/filesfolders/views.py @@ -24,10 +24,6 @@ from db_file_storage.storage import DatabaseFileStorage -from filesfolders.forms import FolderForm, FileForm, HyperLinkForm -from filesfolders.models import Folder, File, FileData, HyperLink -from filesfolders.utils import build_public_url - # Projectroles dependency from projectroles.models import Project, SODAR_CONSTANTS from projectroles.plugins import get_backend_api @@ -43,6 +39,10 @@ InvalidFormMixin, ) +from filesfolders.forms import FolderForm, FileForm, HyperLinkForm +from filesfolders.models import Folder, File, FileData, HyperLink +from filesfolders.utils import build_public_url + app_settings = AppSettingAPI() logger = logging.getLogger(__name__) @@ -73,10 +73,9 @@ def has_permission(self): return self.request.user.has_perm( 'filesfolders.update_data_own', self.get_permission_object() ) - else: - return self.request.user.has_perm( - 'filesfolders.update_data_all', self.get_permission_object() - ) + return self.request.user.has_perm( + 'filesfolders.update_data_all', self.get_permission_object() + ) except type(self.get_object()).DoesNotExist: return False @@ -160,7 +159,6 @@ def form_valid(self, form): view_action = self.get_view_action() old_data = {} update_attrs = ['name', 'folder', 'description', 'flag'] - if view_action == 'update': old_item = self.get_object() if old_item.__class__.__name__ == 'HyperLink': @@ -170,7 +168,6 @@ def form_valid(self, form): # Get old fields for a in update_attrs: old_data[a] = getattr(old_item, a) - self.object = form.save() # Add event in Timeline @@ -181,20 +178,17 @@ def form_valid(self, form): update_attrs=update_attrs, old_data=old_data, ) - messages.success( self.request, '{} "{}" successfully {}d.'.format( self.object.__class__.__name__, self.object.name, view_action ), ) - # TODO: Repetition, put this in a mixin? if self.object.folder: re_kwargs = {'folder': self.object.folder.sodar_uuid} else: re_kwargs = {'project': self.object.project.sodar_uuid} - return redirect(reverse('filesfolders:list', kwargs=re_kwargs)) @@ -203,7 +197,6 @@ class DeleteSuccessMixin(DeletionMixin): def get_success_url(self): timeline = get_backend_api('timeline_backend') - # Add event in Timeline if timeline: obj_type = TL_OBJ_TYPES[self.object.__class__.__name__] @@ -230,13 +223,11 @@ def get_success_url(self): self.object.__class__.__name__, self.object.name ), ) - # TODO: Repetition, put this in a mixin? if self.object.folder: re_kwargs = {'folder': self.object.folder.sodar_uuid} else: re_kwargs = {'project': self.object.project.sodar_uuid} - return reverse('filesfolders:list', kwargs=re_kwargs) @@ -246,39 +237,31 @@ class FileServeMixin: def get(self, *args, **kwargs): """GET request to return the file as attachment""" timeline = get_backend_api('timeline_backend') + if kwargs.get('project'): + redirect_url = reverse( + 'filesfolders:list', kwargs={'project': kwargs['project']} + ) + else: + redirect_url = reverse('home') # Get File object try: file = File.objects.get(sodar_uuid=kwargs['file']) except File.DoesNotExist: messages.error(self.request, 'File object not found.') - return redirect( - reverse( - 'filesfolders:list', kwargs={'project': kwargs['project']} - ) - ) - + return redirect(redirect_url) # Get corresponding FileData object with file content try: file_data = FileData.objects.get(file_name=file.file.name) except FileData.DoesNotExist: messages.error(self.request, 'File data not found.') - return redirect( - reverse( - 'filesfolders:list', kwargs={'project': kwargs['project']} - ) - ) - + return redirect(redirect_url) # Open file for serving try: file_content = storage.open(file_data.file_name) except Exception: messages.error(self.request, 'Error opening file.') - return redirect( - reverse( - 'filesfolders:list', kwargs={'project': kwargs['project']} - ) - ) + return redirect(redirect_url) # Return file as attachment response = HttpResponse( @@ -288,7 +271,6 @@ def get(self, *args, **kwargs): response['Content-Disposition'] = 'attachment; filename={}'.format( file.name ) - if self.request.user.is_authenticated: # Add event in Timeline if timeline: @@ -302,7 +284,6 @@ def get(self, *args, **kwargs): status_type='INFO', ) tl_event.add_object(file, 'file', file.name) - return response @@ -375,7 +356,6 @@ def get_context_data(self, *args, **kwargs): breadcrumb.insert(0, f.folder) f = f.folder context['folder_breadcrumb'] = breadcrumb - context['folders'] = Folder.objects.filter( project=project, folder=root_folder ) @@ -419,7 +399,6 @@ def get_context_data(self, *args, **kwargs): 'Exception in accessing readme file data (UUID={}): ' '{}'.format(readme_file.sodar_uuid, ex) ) - return context @@ -482,17 +461,11 @@ def form_valid(self, form): """Override form_valid() for zip file unpacking""" timeline = get_backend_api('timeline_backend') - ###################### # Regular file upload - ###################### - if not form.cleaned_data.get('unpack_archive'): return super().form_valid(form) - ##################### # Zip file unpacking - ##################### - file = form.cleaned_data.get('file') folder = form.cleaned_data.get('folder') project = self.get_project(self.request, self.kwargs) @@ -555,12 +528,10 @@ def form_valid(self, form): self.add_item_modify_event( obj=new_folder, request=self.request, view_action='create' ) - for new_file in new_files: self.add_item_modify_event( obj=new_file, request=self.request, view_action='create' ) - if timeline: timeline.add_event( project=project, @@ -638,7 +609,6 @@ class FileServePublicView(FileServeMixin, View): def get(self, *args, **kwargs): """Override of GET for checking request URL""" - try: file = File.objects.get(secret=kwargs['secret']) # Check if sharing public files is not allowed in project settings @@ -652,7 +622,6 @@ def get(self, *args, **kwargs): # If public URL serving is disabled, don't serve file if not file.public_url: return HttpResponseBadRequest(LINK_BAD_REQUEST_MSG) - # Update kwargs with file and project uuid:s kwargs.update( {'file': file.sodar_uuid, 'project': file.project.sodar_uuid} @@ -680,7 +649,6 @@ def get(self, *args, **kwargs): except File.DoesNotExist: messages.error(self.request, 'File not found.') return redirect(reverse('home')) - if not app_settings.get(APP_NAME, 'allow_public_links', file.project): messages.error( self.request, @@ -694,19 +662,16 @@ def get(self, *args, **kwargs): kwargs={'project': file.project.sodar_uuid}, ) ) - return super().get(*args, **kwargs) def get_context_data(self, *args, **kwargs): """Provide URL to context""" context = super().get_context_data(*args, **kwargs) - try: file = File.objects.get(sodar_uuid=self.kwargs['file']) except File.DoesNotExist: messages.error(self.request, 'File not found.') return redirect(reverse('home')) - if not file.public_url: messages.error(self.request, 'Public URL for file not enabled.') return redirect( @@ -715,7 +680,6 @@ def get_context_data(self, *args, **kwargs): kwargs={'project': file.project.sodar_uuid}, ) ) - context['file'] = file context['public_url'] = build_public_url(file, self.request) return context @@ -816,7 +780,6 @@ def _render_confirmation(self, **kwargs): exclude_list = [ x.sodar_uuid for x in self.items if isinstance(x, Folder) ] - # Exclude folders under folders to be moved for i in self.items: exclude_list += [ @@ -826,19 +789,15 @@ def _render_confirmation(self, **kwargs): ) if x.has_in_path(i) ] - # Exclude current folder if 'folder' in kwargs: exclude_list.append(kwargs['folder']) - folder_choices = Folder.objects.filter( project=self.project ).exclude(sodar_uuid__in=exclude_list) context['folder_choices'] = folder_choices - if folder_choices.count() == 0: context['folder_check'] = False - return super().render_to_response(context) def _finalize_edit(self, edit_count, target_folder, **kwargs): @@ -857,7 +816,6 @@ def _finalize_edit(self, edit_count, target_folder, **kwargs): ', '.join(f.name for f in self.failed), ), ) - if edit_count > 0: messages.success( self.request, @@ -874,7 +832,6 @@ def _finalize_edit(self, edit_count, target_folder, **kwargs): 'items': [x.name for x in self.items], 'failed': [x.name for x in self.failed], } - tl_event = timeline.add_event( project=Project.objects.filter( sodar_uuid=self.project.sodar_uuid @@ -896,7 +853,6 @@ def _finalize_edit(self, edit_count, target_folder, **kwargs): extra_data=extra_data, status_type='OK' if edit_count > 0 else 'FAILED', ) - if self.batch_action == 'move' and target_folder: tl_event.add_object( target_folder, 'target_folder', target_folder.get_path() @@ -906,7 +862,6 @@ def _finalize_edit(self, edit_count, target_folder, **kwargs): re_kwargs = {'folder': kwargs['folder']} else: re_kwargs = {'project': kwargs['project']} - return redirect(reverse('filesfolders:list', kwargs=re_kwargs)) def post(self, request, **kwargs): @@ -943,14 +898,10 @@ def post(self, request, **kwargs): #: Item permission perm_ok = can_update_all | (item.owner == request.user) - ######### # Checks - ######### - # Perm check if not perm_ok: self.failed.append(item) - # Moving checks (after user has selected target folder) elif self.batch_action == 'move' and user_confirmed: # Can't move if item with same name in target @@ -961,17 +912,13 @@ def post(self, request, **kwargs): } if cls.objects.filter(**get_kwargs): self.failed.append(item) - # Deletion checks elif self.batch_action == 'delete': # Can't delete a non-empty folder if isinstance(item, Folder) and not item.is_empty(): self.failed.append(item) - ############## # Modify item - ############## - if perm_ok and item not in self.failed: if not user_confirmed: self.items.append(item) @@ -984,10 +931,7 @@ def post(self, request, **kwargs): item.delete() edit_count += 1 - ################## # Render/redirect - ################## - # Confirmation needed if not user_confirmed: return self._render_confirmation(**kwargs) diff --git a/projectroles/app_settings.py b/projectroles/app_settings.py index e3eea462..a6b50d14 100644 --- a/projectroles/app_settings.py +++ b/projectroles/app_settings.py @@ -221,7 +221,7 @@ def _get_defs(cls, plugin=None, app_name=None): """ Ensure valid argument values for a settings def query. - :param plugin: Plugin object extending ProjectAppPluginPoint or None + :param plugin: Plugin object or None :param app_name: Name of the app plugin (string or None) :return: Dict :raise: ValueError if args are not valid or plugin is not found @@ -726,7 +726,7 @@ def get_definition(cls, name, plugin=None, app_name=None): or the plugin object. :param name: Setting name - :param plugin: Plugin object extending ProjectAppPluginPoint or None + :param plugin: Plugin object or None :param app_name: Name of the app plugin (string or None) :return: Dict :raise: ValueError if neither app_name nor plugin are set or if setting @@ -756,7 +756,7 @@ def get_definitions( Return app setting definitions of a specific scope from a plugin. :param scope: PROJECT, USER or PROJECT_USER - :param plugin: Plugin object extending ProjectAppPluginPoint or None + :param plugin: Plugin object or None :param app_name: Name of the app plugin (string or None) :param user_modifiable: Only return non-superuser modifiable settings if True (boolean) diff --git a/projectroles/plugins.py b/projectroles/plugins.py index 49b721be..b5cbb4b9 100644 --- a/projectroles/plugins.py +++ b/projectroles/plugins.py @@ -245,7 +245,7 @@ def revert_project_archive( class ProjectAppPluginPoint(PluginPoint): """Projectroles plugin point for registering project specific apps""" - #: App URLs (will be included in settings by djangoplugins) + #: UI URLs urls = [] #: App settings definition @@ -652,27 +652,25 @@ def get_active_plugins(plugin_type='project_app', custom_order=False): ', '.join(PLUGIN_TYPES.keys()) ) ) - plugins = eval(PLUGIN_TYPES[plugin_type]).get_plugins() - if plugins: - return sorted( - [ - p - for p in plugins - if ( - p.is_active() - and ( - plugin_type in ['project_app', 'site_app'] - or p.name in settings.ENABLED_BACKEND_PLUGINS - ) + ret = [ + p + for p in plugins + if ( + p.is_active() + and ( + plugin_type in ['project_app', 'site_app'] + or p.name in settings.ENABLED_BACKEND_PLUGINS ) - ], + ) + ] + return sorted( + ret, key=lambda x: x.plugin_ordering if custom_order and plugin_type == 'project_app' else x.name, ) - return None @@ -694,14 +692,12 @@ def change_plugin_status(name, status, plugin_type='app'): plugin = SiteAppPluginPoint.get_plugin(name) else: raise ValueError('Invalid plugin_type: "{}"'.format(plugin_type)) - if not plugin: raise ValueError( 'Plugin of type "{}" not found with name "{}"'.format( plugin_type, name ) ) - plugin = plugin.get_model() plugin.status = status plugin.save() @@ -750,13 +746,13 @@ def get_backend_api(plugin_name, force=False, **kwargs): class RemoteSiteAppPlugin(SiteAppPluginPoint): """Site plugin for remote site and project management""" - #: Name (slug-safe, used in URLs) + #: Name (used as plugin ID) name = 'remotesites' #: Title (used in templates) title = 'Remote Site Access' - #: App URLs (will be included in settings by djangoplugins) + #: UI URLs urls = [] #: Iconify icon diff --git a/projectroles/tests/test_views.py b/projectroles/tests/test_views.py index f624e837..ea56871e 100644 --- a/projectroles/tests/test_views.py +++ b/projectroles/tests/test_views.py @@ -139,6 +139,17 @@ }, } +EX_PROJECT_UI_SETTINGS = [ + 'project_str_setting', + 'project_int_setting', + 'project_str_setting_options', + 'project_int_setting_options', + 'project_bool_setting', + 'project_json_setting', + 'project_callable_setting', + 'project_callable_setting_options', +] + class TestViewsBase(RoleMixin, TestCase): """Base class for view testing""" @@ -157,7 +168,7 @@ def setUp(self): class TestHomeView(ProjectMixin, RoleAssignmentMixin, TestViewsBase): - """Tests for the home view""" + """Tests for HomeView""" def setUp(self): super().setUp() @@ -170,11 +181,12 @@ def setUp(self): self.owner_as = self.make_assignment( self.project, self.user, self.role_owner ) + self.url = reverse('home') - def test_render(self): - """Test rendering the home view""" + def test_get(self): + """Test HomeView GET""" with self.login(self.user): - response = self.client.get(reverse('home')) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) custom_cols = response.context['project_custom_cols'] self.assertEqual(len(custom_cols), 2) @@ -182,74 +194,74 @@ def test_render(self): self.assertEqual(response.context['project_col_count'], 4) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_render_anon(self): - """Test rendering with anonymous access""" - response = self.client.get(reverse('home')) + def test_get_anon(self): + """Test GET with anonymous access""" + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) - def test_context_get_sidebar_icon_size(self): - """Test getting sidebar icon size in context""" + def test_get_context_sidebar_icon_size(self): + """Test GET context for sidebar icon size""" with self.login(self.user): - response = self.client.get(reverse('home')) + response = self.client.get(self.url) self.assertEqual(response.context['sidebar_icon_size'], 36) @override_settings(PROJECTROLES_SIDEBAR_ICON_SIZE=SIDEBAR_ICON_MIN_SIZE - 2) - def test_context_get_sidebar_icon_size_min(self): - """Test sidebar icon size with value below minimum""" + def test_get_context_sidebar_icon_size_min(self): + """Test GET context for sidebar icon size with value below minimum""" with self.login(self.user): - response = self.client.get(reverse('home')) + response = self.client.get(self.url) self.assertEqual( response.context['sidebar_icon_size'], SIDEBAR_ICON_MIN_SIZE, ) @override_settings(PROJECTROLES_SIDEBAR_ICON_SIZE=SIDEBAR_ICON_MAX_SIZE + 2) - def test_context_get_sidebar_icon_size_max(self): - """Test sidebar icon size with value over max""" + def test_get_context_sidebar_icon_size_max(self): + """Test GET context for sidebar icon size with value over max""" with self.login(self.user): - response = self.client.get(reverse('home')) + response = self.client.get(self.url) self.assertEqual( response.context['sidebar_icon_size'], SIDEBAR_ICON_MAX_SIZE, ) - def test_context_get_sidebar_notch_pos(self): - """Test siderbar notch position""" + def test_get_context_sidebar_notch_pos(self): + """Test GET context for siderbar notch position""" with self.login(self.user): - response = self.client.get(reverse('home')) + response = self.client.get(self.url) self.assertEqual(response.context['sidebar_notch_pos'], 12) - def test_context_get_sidebar_notch_size(self): - """Test sidebar notch size""" + def test_get_context_sidebar_notch_size(self): + """Test GET context for sidebar notch size""" with self.login(self.user): - response = self.client.get(reverse('home')) + response = self.client.get(self.url) self.assertEqual(response.context['sidebar_notch_size'], 12) @override_settings(PROJECTROLES_SIDEBAR_ICON_SIZE=SIDEBAR_ICON_MIN_SIZE) - def test_context_get_sidebar_notch_size_min(self): - """Test sidebar notch size with minimum icon size""" + def test_get_context_sidebar_notch_size_min(self): + """Test GET context for sidebar notch size with minimum icon size""" with self.login(self.user): - response = self.client.get(reverse('home')) + response = self.client.get(self.url) self.assertEqual(response.context['sidebar_notch_size'], 9) - def test_context_get_sidebar_padding(self): - """Test sidebar padding""" + def test_get_context_sidebar_padding(self): + """Test GET context for sidebar padding""" with self.login(self.user): - response = self.client.get(reverse('home')) + response = self.client.get(self.url) self.assertEqual(response.context['sidebar_padding'], 8) @override_settings(PROJECTROLES_SIDEBAR_ICON_SIZE=SIDEBAR_ICON_MAX_SIZE) - def test_context_get_sidebar_padding_max(self): - """Test sidebar padding with maximum icon size""" + def test_get_context_sidebar_padding_max(self): + """Test GET context for sidebar padding with maximum icon size""" with self.login(self.user): - response = self.client.get(reverse('home')) + response = self.client.get(self.url) self.assertEqual(response.context['sidebar_padding'], 10) @override_settings(PROJECTROLES_SIDEBAR_ICON_SIZE=SIDEBAR_ICON_MIN_SIZE) - def test_context_get_sidebar_padding_min(self): - """Test sidebar padding with minimum icon size""" + def test_get_context_sidebar_padding_min(self): + """Test GET context for sidebar padding with minimum icon size""" with self.login(self.user): - response = self.client.get(reverse('home')) + response = self.client.get(self.url) self.assertEqual(response.context['sidebar_padding'], 4) @@ -260,7 +272,7 @@ class TestProjectSearchResultsView( ProjectEventMixin, ProjectEventStatusMixin, ): - """Tests for the project search results view""" + """Tests for ProjectSearchResultsView""" def setUp(self): super().setUp() @@ -275,8 +287,8 @@ def setUp(self): ) self.plugins = get_active_plugins(plugin_type='project_app') - def test_render(self): - """Test rendering project search view""" + def test_get(self): + """Test ProjectSearchResultsView GET""" with self.login(self.user): response = self.client.get( reverse('projectroles:search') + '?' + urlencode({'s': 'test'}) @@ -291,8 +303,8 @@ def test_render(self): len([p for p in self.plugins if p.search_enable]), ) - def test_render_search_type(self): - """Test rendering with search type""" + def test_get_search_type(self): + """Test GET with search type""" with self.login(self.user): response = self.client.get( reverse('projectroles:search') @@ -318,16 +330,16 @@ def test_render_search_type(self): ), ) - def test_render_non_text_input(self): - """Test non-text input from standard search (should redirect)""" + def test_get_non_text_input(self): + """Test GET with non-text input from standard search (should redirect)""" with self.login(self.user): response = self.client.get( reverse('projectroles:search') + '?s=+++' ) self.assertRedirects(response, reverse('home')) - def test_render_finder(self): - """Test rendering project search view as finder""" + def test_get_finder(self): + """Test GET as finder""" user_finder = self.make_user('user_finder') finder_cat = self.make_project( 'FinderCategory', PROJECT_TYPE_CATEGORY, self.category @@ -346,8 +358,8 @@ def test_render_finder(self): self.assertEqual(len(response.context['project_results']), 1) self.assertEqual(response.context['project_results'][0], finder_project) - def test_render_advanced(self): - """Test input from advanced search""" + def test_post_advanced(self): + """Test POST from ProjectAdvancedSearchView""" new_project = self.make_project( 'AnotherProject', PROJECT_TYPE_PROJECT, @@ -357,7 +369,6 @@ def test_render_advanced(self): self.cat_owner_as = self.make_assignment( new_project, self.user, self.role_owner ) - with self.login(self.user): response = self.client.post( reverse('projectroles:search_advanced'), @@ -371,8 +382,8 @@ def test_render_advanced(self): self.assertEqual(response.context['search_type'], None) self.assertEqual(len(response.context['project_results']), 2) - def test_render_advanced_short_input(self): - """Test input from advanced search with a short term (< 3 characters)""" + def test_post_advanced_short_input(self): + """Test POST with short term (< 3 characters)""" new_project = self.make_project( 'AnotherProject', PROJECT_TYPE_PROJECT, @@ -382,7 +393,6 @@ def test_render_advanced_short_input(self): self.cat_owner_as = self.make_assignment( new_project, self.user, self.role_owner ) - with self.login(self.user): response = self.client.post( reverse('projectroles:search_advanced'), @@ -392,8 +402,8 @@ def test_render_advanced_short_input(self): self.assertEqual(response.context['search_terms'], ['testproject']) self.assertEqual(len(response.context['project_results']), 1) - def test_render_advanced_empty_input(self): - """Test advanced search input with empty term (should be ignored)""" + def test_post_advanced_empty_input(self): + """Test POST with empty term (should be ignored)""" new_project = self.make_project( 'AnotherProject', PROJECT_TYPE_PROJECT, @@ -403,7 +413,6 @@ def test_render_advanced_empty_input(self): self.cat_owner_as = self.make_assignment( new_project, self.user, self.role_owner ) - with self.login(self.user): response = self.client.post( reverse('projectroles:search_advanced'), @@ -415,8 +424,8 @@ def test_render_advanced_empty_input(self): ) self.assertEqual(len(response.context['project_results']), 2) - def test_render_advanced_dupe(self): - """Test input from advanced search with a duplicate term""" + def test_post_advanced_dupe(self): + """Test POST with duplicate term""" with self.login(self.user): response = self.client.post( reverse('projectroles:search_advanced'), @@ -426,16 +435,16 @@ def test_render_advanced_dupe(self): self.assertEqual(response.context['search_terms'], ['xxx']) @override_settings(PROJECTROLES_ENABLE_SEARCH=False) - def test_disable_search(self): - """Test redirecting the view due to search being disabled""" + def test_get_disabled(self): + """Test GET with disabled search""" with self.login(self.user): response = self.client.get( reverse('projectroles:search') + '?' + urlencode({'s': 'test'}) ) self.assertRedirects(response, reverse('home')) - def test_search_omit_app(self): - """Test omitting an app from the advanced search""" + def test_get_omit_app(self): + """Test GET with omitted app""" self.event = self.make_event( project=self.project, app='projectroles', @@ -471,24 +480,24 @@ def test_search_omit_app(self): class TestProjectAdvancedSearchView( ProjectMixin, RoleAssignmentMixin, TestViewsBase ): - """Tests for the advanced search view""" + """Tests for ProjectAdvancedSearchView""" - def test_render(self): - """Test to ensure the advanced search view renders correctly""" + def test_get(self): + """Test ProjectAdvancedSearchView GET""" with self.login(self.user): response = self.client.get(reverse('projectroles:search_advanced')) self.assertEqual(response.status_code, 200) @override_settings(PROJECTROLES_ENABLE_SEARCH=False) - def test_disable_search(self): - """Test redirecting the view due to search being disabled""" + def test_get_disabled(self): + """Test GET with disabled search""" with self.login(self.user): response = self.client.get(reverse('projectroles:search_advanced')) self.assertRedirects(response, reverse('home')) class TestProjectDetailView(ProjectMixin, RoleAssignmentMixin, TestViewsBase): - """Tests for Project detail view""" + """Tests for ProjectDetailView""" def setUp(self): super().setUp() @@ -499,8 +508,8 @@ def setUp(self): self.project, self.user, self.role_owner ) - def test_render(self): - """Test rendering of project detail view""" + def test_get(self): + """Test ProjectDetailView GET""" with self.login(self.user): response = self.client.get( reverse( @@ -511,8 +520,8 @@ def test_render(self): self.assertEqual(response.status_code, 200) self.assertEqual(response.context['object'].pk, self.project.pk) - def test_render_not_found(self): - """Test rendering of project detail view with invalid UUID""" + def test_get_not_found(self): + """Test GET with invalid UUID""" with self.login(self.user): response = self.client.get( reverse( @@ -524,14 +533,14 @@ def test_render_not_found(self): class TestProjectCreateView(ProjectMixin, RoleAssignmentMixin, TestViewsBase): - """Tests for Project creation view""" + """Tests for ProjectCreateView""" def setUp(self): super().setUp() self.app_alert_model = get_backend_api('appalerts_backend').get_model() - def test_render_top(self): - """Test rendering top level category creation form""" + def test_get_top(self): + """Test ProjectCreateView GET with top level category creation form""" with self.login(self.user): response = self.client.get(reverse('projectroles:create')) self.assertEqual(response.status_code, 200) @@ -543,8 +552,8 @@ def test_render_top(self): self.assertEqual(form.initial['owner'], self.user) @override_settings(PROJECTROLES_DISABLE_CATEGORIES=True) - def test_render_top_disable_categories(self): - """Test rendering top level creation with categories disabled""" + def test_get_top_disable_categories(self): + """Test GET with top level and categories disabled""" with self.login(self.user): response = self.client.get(reverse('projectroles:create')) self.assertEqual(response.status_code, 200) @@ -555,14 +564,14 @@ def test_render_top_disable_categories(self): self.assertIsInstance(form.fields['parent'].widget, HiddenInput) self.assertEqual(form.initial['owner'], self.user) - def test_render_sub(self): - """Test rendering if creating a subproject""" + def test_get_sub(self): + """Test GET under category""" category = self.make_project( 'TestCategory', PROJECT_TYPE_CATEGORY, None ) self.make_assignment(category, self.user, self.role_owner) # Create another user to enable checking for owner selection - self.make_user('new_user') + self.make_user('user_new') with self.login(self.user): response = self.client.get( @@ -592,13 +601,13 @@ def test_render_sub(self): self.assertIsInstance(form.fields['parent'].widget, HiddenInput) self.assertEqual(form.initial['owner'], self.user) - def test_render_sub_cat_member(self): - """Test rendering under a category as a category non-owner""" + def test_get_cat_member(self): + """Test GET under category as category non-owner""" category = self.make_project( 'TestCategory', PROJECT_TYPE_CATEGORY, None ) self.make_assignment(category, self.user, self.role_owner) - new_user = self.make_user('new_user') + new_user = self.make_user('user_new') self.make_assignment(category, new_user, self.role_contributor) with self.login(new_user): @@ -613,13 +622,11 @@ def test_render_sub_cat_member(self): # Current user should be the initial value for owner self.assertEqual(form.initial['owner'], new_user) - def test_render_sub_project(self): - """Test rendering if creating under a project (should fail)""" + def test_get_project(self): + """Test GET under project (should fail)""" project = self.make_project('TestProject', PROJECT_TYPE_PROJECT, None) self.make_assignment(project, self.user, self.role_owner) - # Create another user to enable checking for owner selection - self.make_user('new_user') - + self.make_user('user_new') with self.login(self.user): response = self.client.get( reverse( @@ -629,8 +636,8 @@ def test_render_sub_project(self): ) self.assertEqual(response.status_code, 302) - def test_render_sub_not_found(self): - """Test rendering with invalid parent UUID""" + def test_get_not_found(self): + """Test GET with invalid parent UUID""" with self.login(self.user): response = self.client.get( reverse( @@ -640,14 +647,13 @@ def test_render_sub_not_found(self): ) self.assertEqual(response.status_code, 404) - def test_render_parent_owner(self): - """Test rendering with parent owner as initial value""" + def test_get_parent_owner(self): + """Test GET with parent owner as initial value""" category = self.make_project( 'TestCategory', PROJECT_TYPE_CATEGORY, None ) - user_new = self.make_user('new_user') + user_new = self.make_user('user_new') self.make_assignment(category, user_new, self.role_owner) - with self.login(self.user): response = self.client.get( reverse( @@ -659,8 +665,8 @@ def test_render_parent_owner(self): form = response.context['form'] self.assertEqual(form.initial['owner'], user_new) - def test_create_top_level_category(self): - """Test creation of top level category""" + def test_post_top_level_category(self): + """Test POST for top level category""" self.assertEqual(Project.objects.all().count(), 0) values = { 'title': 'TestCategory', @@ -729,27 +735,12 @@ def test_create_top_level_category(self): ), ) - def test_create_project(self): - """Test Project creation""" - # Create category - values = { - 'title': 'TestCategory', - 'type': PROJECT_TYPE_CATEGORY, - 'parent': '', - 'owner': self.user.sodar_uuid, - 'description': 'description', - 'public_guest_access': False, - } - # Add settings values - values.update( - app_settings.get_defaults(APP_SETTING_SCOPE_PROJECT, post_safe=True) + def test_post_project(self): + """Test POST for project creation""" + category = self.make_project( + 'TestCategory', PROJECT_TYPE_CATEGORY, None ) - - with self.login(self.user): - response = self.client.post(reverse('projectroles:create'), values) - self.assertEqual(response.status_code, 302) - category = Project.objects.first() - self.assertIsNotNone(category) + self.make_assignment(category, self.user, self.role_owner) # Create project values = { @@ -831,14 +822,14 @@ def test_create_project(self): } self.assertEqual(model_to_dict(owner_as), expected) - def test_create_project_cat_member(self): - """Test Project creation as category member""" + def test_post_project_cat_member(self): + """Test POST for project as category member""" # Create category and add new user as member category = self.make_project( title='TestCategory', type=PROJECT_TYPE_CATEGORY, parent=None ) self.make_assignment(category, self.user, self.role_owner) - new_user = self.make_user('new_user') + new_user = self.make_user('user_new') self.make_assignment(category, new_user, self.role_contributor) values = { @@ -869,8 +860,8 @@ def test_create_project_cat_member(self): self.assertEqual(self.app_alert_model.objects.count(), 1) self.assertEqual(len(mail.outbox), 1) - def test_create_project_title_delimiter(self): - """Test Project creation with category delimiter in title (should fail)""" + def test_post_project_title_delimiter(self): + """Test POST for project with category delimiter in title (should fail)""" category = self.make_project( 'TestCategory', PROJECT_TYPE_CATEGORY, None ) @@ -902,7 +893,7 @@ def test_create_project_title_delimiter(self): class TestProjectUpdateView( ProjectMixin, RoleAssignmentMixin, RemoteTargetMixin, TestViewsBase ): - """Tests for Project updating view""" + """Tests for ProjectUpdateView""" @classmethod def _get_post_app_settings(cls, project, user): @@ -961,18 +952,20 @@ def setUp(self): self.owner_as = self.make_assignment( self.project, self.user, self.role_owner ) - # Get AppAlert model self.app_alert_model = get_backend_api('appalerts_backend').get_model() + self.url = reverse( + 'projectroles:update', + kwargs={'project': self.project.sodar_uuid}, + ) + self.url_cat = reverse( + 'projectroles:update', + kwargs={'project': self.category.sodar_uuid}, + ) - def test_render_project(self): - """Test rendering of Project updating form with an existing project""" + def test_get_project(self): + """Test GET with project""" with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:update', - kwargs={'project': self.project.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) form = response.context['form'] self.assertIsNotNone(form) @@ -980,10 +973,10 @@ def test_render_project(self): self.assertNotIsInstance(form.fields['parent'].widget, HiddenInput) self.assertIsInstance(form.fields['owner'].widget, HiddenInput) - def test_render_parent(self): - """Test current parent selectability without parent role""" + def test_get_no_parent_role(self): + """Test GET for current parent selectability without parent role""" # Create new user and project, make new user the owner - user_new = self.make_user('new_user') + user_new = self.make_user('user_new') self.owner_as.user = user_new self.owner_as.save() # Create another category with new user as owner @@ -993,12 +986,7 @@ def test_render_parent(self): self.make_assignment(category2, user_new, self.role_owner) with self.login(user_new): - response = self.client.get( - reverse( - 'projectroles:update', - kwargs={'project': self.project.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) form = response.context['form'] self.assertIsNotNone(form) @@ -1006,8 +994,8 @@ def test_render_parent(self): self.assertEqual(form.initial['parent'], self.category.sodar_uuid) self.assertEqual(len(form.fields['parent'].choices), 2) - def test_update_project_superuser(self): - """Test project updating as superuser""" + def test_post_project_superuser(self): + """Test POST for project as superuser""" timeline = get_backend_api('timeline_backend') new_category = self.make_project('NewCat', PROJECT_TYPE_CATEGORY, None) self.make_assignment(new_category, self.user, self.role_owner) @@ -1021,15 +1009,8 @@ def test_update_project_superuser(self): # Add settings values ps = self._get_post_app_settings(self.project, self.user) values.update(ps) - with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:update', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url, values) self.assertEqual(Project.objects.all().count(), 3) self.project.refresh_from_db() @@ -1087,8 +1068,8 @@ def test_update_project_superuser(self): self.assertIn('description', tl_event.extra_data) self.assertIn('parent', tl_event.extra_data) - def test_update_project_regular_user(self): - """Test project updating as regular user""" + def test_post_project_regular_user(self): + """Test POST as regular user""" # Create new user and set as self.project owner user_new = self.make_user('user_new') self.owner_as.user = user_new @@ -1118,15 +1099,8 @@ def test_update_project_regular_user(self): values['owner'] = user_new.sodar_uuid ps = self._get_post_app_settings(self.project, user_new) values.update(ps) - with self.login(user_new): - self.client.post( - reverse( - 'projectroles:update', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) + self.client.post(self.url, values) self.assertEqual(Project.objects.all().count(), 3) self.project.refresh_from_db() @@ -1161,8 +1135,8 @@ def test_update_project_regular_user(self): ) self.assertEqual(hidden_json, UPDATED_HIDDEN_JSON_SETTING) - def test_update_project_title_delimiter(self): - """Test Project updating with category delimiter in title (should fail)""" + def test_post_project_title_delimiter(self): + """Test POST with category delimiter in title (should fail)""" # TODO: Add values getter as a helper values = model_to_dict(self.project) values['parent'] = self.category.sodar_uuid @@ -1173,19 +1147,13 @@ def test_update_project_title_delimiter(self): app_settings.get_all(project=self.project, post_safe=True) ) with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:update', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url, values) self.assertEqual(response.status_code, 200) self.project.refresh_from_db() self.assertEqual(self.project.title, 'TestProject') - def test_update_project_custom_validation(self): - """Test updating with custom validation and invalid value (should fail)""" + def test_post_project_custom_validation(self): + """Test POST with custom validation and invalid value (should fail)""" values = model_to_dict(self.project) values['parent'] = self.category.sodar_uuid values['owner'] = self.user.sodar_uuid @@ -1196,13 +1164,7 @@ def test_update_project_custom_validation(self): 'settings.example_project_app.project_str_setting' ] = INVALID_SETTING_VALUE with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:update', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url, values) self.assertEqual(response.status_code, 200) self.assertEqual( list(get_messages(response.wsgi_request))[0].message, @@ -1215,15 +1177,32 @@ def test_update_project_custom_validation(self): '', ) - def test_render_category(self): - """Test rendering with existing category""" + def test_post_project_public_access(self): + """Test POST with public guest access""" + self.assertEqual(self.project.public_guest_access, False) + self.assertEqual(self.category.has_public_children, False) + + values = model_to_dict(self.project) + values['public_guest_access'] = True + values['parent'] = self.category.sodar_uuid # NOTE: Must add parent + values['owner'] = self.user.sodar_uuid # NOTE: Must add owner + values.update( + app_settings.get_all(project=self.project, post_safe=True) + ) with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:update', - kwargs={'project': self.category.sodar_uuid}, - ) - ) + response = self.client.post(self.url, values) + + self.assertEqual(response.status_code, 302) + self.project.refresh_from_db() + self.category.refresh_from_db() + self.assertEqual(self.project.public_guest_access, True) + # Assert the parent category has_public_children is set true + self.assertEqual(self.category.has_public_children, True) + + def test_get_category(self): + """Test GET with category""" + with self.login(self.user): + response = self.client.get(self.url_cat) self.assertEqual(response.status_code, 200) form = response.context['form'] self.assertIsNotNone(form) @@ -1231,10 +1210,9 @@ def test_render_category(self): self.assertNotIsInstance(form.fields['parent'].widget, HiddenInput) self.assertIsInstance(form.fields['owner'].widget, HiddenInput) - def test_update_category(self): - """Test category updating""" + def test_post_category(self): + """Test POST with category""" self.assertEqual(Project.objects.all().count(), 2) - values = model_to_dict(self.category) values['title'] = 'updated title' values['description'] = 'updated description' @@ -1245,13 +1223,7 @@ def test_update_category(self): app_settings.get_all(project=self.category, post_safe=True) ) with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:update', - kwargs={'project': self.category.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url_cat, values) self.assertEqual(response.status_code, 302) self.assertEqual(Project.objects.all().count(), 2) @@ -1282,7 +1254,6 @@ def test_update_category(self): self.assertEqual(settings.count(), 1) setting = settings.first() self.assertEqual(setting.name, 'project_category_bool_setting') - # Assert redirect with self.login(self.user): self.assertRedirects( @@ -1293,8 +1264,8 @@ def test_update_category(self): ), ) - def test_update_category_parent(self): - """Test category parent updating to ensure titles are changed""" + def test_post_category_parent(self): + """Test POST with updated category parent""" new_category = self.make_project( 'NewCategory', PROJECT_TYPE_CATEGORY, None ) @@ -1317,15 +1288,8 @@ def test_update_category_parent(self): values.update( app_settings.get_all(project=self.category, post_safe=True) ) - with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:update', - kwargs={'project': self.category.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url_cat, values) self.assertEqual(response.status_code, 302) # Assert category state and project title after update @@ -1345,46 +1309,12 @@ def test_update_category_parent(self): + self.project.title, ) - def test_update_public_access(self): - """Test Project updating with public guest access""" - self.assertEqual(self.project.public_guest_access, False) - self.assertEqual(self.category.has_public_children, False) - - values = model_to_dict(self.project) - values['public_guest_access'] = True - values['parent'] = self.category.sodar_uuid # NOTE: Must add parent - values['owner'] = self.user.sodar_uuid # NOTE: Must add owner - values.update( - app_settings.get_all(project=self.project, post_safe=True) - ) - - with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:update', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) - - self.assertEqual(response.status_code, 302) - self.project.refresh_from_db() - self.category.refresh_from_db() - self.assertEqual(self.project.public_guest_access, True) - # Assert the parent category has_public_children is set true - self.assertEqual(self.category.has_public_children, True) - @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET) - def test_render_remote(self): - """Test rendering form for remote site as target""" + def test_get_remote(self): + """Test GET with remote project as target""" self.set_up_as_target(projects=[self.category, self.project]) with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:update', - kwargs={'project': self.project.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) form = response.context['form'] @@ -1432,8 +1362,8 @@ def test_render_remote(self): ) @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET) - def test_update_remote(self): - """Test updating remote project as target""" + def test_post_remote(self): + """Test POST with remote project as target""" self.set_up_as_target(projects=[self.category, self.project]) values = model_to_dict(self.project) values['owner'] = self.user.sodar_uuid @@ -1454,20 +1384,13 @@ def test_update_remote(self): values['settings.projectroles.ip_restrict'] = True values['settings.projectroles.ip_allowlist'] = '["192.168.1.1"]' self.assertEqual(Project.objects.all().count(), 2) - with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:update', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url, values) self.assertEqual(response.status_code, 302) self.assertEqual(Project.objects.all().count(), 2) - def test_render_not_found(self): - """Test rendering with invalid project UUID""" + def test_get_not_found(self): + """Test GET with invalid project UUID""" with self.login(self.user): response = self.client.get( reverse( @@ -1513,34 +1436,30 @@ def setUp(self): self.owner_as = self.make_assignment( self.project, self.user, self.role_owner ) - # Add contributor user to project self.user_contributor = self.make_user('user_contributor') self.contributor_as = self.make_assignment( self.project, self.user_contributor, self.role_contributor ) - # Get AppAlert model self.app_alert_model = get_backend_api('appalerts_backend').get_model() + self.url = reverse( + 'projectroles:archive', + kwargs={'project': self.project.sodar_uuid}, + ) + self.url_cat = reverse( + 'projectroles:archive', + kwargs={'project': self.category.sodar_uuid}, + ) - def test_render(self): - """Test rendering ProjectArchiveView with a project""" + def test_get(self): + """Test ProjectArchiveView GET with project""" with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:archive', - kwargs={'project': self.project.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) - def test_render_category(self): - """Test rendering ProjectArchiveView with a category (should fail)""" + def test_get_category(self): + """Test GET with category (should fail)""" with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:archive', - kwargs={'project': self.category.sodar_uuid}, - ) - ) + response = self.client.get(self.url_cat) self.assertRedirects( response, reverse( @@ -1549,8 +1468,8 @@ def test_render_category(self): ), ) - def test_archive(self): - """Test archiving project""" + def test_post(self): + """Test ProjectArchiveView POST""" self.assertEqual(self.project.archive, False) self.assertEqual(self._get_tl().count(), 0) self.assertEqual(self._get_tl_un().count(), 0) @@ -1560,13 +1479,7 @@ def test_archive(self): values = {'status': True} with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:archive', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url, values) self.assertRedirects( response, reverse( @@ -1585,8 +1498,8 @@ def test_archive(self): self.assertEqual(self._get_alerts().first().user, self.user_contributor) self.assertEqual(len(mail.outbox), 1) - def test_unarchive(self): - """Test unarchiving project""" + def test_post_unarchive(self): + """Test POST to unarchiving project""" self.project.set_archive() self.assertEqual(self._get_tl().count(), 0) self.assertEqual(self._get_tl_un().count(), 0) @@ -1596,13 +1509,7 @@ def test_unarchive(self): values = {'status': False} with self.login(self.user): - self.client.post( - reverse( - 'projectroles:archive', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) + self.client.post(self.url, values) self.project.refresh_from_db() self.assertEqual(self.project.archive, False) @@ -1615,41 +1522,29 @@ def test_unarchive(self): ) self.assertEqual(len(mail.outbox), 1) - def test_archive_project_archived(self): - """Test archiving an already archived project""" + def test_post_project_archived(self): + """Test POST with already archived project""" self.project.set_archive() self.assertEqual(self._get_tl().count(), 0) self.assertEqual(self._get_tl_un().count(), 0) self.assertEqual(len(mail.outbox), 0) values = {'status': True} with self.login(self.user): - self.client.post( - reverse( - 'projectroles:archive', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) + self.client.post(self.url, values) self.project.refresh_from_db() self.assertEqual(self.project.archive, True) self.assertEqual(self._get_tl().count(), 0) self.assertEqual(self._get_alerts().count(), 0) self.assertEqual(len(mail.outbox), 0) - def test_archive_category(self): - """Test archiving category (should fail)""" + def test_post_category(self): + """Test POST with category (should fail)""" self.assertEqual(self.category.archive, False) self.assertEqual(self._get_tl().count(), 0) self.assertEqual(len(mail.outbox), 0) values = {'status': True} with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:archive', - kwargs={'project': self.category.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url_cat, values) self.assertRedirects( response, reverse( @@ -1664,12 +1559,11 @@ def test_archive_category(self): self.assertEqual(len(mail.outbox), 0) -class TestProjectSettingsForm( +class TestProjectForm( AppSettingMixin, TestViewsBase, ProjectMixin, RoleAssignmentMixin ): - """Tests for project settings in the project create/update view""" + """Tests for ProjectForm""" - # NOTE: This assumes an example app is available def setUp(self): super().setUp() # Init user & role @@ -1679,132 +1573,29 @@ def setUp(self): self.owner_as = self.make_assignment( self.project, self.user, self.role_owner ) - - # Init string setting - self.setting_str = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_str_setting', - setting_type='STRING', - value='', - project=self.project, - ) - # Init string setting with options - self.setting_str_options = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_str_setting_options', - setting_type='STRING', - value='string1', - project=self.project, - ) - # Init integer setting - self.setting_int = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_int_setting', - setting_type='INTEGER', - value=0, - project=self.project, - ) - # Init integer setting with options - self.setting_int_options = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_int_setting_options', - setting_type='INTEGER', - value=0, - project=self.project, - ) - # Init boolean setting - self.setting_bool = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_bool_setting', - setting_type='BOOLEAN', - value=False, - project=self.project, - ) - # Init JSON setting - self.setting_json = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_json_setting', - setting_type='JSON', - value=None, - value_json={ - 'Example': 'Value', - 'list': [1, 2, 3, 4, 5], - 'level_6': False, - }, - project=self.project, - ) - # Init IP restrict setting - self.setting_ip_restrict = self.make_setting( - app_name='projectroles', - name='ip_restrict', - setting_type='BOOLEAN', - value=False, - project=self.project, - ) - # Init IP allowlist setting - self.setting_ip_allowlist = self.make_setting( - app_name='projectroles', - name='ip_allowlist', - setting_type='JSON', - value=None, - value_json=[], - project=self.project, + self.url = reverse( + 'projectroles:update', + kwargs={'project': self.project.sodar_uuid}, ) def test_get(self): - """Test rendering settings values""" + """Test GET for settings values""" with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:update', - kwargs={'project': self.project.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) self.assertIsNotNone(response.context['form']) - field = response.context['form'].fields.get( - 'settings.example_project_app.project_str_setting' - ) - self.assertIsNotNone(field) + fields = response.context['form'].fields + for s in EX_PROJECT_UI_SETTINGS: + self.assertIsNotNone(fields['settings.example_project_app.' + s]) + field = fields['settings.example_project_app.project_str_setting'] self.assertEqual(field.widget.attrs['placeholder'], 'Example string') - field = response.context['form'].fields.get( - 'settings.example_project_app.project_int_setting' - ) - self.assertIsNotNone(field) + field = fields['settings.example_project_app.project_int_setting'] self.assertEqual(field.widget.attrs['placeholder'], 0) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_str_setting_options' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_int_setting_options' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_bool_setting' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_json_setting' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.projectroles.ip_restrict' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.projectroles.ip_allowlist' - ) - ) + self.assertIsNotNone(fields['settings.projectroles.ip_restrict']) + self.assertIsNotNone(fields['settings.projectroles.ip_allowlist']) def test_post(self): - """Test modifying settings values""" + """Test POST to modify settings values""" self.assertEqual( app_settings.get( EXAMPLE_APP_NAME, 'project_str_setting', project=self.project @@ -1875,20 +1666,13 @@ def test_post(self): 'title': 'TestProject', 'type': PROJECT_TYPE_PROJECT, } + with self.login(self.user): + response = self.client.post(self.url, values) + # Assert redirect with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:update', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) - - # Assert redirect - with self.login(self.user): - self.assertRedirects( - response, + self.assertRedirects( + response, reverse( 'projectroles:detail', kwargs={'project': self.project.sodar_uuid}, @@ -1950,7 +1734,7 @@ def test_post(self): @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET) -class TestProjectSettingsFormTarget( +class TestProjectFormTarget( RemoteSiteMixin, RemoteProjectMixin, AppSettingMixin, @@ -1958,21 +1742,16 @@ class TestProjectSettingsFormTarget( ProjectMixin, RoleAssignmentMixin, ): - """ - Tests for project settings in the project create/update view on target site - """ + """Tests for project create/update form on a target site""" - # NOTE: This assumes an example app is available def setUp(self): super().setUp() - # Init user & role self.project = self.make_project( 'TestProject', PROJECT_TYPE_PROJECT, None ) self.owner_as = self.make_assignment( self.project, self.user, self.role_owner ) - # Create site self.site = self.make_site( name=REMOTE_SITE_NAME, url=REMOTE_SITE_URL, @@ -1986,135 +1765,27 @@ def setUp(self): site=self.site, level=SODAR_CONSTANTS['REMOTE_LEVEL_READ_ROLES'], ) - - # Init string setting - self.setting_str = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_str_setting', - setting_type='STRING', - value='', - project=self.project, - ) - # Init string setting with options - self.setting_str_options = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_str_setting_options', - setting_type='STRING', - value='string1', - project=self.project, - ) - # Init integer setting - self.setting_int = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_int_setting', - setting_type='INTEGER', - value='0', - project=self.project, - ) - # Init integer setting with options - self.setting_int_options = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_int_setting_options', - setting_type='INTEGER', - value=0, - project=self.project, - ) - # Init boolean setting - self.setting_bool = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_bool_setting', - setting_type='BOOLEAN', - value=False, - project=self.project, - ) - # Init JSON setting - self.setting_json = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_json_setting', - setting_type='JSON', - value=None, - value_json={ - 'Example': 'Value', - 'list': [1, 2, 3, 4, 5], - 'level_6': False, - }, - project=self.project, + self.url = reverse( + 'projectroles:update', + kwargs={'project': self.project.sodar_uuid}, ) def test_get(self): - """Test rendering the settings values""" + """Test GET for settings values""" with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:update', - kwargs={'project': self.project.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) self.assertIsNotNone(response.context['form']) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_str_setting' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_int_setting' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_str_setting_options' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_int_setting_options' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_bool_setting' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_json_setting' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_callable_setting' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_callable_setting_options' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.projectroles.ip_restrict' - ) - ) - self.assertTrue( - response.context['form'] - .fields.get('settings.projectroles.ip_restrict') - .disabled - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.projectroles.ip_allowlist' - ) - ) - self.assertTrue( - response.context['form'] - .fields.get('settings.projectroles.ip_allowlist') - .disabled - ) + fields = response.context['form'].fields + for s in EX_PROJECT_UI_SETTINGS: + self.assertIsNotNone(fields['settings.example_project_app.' + s]) + self.assertIsNotNone(fields['settings.projectroles.ip_restrict']) + self.assertTrue(fields['settings.projectroles.ip_restrict'].disabled) + self.assertIsNotNone(fields['settings.projectroles.ip_allowlist']) + self.assertTrue(fields['settings.projectroles.ip_allowlist'].disabled) def test_post(self): - """Test modifying the settings values""" + """Test POST to modify settings values as target""" self.assertEqual( app_settings.get( EXAMPLE_APP_NAME, 'project_str_setting', project=self.project @@ -2173,15 +1844,9 @@ def test_post(self): 'title': 'TestProject', 'type': PROJECT_TYPE_PROJECT, } - with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:update', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url, values) + # Assert redirect with self.login(self.user): self.assertRedirects( @@ -2254,7 +1919,7 @@ def test_post(self): PROJECTROLES_SITE_MODE=SITE_MODE_TARGET, PROJECTROLES_APP_SETTINGS_TEST=PROJECTROLES_APP_SETTINGS_TEST_LOCAL, ) -class TestProjectSettingsFormTargetLocal( +class TestProjectFormTargetLocal( RemoteSiteMixin, RemoteProjectMixin, AppSettingMixin, @@ -2263,20 +1928,17 @@ class TestProjectSettingsFormTargetLocal( RoleAssignmentMixin, ): """ - Tests for project settings in the project create/update view on target site + Tests for project create/update form on target site with local settings """ - # NOTE: This assumes an example app is available def setUp(self): super().setUp() - # Init user & role self.project = self.make_project( 'TestProject', PROJECT_TYPE_PROJECT, None ) self.owner_as = self.make_assignment( self.project, self.user, self.role_owner ) - # Create site self.site = self.make_site( name=REMOTE_SITE_NAME, url=REMOTE_SITE_URL, @@ -2290,125 +1952,29 @@ def setUp(self): site=self.site, level=SODAR_CONSTANTS['REMOTE_LEVEL_READ_ROLES'], ) - - # Init string setting - self.setting_str = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_str_setting', - setting_type='STRING', - value='', - project=self.project, - ) - # Init string setting with options - self.setting_str_options = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_str_setting_options', - setting_type='STRING', - value='string1', - project=self.project, - ) - # Init integer setting - self.setting_int = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_int_setting', - setting_type='INTEGER', - value='0', - project=self.project, - ) - # Init integer setting with options - self.setting_int_options = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_int_setting_options', - setting_type='INTEGER', - value=0, - project=self.project, - ) - # Init boolean setting - self.setting_bool = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_bool_setting', - setting_type='BOOLEAN', - value=False, - project=self.project, - ) - # Init JSON setting - self.setting_json = self.make_setting( - app_name=EXAMPLE_APP_NAME, - name='project_json_setting', - setting_type='JSON', - value=None, - value_json={ - 'Example': 'Value', - 'list': [1, 2, 3, 4, 5], - 'level_6': False, - }, - project=self.project, + self.url = reverse( + 'projectroles:update', + kwargs={'project': self.project.sodar_uuid}, ) def test_get(self): - """Test rendering settings values as target""" + """Test GET for settings values as target""" with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:update', - kwargs={'project': self.project.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) self.assertIsNotNone(response.context['form']) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_str_setting' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_int_setting' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_str_setting_options' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_int_setting_options' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_bool_setting' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.example_project_app.project_json_setting' - ) - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.projectroles.test_setting_local' - ) - ) + fields = response.context['form'].fields + for s in EX_PROJECT_UI_SETTINGS: + self.assertIsNotNone(fields['settings.example_project_app.' + s]) + self.assertIsNotNone(fields['settings.projectroles.test_setting_local']) self.assertFalse( - response.context['form'] - .fields.get('settings.projectroles.test_setting_local') - .disabled - ) - self.assertIsNotNone( - response.context['form'].fields.get( - 'settings.projectroles.test_setting' - ) - ) - self.assertTrue( - response.context['form'] - .fields.get('settings.projectroles.test_setting') - .disabled + fields['settings.projectroles.test_setting_local'].disabled ) + self.assertIsNotNone(fields['settings.projectroles.test_setting']) + self.assertTrue(fields['settings.projectroles.test_setting'].disabled) def test_post(self): - """Test modifying settings values as target""" + """Test POST to modify settings values as target""" self.assertEqual( app_settings.get( EXAMPLE_APP_NAME, 'project_str_setting', project=self.project @@ -2474,15 +2040,8 @@ def test_post(self): 'title': 'TestProject', 'type': PROJECT_TYPE_PROJECT, } - with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:update', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url, values) # Assert redirect with self.login(self.user): @@ -2565,7 +2124,7 @@ def test_post(self): class TestProjectRoleView(ProjectMixin, RoleAssignmentMixin, TestViewsBase): - """Tests for project roles view""" + """Tests for ProjectRoleView""" def setUp(self): super().setUp() @@ -2587,8 +2146,8 @@ def setUp(self): self.project, self.user_guest, self.role_guest ) - def test_render(self): - """Test rendering project roles view""" + def test_get(self): + """Test ProjectRoleView GET""" with self.login(self.user): response = self.client.get( reverse( @@ -2599,7 +2158,6 @@ def test_render(self): self.assertEqual(response.status_code, 200) self.assertEqual(response.context['project'].pk, self.project.pk) - # Assert users expected = [ { 'id': self.owner_as.pk, @@ -2626,11 +2184,10 @@ def test_render(self): self.assertEqual( [model_to_dict(m) for m in response.context['roles']], expected ) - # Assert other context data self.assertNotIn('remote_role_url', response.context) - def test_render_not_found(self): - """Test rendering project roles view with invalid project UUID""" + def test_get_not_found(self): + """Test GET view with invalid project UUID""" with self.login(self.user): response = self.client.get( reverse( @@ -2644,7 +2201,7 @@ def test_render_not_found(self): class TestRoleAssignmentCreateView( ProjectMixin, RoleAssignmentMixin, TestViewsBase ): - """Tests for RoleAssignment creation view""" + """Tests for RoleAssignmentCreateView and related helper views""" def setUp(self): super().setUp() @@ -2664,18 +2221,21 @@ def setUp(self): self.project, self.user_owner, self.role_owner ) self.user_new = self.make_user('user_new') - # Get AppAlert model + # Set up helpers self.app_alert_model = get_backend_api('appalerts_backend').get_model() + self.url = reverse( + 'projectroles:role_create', + kwargs={'project': self.project.sodar_uuid}, + ) + self.url_cat = reverse( + 'projectroles:role_create', + kwargs={'project': self.category.sodar_uuid}, + ) - def test_render(self): - """Test rendering of RoleAssignment creation form""" + def test_get(self): + """Test RoleAssignmentCreateView GET""" with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:role_create', - kwargs={'project': self.project.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) form = response.context['form'] self.assertIsNotNone(form) @@ -2707,15 +2267,10 @@ def test_render(self): form.fields['role'].choices, ) - def test_render_category(self): - """Test rendering for category""" + def test_get_category(self): + """Test GET with category""" with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:role_create', - kwargs={'project': self.category.sodar_uuid}, - ) - ) + response = self.client.get(self.url_cat) self.assertEqual(response.status_code, 200) form = response.context['form'] self.assertIsInstance(form.fields['project'].widget, HiddenInput) @@ -2744,8 +2299,8 @@ def test_render_category(self): form.fields['role'].choices, ) - def test_render_not_found(self): - """Test rendering with invalid project UUID""" + def test_get_not_found(self): + """Test GET with invalid project UUID""" with self.login(self.user): response = self.client.get( reverse( @@ -2755,8 +2310,8 @@ def test_render_not_found(self): ) self.assertEqual(response.status_code, 404) - def test_create_assignment(self): - """Test RoleAssignment creation""" + def test_post(self): + """Test RoleAssignmentCreateView POST""" self.assertEqual(RoleAssignment.objects.all().count(), 2) self.assertEqual( self.app_alert_model.objects.filter( @@ -2771,13 +2326,7 @@ def test_create_assignment(self): 'role': self.role_guest.pk, } with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:role_create', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url, values) self.assertEqual(RoleAssignment.objects.all().count(), 3) role_as = RoleAssignment.objects.get( @@ -2806,8 +2355,8 @@ def test_create_assignment(self): ), ) - def test_create_delegate(self): - """Test RoleAssignment creation with project delegate role""" + def test_post_delegate(self): + """Test POST with project delegate role""" self.assertEqual(RoleAssignment.objects.all().count(), 2) values = { 'project': self.project.sodar_uuid, @@ -2815,14 +2364,7 @@ def test_create_delegate(self): 'role': self.role_delegate.pk, } with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:role_create', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) - + response = self.client.post(self.url, values) self.assertEqual(response.status_code, 302) self.assertEqual(RoleAssignment.objects.all().count(), 3) role_as = RoleAssignment.objects.get( @@ -2837,26 +2379,18 @@ def test_create_delegate(self): } self.assertEqual(model_to_dict(role_as), expected) - def test_create_delegate_limit_reached(self): - """Test RoleAssignment creation with exceeded delegate limit""" + def test_post_delegate_limit_reached(self): + """Test POST with reached delegate limit""" del_user = self.make_user('new_del_user') self.make_assignment(self.project, del_user, self.role_delegate) self.assertEqual(RoleAssignment.objects.all().count(), 3) - values = { 'project': self.project.sodar_uuid, 'user': self.user_new.sodar_uuid, 'role': self.role_delegate.pk, } with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:role_create', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) - + response = self.client.post(self.url, values) self.assertEqual(response.status_code, 200) self.assertEqual(RoleAssignment.objects.all().count(), 3) self.assertIsNone( @@ -2866,26 +2400,18 @@ def test_create_delegate_limit_reached(self): ) @override_settings(PROJECTROLES_DELEGATE_LIMIT=2) - def test_create_delegate_limit_increased(self): - """Test RoleAssignment creation with delegate limit > 1""" + def test_post_delegate_limit_increased(self): + """Test POST with delegate limit > 1""" del_user = self.make_user('new_del_user') self.make_assignment(self.project, del_user, self.role_delegate) self.assertEqual(RoleAssignment.objects.all().count(), 3) - values = { 'project': self.project.sodar_uuid, 'user': self.user_new.sodar_uuid, 'role': self.role_delegate.pk, } with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:role_create', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) - + response = self.client.post(self.url, values) self.assertEqual(response.status_code, 302) self.assertEqual(RoleAssignment.objects.all().count(), 4) self.assertIsNotNone( @@ -2894,27 +2420,19 @@ def test_create_delegate_limit_increased(self): ).first() ) - def test_create_delegate_limit_inherited(self): - """Test creation with existing delegate role for inherited owner""" + def test_post_delegate_limit_inherited(self): + """Test POST with existing delegate role for inherited owner""" self.make_assignment( self.project, self.user_owner_cat, self.role_delegate ) self.assertEqual(RoleAssignment.objects.all().count(), 3) - values = { 'project': self.project.sodar_uuid, 'user': self.user_new.sodar_uuid, 'role': self.role_delegate.pk, } with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:role_create', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) - + response = self.client.post(self.url, values) # NOTE: Limit should be reached, but inherited owner role is disregarded self.assertEqual(response.status_code, 302) self.assertEqual(RoleAssignment.objects.all().count(), 4) @@ -2924,8 +2442,8 @@ def test_create_delegate_limit_inherited(self): ).first() ) - def test_redirect_to_invite(self): - """Test redirects for the ProjectInvite creation view""" + def test_post_autocomplete_redirect_to_invite(self): + """Test POST for redirecting to ProjectInviteCreateView""" values = { 'project': self.project.sodar_uuid, 'role': self.role_guest.pk, @@ -2947,8 +2465,8 @@ def test_redirect_to_invite(self): ), ) - def test_display_options(self): - """Test test displaying options by SODARUserRedirectWidget""" + def test_get_autocomplete_display_options(self): + """Test GET for displaying options by SODARUserRedirectWidget""" values = { 'project': self.project.sodar_uuid, 'role': self.role_guest.pk, @@ -2967,8 +2485,8 @@ def test_display_options(self): data = json.loads(response.content) self.assertIn(new_option, data['results']) - def test_display_options_invalid_email(self): - """Test displaying options with invalid email""" + def test_get_autocomplete_display_options_invalid_email(self): + """Test GET for displaying options with invalid email""" values = { 'project': self.project.sodar_uuid, 'role': self.role_guest.pk, @@ -2987,8 +2505,8 @@ def test_display_options_invalid_email(self): data = json.loads(response.content) self.assertNotIn(new_option, data['results']) - def test_render_promote(self): - """Test rendering for inherited role promotion""" + def test_get_promote(self): + """Test GET with inherited role promotion""" # Assign category guest user for inherit/promote tests guest_as_cat = self.make_assignment( self.category, self.user_new, self.role_guest @@ -3003,7 +2521,6 @@ def test_render_promote(self): }, ) ) - self.assertEqual(response.status_code, 200) self.assertEqual(response.context['promote_as'], guest_as_cat) form = response.context['form'] @@ -3016,8 +2533,8 @@ def test_render_promote(self): [self.role_delegate.pk, self.role_contributor.pk], ) - def test_render_promote_local_role(self): - """Test rendering for promotion with local role (should fail)""" + def test_get_promote_local_role(self): + """Test GET for promotion with local role (should fail)""" guest_as = self.make_assignment( self.project, self.user_new, self.role_guest ) @@ -3040,8 +2557,8 @@ def test_render_promote_local_role(self): ), ) - def test_render_promote_child_role(self): - """Test rendering for promotion with child role (should fail)""" + def test_get_promote_child_role(self): + """Test GET for promotion with child role (should fail)""" # Set up sub category and project with role sub_category = self.make_project( 'SubCategory', PROJECT_TYPE_CATEGORY, self.category @@ -3052,7 +2569,6 @@ def test_render_promote_child_role(self): sub_as = self.make_assignment( sub_project, self.user_new, self.role_guest ) - with self.login(self.user): response = self.client.get( reverse( @@ -3065,8 +2581,8 @@ def test_render_promote_child_role(self): ) self.assertEqual(response.status_code, 302) - def test_render_promote_delegate(self): - """Test rendering for promotion with delegate role (should fail)""" + def test_get_promote_delegate(self): + """Test GET for promotion with delegate role (should fail)""" delegate_as_cat = self.make_assignment( self.category, self.user_new, self.role_delegate ) @@ -3082,8 +2598,8 @@ def test_render_promote_delegate(self): ) self.assertEqual(response.status_code, 302) - def test_render_promote_owner(self): - """Test rendering for promotion with ownere role (should fail)""" + def test_get_promote_owner(self): + """Test GET for promotion with ownere role (should fail)""" with self.login(self.user): response = self.client.get( reverse( @@ -3097,7 +2613,7 @@ def test_render_promote_owner(self): self.assertEqual(response.status_code, 302) def test_post_promote(self): - """Test RoleAssignment creation for promoting inherited role""" + """Test POST for promoting inherited role""" self.make_assignment(self.category, self.user_new, self.role_guest) self.assertEqual(RoleAssignment.objects.all().count(), 3) self.assertEqual( @@ -3114,13 +2630,7 @@ def test_post_promote(self): 'promote': True, } with self.login(self.user): - self.client.post( - reverse( - 'projectroles:role_create', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) + self.client.post(self.url, values) self.assertEqual(RoleAssignment.objects.all().count(), 4) role_as = RoleAssignment.objects.get( @@ -3139,7 +2649,7 @@ def test_post_promote(self): class TestRoleAssignmentUpdateView( ProjectMixin, RoleAssignmentMixin, TestViewsBase ): - """Tests for RoleAssignment update view""" + """Tests for RoleAssignmentUpdateView""" def setUp(self): super().setUp() @@ -3163,20 +2673,18 @@ def setUp(self): self.role_as = self.make_assignment( self.project, self.user_guest, self.role_guest ) - # Get AppAlert model + # Set up helpers self.app_alert_model = get_backend_api('appalerts_backend').get_model() + self.url = reverse( + 'projectroles:role_update', + kwargs={'roleassignment': self.role_as.sodar_uuid}, + ) - def test_render(self): - """Test rendering of RoleAssignment updating form""" + def test_get(self): + """Test RoleAssignmentUpdateView GET""" with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:role_update', - kwargs={'roleassignment': self.role_as.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) - form = response.context['form'] self.assertIsNotNone(form) self.assertIsInstance(form.fields['project'].widget, HiddenInput) @@ -3199,8 +2707,8 @@ def test_render(self): form.fields['role'].choices, ) - def test_render_category(self): - """Test rendering for category""" + def test_get_category(self): + """Test GET for category""" user_new = self.make_user('user_new') new_as = self.make_assignment(self.category, user_new, self.role_guest) with self.login(self.user): @@ -3231,8 +2739,8 @@ def test_render_category(self): form.fields['role'].choices, ) - def test_render_not_found(self): - """Test rendering with invalid assignment UUID""" + def test_get_not_found(self): + """Test GET with invalid assignment UUID""" with self.login(self.user): response = self.client.get( reverse( @@ -3242,8 +2750,8 @@ def test_render_not_found(self): ) self.assertEqual(response.status_code, 404) - def test_update_assignment(self): - """Test RoleAssignment updating""" + def test_post(self): + """Test RoleAssignmentUpdateView POST""" self.assertEqual(RoleAssignment.objects.all().count(), 3) self.assertEqual( self.app_alert_model.objects.filter( @@ -3258,13 +2766,7 @@ def test_update_assignment(self): 'role': self.role_contributor.pk, } with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:role_update', - kwargs={'roleassignment': self.role_as.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url, values) self.assertEqual(RoleAssignment.objects.all().count(), 3) role_as = RoleAssignment.objects.get( @@ -3293,8 +2795,8 @@ def test_update_assignment(self): ), ) - def test_update_delegate(self): - """Test RoleAssignment updating to delegate""" + def test_post_delegate(self): + """Test POST to update RoleAssignment to delegate""" self.assertEqual(RoleAssignment.objects.all().count(), 3) values = { 'project': self.role_as.project.sodar_uuid, @@ -3302,13 +2804,7 @@ def test_update_delegate(self): 'role': self.role_delegate.pk, } with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:role_update', - kwargs={'roleassignment': self.role_as.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url, values) self.assertEqual(response.status_code, 302) self.assertEqual(RoleAssignment.objects.all().count(), 3) @@ -3324,26 +2820,18 @@ def test_update_delegate(self): } self.assertEqual(model_to_dict(role_as), expected) - def test_update_delegate_limit_reached(self): - """Test RoleAssignment updating with exceeded delegate limit""" + def test_post_delegate_limit_reached(self): + """Test POST with reached delegate limit""" del_user = self.make_user('new_del_user') self.make_assignment(self.project, del_user, self.role_delegate) self.assertEqual(RoleAssignment.objects.all().count(), 4) - values = { 'project': self.project.sodar_uuid, 'user': self.user_guest.sodar_uuid, 'role': self.role_delegate.pk, } with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:role_update', - kwargs={'roleassignment': self.role_as.sodar_uuid}, - ), - values, - ) - + response = self.client.post(self.url, values) self.assertEqual(response.status_code, 200) self.assertEqual(RoleAssignment.objects.all().count(), 4) self.assertEqual( @@ -3356,8 +2844,8 @@ def test_update_delegate_limit_reached(self): ) @override_settings(PROJECTROLES_DELEGATE_LIMIT=2) - def test_update_delegate_limit_increased(self): - """Test RoleAssignment updating with delegate limit > 1""" + def test_post_delegate_limit_increased(self): + """Test POST with delegate limit > 1""" del_user = self.make_user('new_del_user') self.make_assignment(self.project, del_user, self.role_delegate) self.assertEqual( @@ -3366,21 +2854,13 @@ def test_update_delegate_limit_increased(self): ).count(), 1, ) - values = { 'project': self.project.sodar_uuid, 'user': self.user_guest.sodar_uuid, 'role': self.role_delegate.pk, } with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:role_update', - kwargs={'roleassignment': self.role_as.sodar_uuid}, - ), - values, - ) - + response = self.client.post(self.url, values) self.assertEqual(response.status_code, 302) self.assertEqual( RoleAssignment.objects.filter( @@ -3389,8 +2869,8 @@ def test_update_delegate_limit_increased(self): 2, ) - def test_update_delegate_limit_inherited(self): - """Test updating with existing delegate role for inherited owner""" + def test_post_delegate_limit_inherited(self): + """Test POST with existing delegate role for inherited owner""" self.make_assignment( self.project, self.user_owner_cat, self.role_delegate ) @@ -3400,21 +2880,13 @@ def test_update_delegate_limit_inherited(self): ).count(), 1, ) - values = { 'project': self.project.sodar_uuid, 'user': self.user_guest.sodar_uuid, 'role': self.role_delegate.pk, } with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:role_update', - kwargs={'roleassignment': self.role_as.sodar_uuid}, - ), - values, - ) - + response = self.client.post(self.url, values) # NOTE: Limit should be reached, but inherited owner role is disregarded self.assertEqual(response.status_code, 302) self.assertEqual( @@ -3424,19 +2896,14 @@ def test_update_delegate_limit_inherited(self): 2, ) - def test_render_inactive_local_role(self): - """Test rendering with inherited role overriding local inactive role""" + def test_get_inactive_local_role(self): + """Test GET with inherited role overriding local inactive role""" # Set user as category contributor self.make_assignment( self.category, self.user_guest, self.role_contributor ) with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:role_update', - kwargs={'roleassignment': self.role_as.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) form = response.context['form'] # Assert only delegate role is selectable @@ -3450,7 +2917,7 @@ def test_render_inactive_local_role(self): class TestRoleAssignmentDeleteView( ProjectMixin, RoleAssignmentMixin, TestViewsBase ): - """Tests for RoleAssignment delete view""" + """Tests for RoleAssignmentDeleteView""" def setUp(self): super().setUp() @@ -3472,25 +2939,24 @@ def setUp(self): self.project, self.user_contrib, self.role_contributor ) self.user_new = self.make_user('user_new') - # Set up appalerts + # Set up helpers self.app_alerts = get_backend_api('appalerts_backend') self.app_alert_model = self.app_alerts.get_model() + self.url = reverse( + 'projectroles:role_delete', + kwargs={'roleassignment': self.contrib_as.sodar_uuid}, + ) - def test_render(self): - """Test rendering RoleAssignment deletion confirmation form""" + def test_get(self): + """Test RoleAssignmentDeleteView GET""" with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:role_delete', - kwargs={'roleassignment': self.contrib_as.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) self.assertIsNone(response.context['inherited_as']) self.assertEqual(response.context['inherited_children'], []) - def test_render_inherit(self): - """Test rendering for user with inherited role""" + def test_get_inherit(self): + """Test GET for user with inherited role""" inh_as = self.make_assignment( self.category, self.user_contrib, self.role_guest ) @@ -3505,8 +2971,8 @@ def test_render_inherit(self): self.assertEqual(response.context['inherited_as'], inh_as) self.assertIsNone(response.context['inherited_children']) - def test_render_children(self): - """Test rendering for user with inherited child roles to be removed""" + def test_get_children(self): + """Test GET for user with inherited child roles to be removed""" inh_as = self.make_assignment( self.category, self.user_new, self.role_guest ) @@ -3521,8 +2987,8 @@ def test_render_children(self): self.assertIsNone(response.context['inherited_as']) self.assertEqual(response.context['inherited_children'], [self.project]) - def test_render_children_finder(self): - """Test rendering for finder user with inherited child roles""" + def test_get_children_finder(self): + """Test GET for finder user with inherited child roles""" inh_as = self.make_assignment( self.category, self.user_new, self.role_finder ) @@ -3537,8 +3003,8 @@ def test_render_children_finder(self): self.assertIsNone(response.context['inherited_as']) self.assertIsNone(response.context['inherited_children']) - def test_render_not_found(self): - """Test rendering with invalid assignment UUID""" + def test_get_not_found(self): + """Test GET with invalid assignment UUID""" with self.login(self.user): response = self.client.get( reverse( @@ -3548,8 +3014,8 @@ def test_render_not_found(self): ) self.assertEqual(response.status_code, 404) - def test_delete(self): - """Test RoleAssignment deleting""" + def test_post(self): + """Test RoleAssignmentDeleteView POST""" alert = self.app_alerts.add_alert( 'projectroles', 'test_alert', @@ -3567,12 +3033,7 @@ def test_delete(self): ) self.assertEqual(len(mail.outbox), 0) with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:role_delete', - kwargs={'roleassignment': self.contrib_as.sodar_uuid}, - ) - ) + response = self.client.post(self.url) self.assertRedirects( response, reverse( @@ -3591,8 +3052,8 @@ def test_delete(self): self.assertEqual(alert.active, False) self.assertEqual(len(mail.outbox), 1) - def test_delete_owner(self): - """Test RoleAssignment owner deletion (should fail)""" + def test_post_owner(self): + """Test POST for RoleAssignment owner deletion (should fail)""" user_owner = self.make_user('user_owner') self.owner_as.user = user_owner # Not a superuser self.owner_as.save() @@ -3609,21 +3070,16 @@ def test_delete_owner(self): self.assertEqual(RoleAssignment.objects.all().count(), 3) self.assertEqual(len(mail.outbox), 0) - def test_delete_delegate(self): - """Test RoleAssignment delegate deleting by contributor (should fail)""" + def test_post_contributor(self): + """Test POST as contributor (should fail)""" self.assertEqual(RoleAssignment.objects.all().count(), 3) with self.login(self.user_contrib): - response = self.client.post( - reverse( - 'projectroles:role_delete', - kwargs={'roleassignment': self.contrib_as.sodar_uuid}, - ) - ) + response = self.client.post(self.url) self.assertEqual(response.status_code, 302) self.assertEqual(RoleAssignment.objects.all().count(), 3) - def test_delete_inherit(self): - """Test RoleAssignment deleting with remaining inherited role""" + def test_post_inherit(self): + """Test POST with existing inherited role""" self.make_assignment(self.category, self.user_contrib, self.role_guest) self.assertEqual(RoleAssignment.objects.all().count(), 4) alert = self.app_alerts.add_alert( @@ -3635,12 +3091,7 @@ def test_delete_inherit(self): ) self.assertEqual(alert.active, True) with self.login(self.user): - self.client.post( - reverse( - 'projectroles:role_delete', - kwargs={'roleassignment': self.contrib_as.sodar_uuid}, - ) - ) + self.client.post(self.url) self.assertEqual(RoleAssignment.objects.all().count(), 3) self.assertEqual( self.app_alert_model.objects.filter( @@ -3657,8 +3108,8 @@ def test_delete_inherit(self): alert.refresh_from_db() self.assertEqual(alert.active, True) - def test_delete_children(self): - """Test RoleAssignment deleting with child categories or projects""" + def test_post_children(self): + """Test POST with child categories or projects""" new_as = self.make_assignment( self.category, self.user_new, self.role_guest ) @@ -3694,8 +3145,8 @@ def test_delete_children(self): alert.refresh_from_db() self.assertEqual(alert.active, False) - def test_delete_app_settings_contributor(self): - """Test deletion of project-user app settings after RoleAssignment contributor deletion""" + def test_post_app_settings_contributor(self): + """Test post with PROJECT_USER app settings after contributor deletion""" self.assertEqual(RoleAssignment.objects.all().count(), 3) app_settings.set( app_name=EXAMPLE_APP_NAME, @@ -3713,12 +3164,7 @@ def test_delete_app_settings_contributor(self): ) ) with self.login(self.user): - self.client.post( - reverse( - 'projectroles:role_delete', - kwargs={'roleassignment': self.contrib_as.sodar_uuid}, - ) - ) + self.client.post(self.url) self.assertEqual(RoleAssignment.objects.all().count(), 2) self.assertEqual( app_settings.get( @@ -3730,8 +3176,8 @@ def test_delete_app_settings_contributor(self): False, ) - def test_delete_app_settings_inherit(self): - """Test deleting project-user app setting after role delete with inherited role""" + def test_post_app_settings_inherit(self): + """Test POST with PROJECT_USER app setting with inherited role""" self.make_assignment(self.category, self.user_contrib, self.role_guest) self.assertEqual(RoleAssignment.objects.all().count(), 4) app_settings.set( @@ -3750,12 +3196,7 @@ def test_delete_app_settings_inherit(self): ) ) with self.login(self.user): - self.client.post( - reverse( - 'projectroles:role_delete', - kwargs={'roleassignment': self.contrib_as.sodar_uuid}, - ) - ) + self.client.post(self.url) self.assertEqual(RoleAssignment.objects.all().count(), 3) self.assertEqual( app_settings.get( @@ -3767,8 +3208,8 @@ def test_delete_app_settings_inherit(self): False, ) - def test_delete_app_settings_children(self): - """Test deleting project-user app setting after role delete with child categories or projects""" + def test_post_app_settings_children(self): + """Test POST with PROJECT_USER app setting with child categories or projects""" new_as = self.make_assignment( self.category, self.user_new, self.role_guest ) @@ -3810,6 +3251,8 @@ def test_delete_app_settings_children(self): class TestRoleAssignmentOwnerTransferView( ProjectMixin, RoleAssignmentMixin, TestViewsBase ): + """Tests for RoleAssignmentOwnerTransferView""" + def setUp(self): super().setUp() # Set up category and project @@ -3834,20 +3277,19 @@ def setUp(self): ) # User without roles self.user_new = self.make_user('user_new') - # Get AppAlert model + # Set up helpers self.app_alert_model = get_backend_api('appalerts_backend').get_model() + self.url = reverse( + 'projectroles:role_owner_transfer', + kwargs={'project': self.project.sodar_uuid}, + ) - def test_render(self): - """Test rendering ownership transfer form""" + def test_get(self): + """Test RoleAssignmentOwnerTransferView GET""" self.assertEqual(self.app_alert_model.objects.count(), 0) self.assertEqual(len(mail.outbox), 0) with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:role_owner_transfer', - kwargs={'project': self.project.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) form = response.context['form'] self.assertIsNotNone(form.fields.get('old_owner_role')) @@ -3858,19 +3300,14 @@ def test_render(self): [c[0] for c in form.fields['old_owner_role'].choices], ) - def test_render_old_inherited_member(self): - """Test rendering with inherited non-owner role for old owner""" + def test_get_old_inherited_member(self): + """Test GET with inherited non-owner role for old owner""" self.make_assignment( self.category, self.user_owner, self.role_contributor ) self.assertEqual(self.project.get_role(self.user_owner), self.owner_as) with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:role_owner_transfer', - kwargs={'project': self.project.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) form = response.context['form'] # Only delegate and contributor roles allowed @@ -3880,18 +3317,13 @@ def test_render_old_inherited_member(self): ) self.assertEqual(form.fields['old_owner_role'].disabled, False) - def test_render_old_inherited_owner(self): - """Test rendering with inherited owner role for old owner""" + def test_get_old_inherited_owner(self): + """Test GET with inherited owner role for old owner""" self.owner_as_cat.user = self.user_owner self.owner_as_cat.save() self.assertEqual(self.project.get_role(self.user_owner), self.owner_as) with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:role_owner_transfer', - kwargs={'project': self.project.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) form = response.context['form'] # Only owner role included @@ -3901,8 +3333,8 @@ def test_render_old_inherited_owner(self): ) self.assertEqual(form.fields['old_owner_role'].disabled, True) - def test_render_category(self): - """Test rendering for category""" + def test_get_category(self): + """Test GET for category""" self.make_assignment(self.category, self.user_new, self.role_guest) with self.login(self.user): response = self.client.get( @@ -3921,16 +3353,13 @@ def test_render_category(self): [c[0] for c in form.fields['old_owner_role'].choices], ) - def test_transfer(self): - """Test ownership transfer""" + def test_post(self): + """Test RoleAssignmentOwnerTransferView POST""" self.assertEqual(self.app_alert_model.objects.count(), 0) self.assertEqual(len(mail.outbox), 0) with self.login(self.user): response = self.client.post( - reverse( - 'projectroles:role_owner_transfer', - kwargs={'project': self.project.sodar_uuid}, - ), + self.url, data={ 'project': self.project.sodar_uuid, 'new_owner': self.user_guest.sodar_uuid, @@ -3948,14 +3377,11 @@ def test_transfer(self): self.assertEqual(self.app_alert_model.objects.count(), 2) self.assertEqual(len(mail.outbox), 2) - def test_transfer_as_old_owner(self): - """Test ownership transfer as old owner""" + def test_post_as_old_owner(self): + """Test POST as old owner""" with self.login(self.user_owner): response = self.client.post( - reverse( - 'projectroles:role_owner_transfer', - kwargs={'project': self.project.sodar_uuid}, - ), + self.url, data={ 'project': self.project.sodar_uuid, 'new_owner': self.user_guest.sodar_uuid, @@ -3974,18 +3400,15 @@ def test_transfer_as_old_owner(self): self.assertEqual(self.app_alert_model.objects.count(), 1) self.assertEqual(len(mail.outbox), 1) - def test_transfer_old_inherited_member(self): - """Test transfer from old owner with inherited non-owner role""" + def test_post_old_inherited_member(self): + """Test POST to transfer from old owner with inherited non-owner role""" self.make_assignment( self.category, self.user_owner, self.role_contributor ) self.assertEqual(self.project.get_role(self.user_owner), self.owner_as) - with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:role_owner_transfer', - kwargs={'project': self.project.sodar_uuid}, - ), + with self.login(self.user): + response = self.client.post( + self.url, data={ 'project': self.project.sodar_uuid, 'new_owner': self.user_guest.sodar_uuid, @@ -4000,17 +3423,14 @@ def test_transfer_old_inherited_member(self): self.assertEqual(self.app_alert_model.objects.count(), 2) self.assertEqual(len(mail.outbox), 2) - def test_transfer_old_inherited_owner(self): - """Test transfer from old owner with inherited owner role""" + def test_post_old_inherited_owner(self): + """Test POST to transfer from old owner with inherited owner role""" self.owner_as_cat.user = self.user_owner self.owner_as_cat.save() self.assertEqual(self.project.get_role(self.user_owner), self.owner_as) with self.login(self.user): response = self.client.post( - reverse( - 'projectroles:role_owner_transfer', - kwargs={'project': self.project.sodar_uuid}, - ), + self.url, data={ 'project': self.project.sodar_uuid, 'new_owner': self.user_guest.sodar_uuid, @@ -4032,18 +3452,15 @@ def test_transfer_old_inherited_owner(self): self.assertEqual(self.app_alert_model.objects.count(), 1) self.assertEqual(len(mail.outbox), 1) - def test_transfer_new_inherited_member(self): - """Test transfer to new owner with inherited non-owner role""" + def test_post_new_inherited_member(self): + """Test POST to transfer to new owner with inherited non-owner role""" self.make_assignment( self.category, self.user_new, self.role_contributor ) self.assertEqual(self.project.get_role(self.user_owner), self.owner_as) with self.login(self.user): response = self.client.post( - reverse( - 'projectroles:role_owner_transfer', - kwargs={'project': self.project.sodar_uuid}, - ), + self.url, data={ 'project': self.project.sodar_uuid, 'new_owner': self.user_new.sodar_uuid, @@ -4058,18 +3475,15 @@ def test_transfer_new_inherited_member(self): self.assertEqual(self.app_alert_model.objects.count(), 2) self.assertEqual(len(mail.outbox), 2) - def test_transfer_new_inherited_owner(self): - """Test transfer to new owner with inherited owner role""" + def test_post_new_inherited_owner(self): + """Test POST to transfer to new owner with inherited owner role""" self.assertEqual( self.project.get_role(self.user_owner_cat), self.owner_as_cat ) self.assertEqual(self.project.get_role(self.user_owner), self.owner_as) with self.login(self.user): response = self.client.post( - reverse( - 'projectroles:role_owner_transfer', - kwargs={'project': self.project.sodar_uuid}, - ), + self.url, data={ 'project': self.project.sodar_uuid, 'new_owner': self.user_owner_cat.sodar_uuid, @@ -4092,18 +3506,15 @@ def test_transfer_new_inherited_owner(self): self.assertEqual(self.app_alert_model.objects.count(), 1) self.assertEqual(len(mail.outbox), 1) - def test_transfer_new_local_role(self): - """Test transfer to new owner with overridden local role""" + def test_post_new_local_role(self): + """Test POST to transfer to new owner with overridden local role""" new_as = self.make_assignment( self.project, self.user_new, self.role_contributor ) self.assertEqual(self.project.get_role(self.user_owner), self.owner_as) with self.login(self.user): response = self.client.post( - reverse( - 'projectroles:role_owner_transfer', - kwargs={'project': self.project.sodar_uuid}, - ), + self.url, data={ 'project': self.project.sodar_uuid, 'new_owner': self.user_new.sodar_uuid, @@ -4123,7 +3534,7 @@ def test_transfer_new_local_role(self): class TestProjectInviteCreateView( ProjectMixin, RoleAssignmentMixin, ProjectInviteMixin, TestViewsBase ): - """Tests for ProjectInvite creation view""" + """Tests for ProjectInviteCreateView""" def setUp(self): super().setUp() @@ -4134,18 +3545,16 @@ def setUp(self): self.project, self.user, self.role_owner ) self.user_new = self.make_user('user_new') + self.url = reverse( + 'projectroles:invite_create', + kwargs={'project': self.project.sodar_uuid}, + ) - def test_render(self): - """Test rendering ProjectInvite creation form""" + def test_get(self): + """Test ProjectInviteCreateView GET""" with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:invite_create', - kwargs={'project': self.project.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) - form = response.context['form'] self.assertIsNotNone(form) # Assert owner role is not selectable @@ -4154,22 +3563,15 @@ def test_render(self): form.fields['role'].choices, ) - def test_render_from_roleassignment(self): - """Test rendering with forwarded values from RoleAssignment Form""" + def test_get_from_roleassignment(self): + """Test GET with forwarded values from RoleAssignment Form""" values = { 'e': 'test@example.com', 'r': self.role_contributor.pk, } with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:invite_create', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) + response = self.client.get(self.url, values) self.assertEqual(response.status_code, 200) - form = response.context['form'] self.assertIsNotNone(form) # Assert owner role is not selectable @@ -4183,8 +3585,8 @@ def test_render_from_roleassignment(self): ) self.assertEqual(form.fields['email'].initial, 'test@example.com') - def test_render_not_found(self): - """Test rendering with invalid project UUID""" + def test_get_not_found(self): + """Test GET with invalid project UUID""" with self.login(self.user): response = self.client.get( reverse( @@ -4194,25 +3596,58 @@ def test_render_not_found(self): ) self.assertEqual(response.status_code, 404) - @override_settings( - PROJECTROLES_ALLOW_LOCAL_USERS=False, - ENABLE_SAML=False, - ) - def test_local_users_not_allowed(self): - """Test creation for local user with local users not allowed""" + def test_post(self): + """Test ProjectInviteCreateView POST""" + self.assertEqual(ProjectInvite.objects.all().count(), 0) values = { 'email': INVITE_EMAIL, 'project': self.project.pk, 'role': self.role_contributor.pk, } with self.login(self.user): - response = self.client.post( + response = self.client.post(self.url, values) + + self.assertEqual(ProjectInvite.objects.all().count(), 1) + invite = ProjectInvite.objects.get( + project=self.project, email=INVITE_EMAIL, active=True + ) + self.assertIsNotNone(invite) + expected = { + 'id': invite.pk, + 'project': self.project.pk, + 'email': INVITE_EMAIL, + 'role': self.role_contributor.pk, + 'issuer': self.user.pk, + 'message': '', + 'date_expire': invite.date_expire, + 'secret': invite.secret, + 'active': True, + 'sodar_uuid': invite.sodar_uuid, + } + self.assertEqual(model_to_dict(invite), expected) + # Assert redirect + with self.login(self.user): + self.assertRedirects( + response, reverse( - 'projectroles:invite_create', + 'projectroles:invites', kwargs={'project': self.project.sodar_uuid}, ), - values, ) + + @override_settings( + PROJECTROLES_ALLOW_LOCAL_USERS=False, + ENABLE_SAML=False, + ) + def test_post_local_users_not_allowed(self): + """Test POST for local user with local users not allowed""" + values = { + 'email': INVITE_EMAIL, + 'project': self.project.pk, + 'role': self.role_contributor.pk, + } + with self.login(self.user): + response = self.client.post(self.url, values) self.assertEqual(response.status_code, 200) self.assertEqual(ProjectInvite.objects.all().count(), 0) @@ -4220,21 +3655,15 @@ def test_local_users_not_allowed(self): PROJECTROLES_ALLOW_LOCAL_USERS=True, ENABLE_SAML=False, ) - def test_local_users_allowed(self): - """Test creation for local user with local users allowed""" + def test_post_local_users_allowed(self): + """Test POST for local user with local users allowed""" values = { 'email': INVITE_EMAIL, 'project': self.project.pk, 'role': self.role_contributor.pk, } with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:invite_create', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url, values) self.assertEqual(response.status_code, 302) invite = ProjectInvite.objects.get( project=self.project, email=INVITE_EMAIL, active=True @@ -4247,21 +3676,15 @@ def test_local_users_allowed(self): ENABLE_LDAP=True, AUTH_LDAP_USERNAME_DOMAIN='EXAMPLE', ) - def test_local_users_email_domain(self): - """Test creation for local user with email domain in AUTH_LDAP_USERNAME_DOMAIN""" + def test_post_local_users_email_domain(self): + """Test POST for local user with email domain in AUTH_LDAP_USERNAME_DOMAIN""" values = { 'email': INVITE_EMAIL, 'project': self.project.pk, 'role': self.role_contributor.pk, } with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:invite_create', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url, values) self.assertEqual(response.status_code, 302) invite = ProjectInvite.objects.get( project=self.project, email=INVITE_EMAIL, active=True @@ -4274,77 +3697,26 @@ def test_local_users_email_domain(self): ENABLE_LDAP=True, LDAP_ALT_DOMAINS=['example.com'], ) - def test_local_users_email_domain_ldap(self): - """Test creation for local user with email domain in LDAP_ALT_DOMAINS""" + def test_post_local_users_email_domain_ldap(self): + """Test POST for local user with email domain in LDAP_ALT_DOMAINS""" values = { 'email': INVITE_EMAIL, 'project': self.project.pk, 'role': self.role_contributor.pk, } with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:invite_create', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url, values) self.assertEqual(response.status_code, 302) invite = ProjectInvite.objects.get( project=self.project, email=INVITE_EMAIL, active=True ) self.assertIsNotNone(invite) - def test_create_invite(self): - """Test ProjectInvite creation""" - self.assertEqual(ProjectInvite.objects.all().count(), 0) - values = { - 'email': INVITE_EMAIL, - 'project': self.project.pk, - 'role': self.role_contributor.pk, - } - with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:invite_create', - kwargs={'project': self.project.sodar_uuid}, - ), - values, - ) - - self.assertEqual(ProjectInvite.objects.all().count(), 1) - invite = ProjectInvite.objects.get( - project=self.project, email=INVITE_EMAIL, active=True - ) - self.assertIsNotNone(invite) - expected = { - 'id': invite.pk, - 'project': self.project.pk, - 'email': INVITE_EMAIL, - 'role': self.role_contributor.pk, - 'issuer': self.user.pk, - 'message': '', - 'date_expire': invite.date_expire, - 'secret': invite.secret, - 'active': True, - 'sodar_uuid': invite.sodar_uuid, - } - self.assertEqual(model_to_dict(invite), expected) - # Assert redirect - with self.login(self.user): - self.assertRedirects( - response, - reverse( - 'projectroles:invites', - kwargs={'project': self.project.sodar_uuid}, - ), - ) - class TestProjectInviteAcceptView( ProjectMixin, RoleAssignmentMixin, ProjectInviteMixin, TestViewsBase ): - """Tests for ProjectInvite accept view""" + """Tests for ProjectInviteAcceptView and related helper views""" def setUp(self): super().setUp() @@ -4357,8 +3729,8 @@ def setUp(self): self.user_new = self.make_user('user_new') @override_settings(ENABLE_LDAP=True, AUTH_LDAP_USERNAME_DOMAIN='EXAMPLE') - def test_accept_ldap(self): - """Test accepting LDAP invite""" + def test_get_ldap(self): + """Test ProjectInviteAcceptView GET with LDAP invite""" invite = self.make_invite( email=INVITE_EMAIL, project=self.project, @@ -4384,7 +3756,6 @@ def test_accept_ldap(self): ), follow=True, ) - self.assertListEqual( response.redirect_chain, [ @@ -4420,8 +3791,8 @@ def test_accept_ldap(self): LDAP_ALT_DOMAINS=['alt.org'], PROJECTROLES_ALLOW_LOCAL_USERS=False, ) - def test_accept_ldap_alt(self): - """Test accepting LDAP invite with email in LDAP_ALT_DOMAINS""" + def test_get_ldap_alt_domain(self): + """Test GET with LDAP invite and email in LDAP_ALT_DOMAINS""" alt_email = 'user@alt.org' invite = self.make_invite( email=alt_email, @@ -4448,7 +3819,6 @@ def test_accept_ldap_alt(self): ), follow=True, ) - self.assertListEqual( response.redirect_chain, [ @@ -4484,8 +3854,8 @@ def test_accept_ldap_alt(self): LDAP_ALT_DOMAINS=[], PROJECTROLES_ALLOW_LOCAL_USERS=False, ) - def test_accept_ldap_alt_not_listed(self): - """Test accepting LDAP invite with alt email not in LDAP_ALT_DOMAINS""" + def test_get_ldap_email_not_listed(self): + """Test GET with LDAP invite and email not in LDAP_ALT_DOMAINS""" alt_email = 'user@alt.org' invite = self.make_invite( email=alt_email, @@ -4512,7 +3882,6 @@ def test_accept_ldap_alt_not_listed(self): ), follow=True, ) - self.assertListEqual(response.redirect_chain, [(reverse('home'), 302)]) self.assertEqual(ProjectInvite.objects.filter(active=True).count(), 1) self.assertEqual( @@ -4525,8 +3894,8 @@ def test_accept_ldap_alt_not_listed(self): ) @override_settings(AUTH_LDAP_USERNAME_DOMAIN='EXAMPLE', ENABLE_LDAP=True) - def test_accept_ldap_expired(self): - """Test accepting expired LDAP invite""" + def test_get_ldap_expired(self): + """Test GET with expired LDAP invite""" invite = self.make_invite( email=INVITE_EMAIL, project=self.project, @@ -4553,7 +3922,6 @@ def test_accept_ldap_expired(self): ), follow=True, ) - self.assertListEqual( response.redirect_chain, [ @@ -4578,9 +3946,8 @@ def test_accept_ldap_expired(self): ) @override_settings(PROJECTROLES_ALLOW_LOCAL_USERS=True) - def test_accept_local(self): - """Test accepting local invite with nonexistent user and no user logged in""" - # Init invite + def test_get_local(self): + """Test GET with local invite and nonexistent user with no user logged in""" invite = self.make_invite( email=INVITE_EMAIL, project=self.project, @@ -4674,8 +4041,8 @@ def test_accept_local(self): self.assertEqual(response.status_code, 200) @override_settings(PROJECTROLES_ALLOW_LOCAL_USERS=True) - def test_accept_expired_local(self): - """Test accepting expired local invite""" + def test_get_expired_local(self): + """Test GET with expired local invite""" invite = self.make_invite( email=INVITE_EMAIL, project=self.project, @@ -4731,8 +4098,8 @@ def test_accept_expired_local(self): AUTH_LDAP_USERNAME_DOMAIN='EXAMPLE', AUTH_LDAP_DOMAIN_PRINTABLE='EXAMPLE', ) - def test_accept_wrong_type_local(self): - """Test accepting local invite in LDAP processing view""" + def test_get_process_ldap_wrong_type_local(self): + """Test ProjectInviteProcessLDAPView GET with local invite""" invite = self.make_invite( email='test@different.com', project=self.project, @@ -4764,8 +4131,8 @@ def test_accept_wrong_type_local(self): AUTH_LDAP_USERNAME_DOMAIN='EXAMPLE', AUTH_LDAP_DOMAIN_PRINTABLE='EXAMPLE', ) - def test_accept_wrong_type_ldap(self): - """Test accepting LDAP invite in local invite processing view""" + def test_get_process_local_wrong_type_ldap(self): + """Test ProjectInviteProcessLocalView GET with LDAP invite""" invite = self.make_invite( email=INVITE_EMAIL, project=self.project, @@ -4790,8 +4157,8 @@ def test_accept_wrong_type_ldap(self): ) @override_settings(PROJECTROLES_ALLOW_LOCAL_USERS=False) - def test_accept_local_user_not_allowed(self): - """Test accepting local invite with local users disabled""" + def test_get_local_user_disabled(self): + """Test GET with local users disabled""" invite = self.make_invite( email=INVITE_EMAIL, project=self.project, @@ -4800,7 +4167,6 @@ def test_accept_local_user_not_allowed(self): message='', ) self.assertEqual(ProjectInvite.objects.filter(active=True).count(), 1) - response = self.client.get( reverse( 'projectroles:invite_accept', @@ -4814,8 +4180,8 @@ def test_accept_local_user_not_allowed(self): ) @override_settings(PROJECTROLES_ALLOW_LOCAL_USERS=False) - def test_process_local_user_not_allowed(self): - """Test processing local invite with local users disabled""" + def test_get_process_local_disabled(self): + """Test ProjectInviteProcessLocalView GET with local users disabled""" invite = self.make_invite( email=INVITE_EMAIL, project=self.project, @@ -4840,8 +4206,8 @@ def test_process_local_user_not_allowed(self): ) @override_settings(PROJECTROLES_ALLOW_LOCAL_USERS=True) - def test_accept_no_local_user_different_user_logged_in(self): - """Test processing local invite with nonexistent user and different user logged in""" + def test_get_process_local_no_user_different_user_logged(self): + """Test ProjectInviteProcessLocalView GET with nonexistent user and different user logged in""" invite = self.make_invite( email=INVITE_EMAIL, project=self.project, @@ -4865,8 +4231,8 @@ def test_accept_no_local_user_different_user_logged_in(self): ) @override_settings(PROJECTROLES_ALLOW_LOCAL_USERS=True) - def test_accept_local_user_exists_different_user_logged_in(self): - """Test processing local invite with existing user and different user logged in""" + def test_get_process_local_user_exists_different_user_logged(self): + """Test ProjectInviteProcessLocalView GET with existing user and different user logged in""" invited_user = self.make_user(INVITE_EMAIL.split('@')[0]) invited_user.email = INVITE_EMAIL invited_user.save() @@ -4893,8 +4259,8 @@ def test_accept_local_user_exists_different_user_logged_in(self): ) @override_settings(PROJECTROLES_ALLOW_LOCAL_USERS=True) - def test_accept_local_user_exists_is_logged_in(self): - """Test processing local invite with existing and logged in user""" + def test_get_process_local_user_exists_is_logged(self): + """Test ProjectInviteProcessLocalView GET with with existing and logged in user""" invited_user = self.make_user(INVITE_EMAIL.split('@')[0]) invited_user.email = INVITE_EMAIL invited_user.save() @@ -4931,8 +4297,8 @@ def test_accept_local_user_exists_is_logged_in(self): ) @override_settings(PROJECTROLES_ALLOW_LOCAL_USERS=True) - def test_accept_local_user_exists_not_logged_in(self): - """Test processing local invite with existing user exists and no user logged in""" + def test_get_process_local_user_exists_not_logged(self): + """Test ProjectInviteProcessLocalView GET with existing user and no user logged in""" invited_user = self.make_user(INVITE_EMAIL.split('@')[0]) invited_user.email = INVITE_EMAIL invited_user.save() @@ -4967,8 +4333,8 @@ def test_accept_local_user_exists_not_logged_in(self): MSG_INVITE_USER_EXISTS, ) - def test_accept_role_exists(self): - """Test accepting invite for user with roles in project""" + def test_get_role_exists(self): + """Test GET for user with roles in project""" invited_user = self.make_user(INVITE_EMAIL.split('@')[0]) invited_user.email = INVITE_EMAIL invited_user.save() @@ -4998,7 +4364,7 @@ def test_accept_role_exists(self): class TestProjectInviteListView( ProjectMixin, RoleAssignmentMixin, ProjectInviteMixin, TestViewsBase ): - """Tests for ProjectInvite list view""" + """Tests for ProjectInviteListView""" def setUp(self): super().setUp() @@ -5016,8 +4382,8 @@ def setUp(self): message='', ) - def test_render(self): - """Test rendering ProjectInvite list form""" + def test_get(self): + """Test ProjectInviteListView GET""" with self.login(self.user): response = self.client.get( reverse( @@ -5027,8 +4393,8 @@ def test_render(self): ) self.assertEqual(response.status_code, 200) - def test_render_not_found(self): - """Test rendering ProjectInvite list form with invalid project UUID""" + def test_get_not_found(self): + """Test GET with invalid project UUID""" with self.login(self.user): response = self.client.get( reverse( @@ -5042,7 +4408,7 @@ def test_render_not_found(self): class TestProjectInviteRevokeView( ProjectMixin, RoleAssignmentMixin, ProjectInviteMixin, TestViewsBase ): - """Tests for ProjectInvite revocation view""" + """Tests for ProjectInviteRevokeView""" def setUp(self): super().setUp() @@ -5059,20 +4425,19 @@ def setUp(self): issuer=self.user, message='', ) + self.url = reverse( + 'projectroles:invite_revoke', + kwargs={'projectinvite': self.invite.sodar_uuid}, + ) - def test_render(self): - """Test rendering ProjectInvite revocation form""" + def test_get(self): + """Test ProjectInviteRevokeView GET""" with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:invite_revoke', - kwargs={'projectinvite': self.invite.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) - def test_render_not_found(self): - """Test rendering with invalid invite UUID""" + def test_get_not_found(self): + """Test GET with invalid invite UUID""" with self.login(self.user): response = self.client.get( reverse( @@ -5082,48 +4447,33 @@ def test_render_not_found(self): ) self.assertEqual(response.status_code, 404) - def test_revoke_invite(self): - """Test invite revocation""" + def test_post_invite(self): + """Test ProjectInviteRevokeView POST""" self.assertEqual(ProjectInvite.objects.all().count(), 1) self.assertEqual(ProjectInvite.objects.filter(active=True).count(), 1) with self.login(self.user): - self.client.post( - reverse( - 'projectroles:invite_revoke', - kwargs={'projectinvite': self.invite.sodar_uuid}, - ) - ) + self.client.post(self.url) self.assertEqual(ProjectInvite.objects.all().count(), 1) self.assertEqual(ProjectInvite.objects.filter(active=True).count(), 0) - def test_revoke_delegate(self): - """Test invite revocation for a delegate role""" + def test_post_delegate(self): + """Test POST with delegate role""" self.invite.role = self.role_delegate self.invite.save() self.assertEqual(ProjectInvite.objects.filter(active=True).count(), 1) with self.login(self.user): - self.client.post( - reverse( - 'projectroles:invite_revoke', - kwargs={'projectinvite': self.invite.sodar_uuid}, - ) - ) + self.client.post(self.url) self.assertEqual(ProjectInvite.objects.filter(active=True).count(), 0) - def test_revoke_delegate_no_perms(self): - """Test delegate revocation with insufficient perms (should fail)""" + def test_post_delegate_no_perms(self): + """Test POST with delegate role and insufficient perms (should fail)""" self.invite.role = self.role_delegate self.invite.save() user_delegate = self.make_user('user_delegate') self.make_assignment(self.project, user_delegate, self.role_delegate) self.assertEqual(ProjectInvite.objects.filter(active=True).count(), 1) with self.login(user_delegate): - self.client.post( - reverse( - 'projectroles:invite_revoke', - kwargs={'projectinvite': self.invite.sodar_uuid}, - ) - ) + self.client.post(self.url) self.assertEqual(ProjectInvite.objects.filter(active=True).count(), 1) @@ -5131,7 +4481,7 @@ def test_revoke_delegate_no_perms(self): class TestRemoteSiteListView(RemoteSiteMixin, TestViewsBase): - """Tests for remote site list view""" + """Tests for RemoteSiteListView""" def setUp(self): super().setUp() @@ -5143,40 +4493,43 @@ def setUp(self): description=REMOTE_SITE_DESC, secret=REMOTE_SITE_SECRET, ) + self.url = reverse('projectroles:remote_sites') - def test_render_as_source(self): - """Test rendering the remote site list view as source""" + def test_get_source(self): + """Test RemoteSiteListView GET as source""" with self.login(self.user): - response = self.client.get(reverse('projectroles:remote_sites')) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) self.assertEqual(response.context['sites'].count(), 1) # 1 target site @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET) - def test_render_as_target(self): - """Test rendering the remote site list view as target""" + def test_get_target(self): + """Test GET as target""" with self.login(self.user): - response = self.client.get(reverse('projectroles:remote_sites')) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) - self.assertEqual(response.context['sites'].count(), 0) # 1 source sites + self.assertEqual(response.context['sites'].count(), 0) # TODO: Remove this once #76 is done @override_settings(PROJECTROLES_DISABLE_CATEGORIES=True) - def test_render_disable_categories(self): - """Test rendering the remote site list view with categories disabled""" + def test_get_disable_categories(self): + """Test GET with categories disabled""" with self.login(self.user): - response = self.client.get(reverse('projectroles:remote_sites')) + response = self.client.get(self.url) self.assertRedirects(response, reverse('home')) class TestRemoteSiteCreateView(RemoteSiteMixin, TestViewsBase): - """Tests for remote site create view""" + """Tests for RemoteSiteCreateView""" + + def setUp(self): + super().setUp() + self.url = reverse('projectroles:remote_site_create') - def test_render_as_source(self): - """Test rendering remote site create view as source""" + def test_get_source(self): + """Test RemoteSiteCreateView GET as source""" with self.login(self.user): - response = self.client.get( - reverse('projectroles:remote_site_create') - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) form = response.context['form'] self.assertIsNotNone(form) @@ -5184,12 +4537,10 @@ def test_render_as_source(self): self.assertEqual(form.fields['secret'].widget.attrs['readonly'], True) @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET) - def test_render_as_target(self): - """Test rendering remote site create view as target""" + def test_get_target(self): + """Test GET as target""" with self.login(self.user): - response = self.client.get( - reverse('projectroles:remote_site_create') - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) form = response.context['form'] self.assertIsNotNone(form) @@ -5197,8 +4548,8 @@ def test_render_as_target(self): self.assertNotIn('readonly', form.fields['secret'].widget.attrs) @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET) - def test_render_as_target_existing(self): - """Test rendering as target with existing source (should fail)""" + def test_get_target_existing(self): + """Test GET as target with existing source (should fail)""" # Create source site self.source_site = self.make_site( name=REMOTE_SITE_NAME, @@ -5208,13 +4559,11 @@ def test_render_as_target_existing(self): secret=REMOTE_SITE_SECRET, ) with self.login(self.user): - response = self.client.get( - reverse('projectroles:remote_site_create') - ) + response = self.client.get(self.url) self.assertRedirects(response, reverse('projectroles:remote_sites')) - def test_create_target(self): - """Test creating a target site""" + def test_post_source(self): + """Test RemoteSiteCreateView POST as source site""" self.assertEqual( 0, ProjectEvent.objects.filter( @@ -5230,9 +4579,7 @@ def test_create_target(self): 'user_display': REMOTE_SITE_USER_DISPLAY, } with self.login(self.user): - response = self.client.post( - reverse('projectroles:remote_site_create'), values - ) + response = self.client.post(self.url, values) self.assertEqual(RemoteSite.objects.all().count(), 1) site = RemoteSite.objects.first() @@ -5257,8 +4604,8 @@ def test_create_target(self): self.assertRedirects(response, reverse('projectroles:remote_sites')) @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET) - def test_create_source(self): - """Test creating a source site as target""" + def test_post_target(self): + """Test POST as target""" self.assertEqual( 0, ProjectEvent.objects.filter(event_name='source_site_set').count(), @@ -5272,9 +4619,8 @@ def test_create_source(self): 'user_display': REMOTE_SITE_USER_DISPLAY, } with self.login(self.user): - response = self.client.post( - reverse('projectroles:remote_site_create'), values - ) + response = self.client.post(self.url, values) + self.assertRedirects(response, reverse('projectroles:remote_sites')) self.assertEqual(RemoteSite.objects.all().count(), 1) site = RemoteSite.objects.first() @@ -5295,11 +4641,9 @@ def test_create_source(self): event_name='source_site_set' ).first() self.assertEqual(tl_event.event_name, 'source_site_set') - with self.login(self.user): - self.assertRedirects(response, reverse('projectroles:remote_sites')) - def test_create_target_existing_name(self): - """Test creating a target site with an existing name""" + def test_post_source_existing_name(self): + """Test POST with an existing name""" self.target_site = self.make_site( name=REMOTE_SITE_NAME, url=REMOTE_SITE_URL, @@ -5317,15 +4661,13 @@ def test_create_target_existing_name(self): 'user_display': REMOTE_SITE_USER_DISPLAY, } with self.login(self.user): - response = self.client.post( - reverse('projectroles:remote_site_create'), values - ) + response = self.client.post(self.url, values) self.assertEqual(response.status_code, 200) self.assertEqual(RemoteSite.objects.all().count(), 1) class TestRemoteSiteUpdateView(RemoteSiteMixin, TestViewsBase): - """Tests for remote site update view""" + """Tests for RemoteSiteUpdateView""" def setUp(self): super().setUp() @@ -5337,16 +4679,15 @@ def setUp(self): description=REMOTE_SITE_DESC, secret=REMOTE_SITE_SECRET, ) + self.url = reverse( + 'projectroles:remote_site_update', + kwargs={'remotesite': self.target_site.sodar_uuid}, + ) - def test_render(self): - """Test rendering remote site update view as source""" + def test_get(self): + """Test RemoteSiteUpdateView GET as source""" with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:remote_site_update', - kwargs={'remotesite': self.target_site.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) form = response.context['form'] self.assertIsNotNone(form) @@ -5357,8 +4698,8 @@ def test_render(self): self.assertEqual(form.fields['secret'].widget.attrs['readonly'], True) self.assertEqual(form['user_display'].initial, REMOTE_SITE_USER_DISPLAY) - def test_render_not_found(self): - """Test rendering remote site update view with invalid site UUID""" + def test_get_not_found(self): + """Test GET with invalid site UUID""" with self.login(self.user): response = self.client.get( reverse( @@ -5368,8 +4709,8 @@ def test_render_not_found(self): ) self.assertEqual(response.status_code, 404) - def test_update(self): - """Test updating target site as source""" + def test_post(self): + """Test RemoteSiteUpdateView POST as source""" self.assertEqual( 0, ProjectEvent.objects.filter( @@ -5385,13 +4726,7 @@ def test_update(self): 'user_display': REMOTE_SITE_USER_DISPLAY, } with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:remote_site_update', - kwargs={'remotesite': self.target_site.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url, values) self.assertEqual(RemoteSite.objects.all().count(), 1) site = RemoteSite.objects.first() @@ -5415,8 +4750,8 @@ def test_update(self): with self.login(self.user): self.assertRedirects(response, reverse('projectroles:remote_sites')) - def test_update_existing_name(self): - """Test creating target site with an existing name as source (should fail)""" + def test_post_existing_name(self): + """Test POST with existing name (should fail)""" new_target_site = self.make_site( name=REMOTE_SITE_NEW_NAME, url=REMOTE_SITE_NEW_URL, @@ -5425,7 +4760,6 @@ def test_update_existing_name(self): secret=REMOTE_SITE_NEW_SECRET, ) self.assertEqual(RemoteSite.objects.all().count(), 2) - values = { 'name': REMOTE_SITE_NAME, # Old name 'url': REMOTE_SITE_NEW_URL, @@ -5446,7 +4780,7 @@ def test_update_existing_name(self): class TestRemoteSiteDeleteView(RemoteSiteMixin, TestViewsBase): - """Tests for remote site delete view""" + """Tests for RemoteSiteDeleteView""" def setUp(self): super().setUp() @@ -5458,20 +4792,19 @@ def setUp(self): description=REMOTE_SITE_DESC, secret=REMOTE_SITE_SECRET, ) + self.url = reverse( + 'projectroles:remote_site_delete', + kwargs={'remotesite': self.target_site.sodar_uuid}, + ) - def test_render(self): - """Test rendering the remote site delete view""" + def test_get(self): + """Test RemoteSiteDeleteView GET""" with self.login(self.user): - response = self.client.get( - reverse( - 'projectroles:remote_site_delete', - kwargs={'remotesite': self.target_site.sodar_uuid}, - ) - ) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) def test_render_not_found(self): - """Test rendering the remote site delete view with invalid site UUID""" + """Test GET with invalid site UUID""" with self.login(self.user): response = self.client.get( reverse( @@ -5481,8 +4814,8 @@ def test_render_not_found(self): ) self.assertEqual(response.status_code, 404) - def test_delete(self): - """Test deleting the remote site""" + def test_post(self): + """Test RemoteSiteDeleteView POST""" self.assertEqual( 0, ProjectEvent.objects.filter( @@ -5491,14 +4824,8 @@ def test_delete(self): ) self.assertEqual(RemoteSite.objects.all().count(), 1) with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:remote_site_delete', - kwargs={'remotesite': self.target_site.sodar_uuid}, - ) - ) + response = self.client.post(self.url) self.assertRedirects(response, reverse('projectroles:remote_sites')) - tl_event = ProjectEvent.objects.filter( event_name='target_site_delete' ).first() @@ -5513,7 +4840,7 @@ class TestRemoteProjectBatchUpdateView( RemoteProjectMixin, TestViewsBase, ): - """Tests for remote project batch update view""" + """Tests for RemoteProjectBatchUpdateView""" def setUp(self): super().setUp() @@ -5535,20 +4862,17 @@ def setUp(self): description=REMOTE_SITE_DESC, secret=REMOTE_SITE_SECRET, ) + self.url = reverse( + 'projectroles:remote_projects_update', + kwargs={'remotesite': self.target_site.sodar_uuid}, + ) - def test_render_confirm(self): - """Test rendering remote project update view in confirm mode""" + def test_post_confirm(self): + """Test RemoteProjectBatchUpdateView POST in confirm mode""" access_field = 'remote_access_{}'.format(self.project.sodar_uuid) values = {access_field: SODAR_CONSTANTS['REMOTE_LEVEL_READ_INFO']} with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:remote_projects_update', - kwargs={'remotesite': self.target_site.sodar_uuid}, - ), - values, - ) - + response = self.client.post(self.url, values) self.assertEqual( 0, ProjectEvent.objects.filter( @@ -5559,18 +4883,12 @@ def test_render_confirm(self): self.assertEqual(response.context['site'], self.target_site) self.assertIsNotNone(response.context['modifying_access']) - def test_render_confirm_no_change(self): - """Test rendering without changes (should redirect)""" + def test_post_confirm_no_change(self): + """Test POST without changes (should redirect)""" access_field = 'remote_access_{}'.format(self.project.sodar_uuid) values = {access_field: SODAR_CONSTANTS['REMOTE_LEVEL_NONE']} with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:remote_projects_update', - kwargs={'remotesite': self.target_site.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url, values) self.assertRedirects( response, reverse( @@ -5587,7 +4905,7 @@ def test_render_confirm_no_change(self): ) def test_post_create(self): - """Test updating remote project access by adding a new RemoteProject""" + """Test POST to create new RemoteProject""" self.assertEqual( 0, ProjectEvent.objects.filter( @@ -5601,13 +4919,7 @@ def test_post_create(self): 'update-confirmed': 1, } with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:remote_projects_update', - kwargs={'remotesite': self.target_site.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url, values) self.assertRedirects( response, reverse( @@ -5627,7 +4939,7 @@ def test_post_create(self): self.assertEqual(tl_event.event_name, 'remote_batch_update') def test_post_update(self): - """Test updating by modifying an existing RemoteProject""" + """Test POST to update existing RemoteProject""" self.assertEqual( 0, ProjectEvent.objects.filter( @@ -5647,13 +4959,7 @@ def test_post_update(self): 'update-confirmed': 1, } with self.login(self.user): - response = self.client.post( - reverse( - 'projectroles:remote_projects_update', - kwargs={'remotesite': self.target_site.sodar_uuid}, - ), - values, - ) + response = self.client.post(self.url, values) self.assertRedirects( response, reverse( @@ -5676,40 +4982,39 @@ def test_post_update(self): # SODAR User view tests -------------------------------------------------------- -class TestUserUpdateView(TestViewsBase): - """Tests for the user update view""" +class TestUserUpdateView(TestCase): + """Tests for UserUpdateView""" - # NOTE: This assumes an example app is available def setUp(self): - # Init user & role self.user_local = self.make_user('local_user') self.user_ldap = self.make_user('ldap_user@EXAMPLE') + self.url = reverse('projectroles:user_update') - def test_render_local_user(self): + def test_get_local_user(self): + """Test TestUserUpdateView GET with local user""" with self.login(self.user_local): - response = self.client.get(reverse('projectroles:user_update')) + response = self.client.get(self.url) self.assertEqual(response.status_code, 200) - def test_render_ldap_user(self): + def test_get_ldap_user(self): + """Test GET with LDAP user""" with self.login(self.user_ldap): - response = self.client.get( - reverse('projectroles:user_update'), follow=True - ) + response = self.client.get(self.url, follow=True) self.assertRedirects(response, reverse('home')) self.assertEqual( list(get_messages(response.wsgi_request))[0].message, MSG_USER_PROFILE_LDAP, ) - def test_submit_local_user(self): + def test_post_local_user(self): + """Test POST with local user""" self.assertEqual(User.objects.count(), 2) user = User.objects.get(pk=self.user_local.pk) self.assertEqual(user.first_name, '') self.assertEqual(user.last_name, '') - with self.login(self.user_local): response = self.client.post( - reverse('projectroles:user_update'), + self.url, { 'first_name': 'Local', 'last_name': 'User', diff --git a/projectroles/views.py b/projectroles/views.py index 8c800e59..46303124 100644 --- a/projectroles/views.py +++ b/projectroles/views.py @@ -1012,9 +1012,7 @@ def _update_settings(cls, project, project_settings): ) @classmethod - def _notify_users( - cls, project, action, owner, old_data, old_parent, request - ): + def _notify_users(cls, project, action, owner, old_parent, request): """ Notify users about project creation and update. Displays app alerts and/or sends emails depending on the site configuration. @@ -1201,9 +1199,7 @@ def modify_project(self, data, request, instance=None): # Once all is done, update timeline event, create alerts and emails if tl_event: tl_event.set_status('OK') - self._notify_users( - project, action, owner, old_data, old_parent, request - ) + self._notify_users(project, action, owner, old_parent, request) return project diff --git a/siteinfo/plugins.py b/siteinfo/plugins.py index 26ecee6e..7c2c954a 100644 --- a/siteinfo/plugins.py +++ b/siteinfo/plugins.py @@ -7,13 +7,13 @@ class SiteAppPlugin(SiteAppPluginPoint): """Projectroles plugin for registering the app""" - #: Name (slug-safe, used in URLs) + #: Name (used as plugin ID) name = 'siteinfo' #: Title (used in templates) title = 'Site Info' - #: App URLs (will be included in settings by djangoplugins) + #: UI URLs urls = urlpatterns #: Iconify icon diff --git a/sodarcache/plugins.py b/sodarcache/plugins.py index 812078c8..2e389163 100644 --- a/sodarcache/plugins.py +++ b/sodarcache/plugins.py @@ -9,7 +9,7 @@ class BackendPlugin(BackendPluginPoint): """Plugin for registering backend app with Projectroles""" - #: Name (slug-safe, used in URLs) + #: Name (used as plugin ID) name = 'sodar_cache' #: Title (used in templates) diff --git a/timeline/plugins.py b/timeline/plugins.py index 569a6e5c..ce222cf2 100644 --- a/timeline/plugins.py +++ b/timeline/plugins.py @@ -19,13 +19,13 @@ class ProjectAppPlugin(ProjectAppPluginPoint): # Properties required by django-plugins ------------------------------ - #: Name (slug-safe, used in URLs) + #: Name (used as plugin ID) name = 'timeline' #: Title (used in templates) title = 'Timeline' - #: App URLs (will be included in settings by djangoplugins) + #: UI URLs urls = urls_ui_project # Properties defined in ProjectAppPluginPoint ----------------------- @@ -124,7 +124,7 @@ def search(self, search_terms, user, search_type=None, keywords=None): class BackendPlugin(BackendPluginPoint): """Plugin for registering backend app with Projectroles""" - #: Name (slug-safe, used in URLs) + #: Name (used as plugin ID) name = 'timeline_backend' #: Title (used in templates) @@ -144,13 +144,13 @@ def get_api(self, **kwargs): class SiteAppPlugin(SiteAppPluginPoint): """Projectroles plugin for registering the app""" - #: Name (slug-safe, used in URLs) + #: Name (used as plugin ID) name = 'timeline_site' #: Title (used in templates) title = 'Site-Wide Events' - #: App URLs (will be included in settings by djangoplugins) + #: UI URLs urls = urls_ui_site #: Iconify icon @@ -169,13 +169,13 @@ class SiteAppPlugin(SiteAppPluginPoint): class AdminSiteAppPlugin(SiteAppPluginPoint): """Projectroles plugin for registering the app""" - #: Name (slug-safe, used in URLs) + #: Name (used as plugin ID) name = 'timeline_site_admin' #: Title (used in templates) title = 'All Timeline Events' - #: App URLs (will be included in settings by djangoplugins) + #: UI URLs urls = urls_ui_admin #: Iconify icon diff --git a/tokens/plugins.py b/tokens/plugins.py index b8c1ab33..910052f0 100644 --- a/tokens/plugins.py +++ b/tokens/plugins.py @@ -9,17 +9,22 @@ class ProjectAppPlugin(SiteAppPluginPoint): """Plugin for registering app with Projectroles""" + #: Name (used as plugin ID) name = 'tokens' + #: Title (used in templates) title = 'API Tokens' + #: UI URLs urls = urlpatterns #: Iconify icon icon = 'mdi:key-chain-variant' + #: Entry point URL ID entry_point_url_id = 'tokens:list' + #: Description string description = 'API Token Management' #: Required permission for accessing the app diff --git a/userprofile/plugins.py b/userprofile/plugins.py index 6d10e8d5..96080b4c 100644 --- a/userprofile/plugins.py +++ b/userprofile/plugins.py @@ -7,13 +7,13 @@ class SiteAppPlugin(SiteAppPluginPoint): """Projectroles plugin for registering the app""" - #: Name (slug-safe, used in URLs) + #: Name (used as plugin ID) name = 'userprofile' #: Title (used in templates) title = 'User Profile' - #: App URLs (will be included in settings by djangoplugins) + #: UI URLs urls = urlpatterns #: Iconify icon