diff --git a/rdmo/core/settings.py b/rdmo/core/settings.py index b19fc15358..c6610be4de 100644 --- a/rdmo/core/settings.py +++ b/rdmo/core/settings.py @@ -200,6 +200,7 @@ 'MULTISITE', 'GROUPS', 'EXPORT_FORMATS', + 'PROJECT_VISIBILITY', 'PROJECT_ISSUES', 'PROJECT_VIEWS', 'PROJECT_EXPORTS', @@ -296,6 +297,8 @@ PROJECT_TABLE_PAGE_SIZE = 20 +PROJECT_VISIBILITY = True + PROJECT_ISSUES = True PROJECT_ISSUE_PROVIDERS = [] diff --git a/rdmo/core/templates/core/bootstrap_form.html b/rdmo/core/templates/core/bootstrap_form.html index 6444d2ebf7..4cbced2125 100644 --- a/rdmo/core/templates/core/bootstrap_form.html +++ b/rdmo/core/templates/core/bootstrap_form.html @@ -6,6 +6,11 @@ {% include 'core/bootstrap_form_fields.html' %} + {% if submit %} + {% endif %} + {% if delete %} + + {% endif %} diff --git a/rdmo/core/templates/core/bootstrap_form_field.html b/rdmo/core/templates/core/bootstrap_form_field.html index f34e8b9454..2104e387d4 100644 --- a/rdmo/core/templates/core/bootstrap_form_field.html +++ b/rdmo/core/templates/core/bootstrap_form_field.html @@ -1,3 +1,4 @@ +{% load i18n %} {% load widget_tweaks %} {% load core_tags %} @@ -61,6 +62,12 @@ {% render_field field class="form-control" %} + {% if type == 'selectmultiple' %} +
+ {% trans 'Hold down "Control", or "Command" on a Mac, to select more than one.' %} +
+ {% endif %} + {% endif %} {% endwith %} diff --git a/rdmo/core/templatetags/core_tags.py b/rdmo/core/templatetags/core_tags.py index 52a094b9ee..548ded6d81 100644 --- a/rdmo/core/templatetags/core_tags.py +++ b/rdmo/core/templatetags/core_tags.py @@ -114,6 +114,9 @@ def bootstrap_form(context, **kwargs): if 'submit' in kwargs: form_context['submit'] = kwargs['submit'] + if 'delete' in kwargs: + form_context['delete'] = kwargs['delete'] + return render_to_string('core/bootstrap_form.html', form_context, request=context.request) diff --git a/rdmo/projects/admin.py b/rdmo/projects/admin.py index bf8956f28d..07c72480ed 100644 --- a/rdmo/projects/admin.py +++ b/rdmo/projects/admin.py @@ -3,6 +3,7 @@ from django.db.models import Prefetch from django.urls import reverse from django.utils.safestring import mark_safe +from django.utils.translation import gettext_lazy as _ from .models import ( Continuation, @@ -15,6 +16,7 @@ Project, Snapshot, Value, + Visibility, ) from .validators import ProjectParentValidator @@ -71,6 +73,25 @@ class ContinuationAdmin(admin.ModelAdmin): list_display = ('project', 'user', 'page') +@admin.register(Visibility) +class VisibilityAdmin(admin.ModelAdmin): + search_fields = ('project__title', 'sites', 'groups') + list_display = ('project', 'sites_list_display', 'groups_list_display') + filter_horizontal = ('sites', 'groups') + + @admin.display(description=_('Sites')) + def sites_list_display(self, obj): + return _('all Sites') if obj.sites.count() == 0 else ', '.join([ + site.domain for site in obj.sites.all() + ]) + + @admin.display(description=_('Groups')) + def groups_list_display(self, obj): + return _('all Groups') if obj.groups.count() == 0 else ', '.join([ + group.name for group in obj.groups.all() + ]) + + @admin.register(Integration) class IntegrationAdmin(admin.ModelAdmin): search_fields = ('project__title', 'provider_key') diff --git a/rdmo/projects/filters.py b/rdmo/projects/filters.py index 211ac992ca..1962d71d67 100644 --- a/rdmo/projects/filters.py +++ b/rdmo/projects/filters.py @@ -1,3 +1,4 @@ +from django.contrib.auth.models import User from django.db.models import F, OuterRef, Q, Subquery from django.db.models.functions import Concat from django.utils.dateparse import parse_datetime @@ -18,6 +19,22 @@ class Meta: fields = ('title', 'catalog') +class ProjectUserFilterBackend(BaseFilterBackend): + + def filter_queryset(self, request, queryset, view): + if view.detail: + return queryset + + user_id = request.GET.get('user') + user_username = request.GET.get('username') + if user_id or user_username: + user = User.objects.filter(Q(id=user_id) | Q(username=user_username)).first() + if user: + queryset = queryset.filter_visibility(user) + + return queryset + + class ProjectSearchFilterBackend(SearchFilter): def filter_queryset(self, request, queryset, view): diff --git a/rdmo/projects/forms.py b/rdmo/projects/forms.py index 11adc0f1b9..3d2d1a220a 100644 --- a/rdmo/projects/forms.py +++ b/rdmo/projects/forms.py @@ -12,7 +12,7 @@ from rdmo.core.utils import markdown2html from .constants import ROLE_CHOICES -from .models import Integration, IntegrationOption, Invite, Membership, Project, Snapshot +from .models import Integration, IntegrationOption, Invite, Membership, Project, Snapshot, Visibility from .validators import ProjectParentValidator @@ -98,6 +98,48 @@ class Meta: fields = ('title', 'description') +class ProjectUpdateVisibilityForm(forms.ModelForm): + + use_required_attribute = False + + def __init__(self, *args, **kwargs): + self.project = kwargs.pop('instance') + try: + instance = self.project.visibility + except Visibility.DoesNotExist: + instance = None + + super().__init__(*args, instance=instance, **kwargs) + + # remove the sites or group sets if they are not needed, doing this in Meta would break tests + if not settings.MULTISITE: + self.fields.pop('sites') + if not settings.GROUPS: + self.fields.pop('groups') + + class Meta: + model = Visibility + fields = ('sites', 'groups') + + def save(self, *args, **kwargs): + if 'cancel' in self.data: + pass + elif 'delete' in self.data: + self.instance.delete() + else: + visibility, created = Visibility.objects.update_or_create(project=self.project) + + sites = self.cleaned_data.get('sites') + if sites is not None: + visibility.sites.set(sites) + + groups = self.cleaned_data.get('groups') + if groups is not None: + visibility.groups.set(groups) + + return self.project + + class ProjectUpdateCatalogForm(forms.ModelForm): use_required_attribute = False diff --git a/rdmo/projects/managers.py b/rdmo/projects/managers.py index 72e7c21a0b..a6d49e4ff6 100644 --- a/rdmo/projects/managers.py +++ b/rdmo/projects/managers.py @@ -21,13 +21,20 @@ def filter_user(self, user): elif is_site_manager(user): return self.filter_current_site() else: - queryset = self.filter(user=user) + queryset = self.filter_visibility(user) for instance in queryset: queryset |= instance.get_descendants() return queryset.distinct() else: return self.none() + def filter_visibility(self, user): + groups = user.groups.all() + sites_filter = Q(visibility__sites=None) | Q(visibility__sites=settings.SITE_ID) + groups_filter = Q(visibility__groups=None) | Q(visibility__groups__in=groups) + visibility_filter = Q(visibility__isnull=False) & sites_filter & groups_filter + return self.filter(Q(user=user) | visibility_filter) + class MembershipQuerySet(models.QuerySet): @@ -157,6 +164,9 @@ def get_queryset(self): def filter_user(self, user): return self.get_queryset().filter_user(user) + def filter_visibility(self, user): + return self.get_queryset().filter_visibility(user) + class MembershipManager(CurrentSiteManagerMixin, models.Manager): diff --git a/rdmo/projects/migrations/0062_visibility.py b/rdmo/projects/migrations/0062_visibility.py new file mode 100644 index 0000000000..e418f334f2 --- /dev/null +++ b/rdmo/projects/migrations/0062_visibility.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.16 on 2024-12-06 10:11 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ('sites', '0002_alter_domain_unique'), + ('projects', '0061_alter_value_value_type'), + ] + + operations = [ + migrations.CreateModel( + name='Visibility', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(editable=False, verbose_name='created')), + ('updated', models.DateTimeField(editable=False, verbose_name='updated')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups for which the project is visible.', to='auth.group', verbose_name='Group')), + ('project', models.OneToOneField(help_text='The project for this visibility.', on_delete=django.db.models.deletion.CASCADE, to='projects.project', verbose_name='Project')), + ('sites', models.ManyToManyField(blank=True, help_text='The sites for which the project is visible (in a multi site setup).', to='sites.site', verbose_name='Sites')), + ], + options={ + 'verbose_name': 'Visibility', + 'verbose_name_plural': 'Visibilities', + 'ordering': ('project',), + }, + ), + ] diff --git a/rdmo/projects/models/__init__.py b/rdmo/projects/models/__init__.py index 35dcab847f..16af648546 100644 --- a/rdmo/projects/models/__init__.py +++ b/rdmo/projects/models/__init__.py @@ -6,3 +6,4 @@ from .project import Project from .snapshot import Snapshot from .value import Value +from .visibility import Visibility diff --git a/rdmo/projects/models/visibility.py b/rdmo/projects/models/visibility.py new file mode 100644 index 0000000000..61e4e3d2f4 --- /dev/null +++ b/rdmo/projects/models/visibility.py @@ -0,0 +1,66 @@ +from django.conf import settings +from django.contrib.auth.models import Group +from django.contrib.sites.models import Site +from django.db import models +from django.utils.translation import gettext_lazy as _ +from django.utils.translation import ngettext_lazy + +from rdmo.core.models import Model + + +class Visibility(Model): + + project = models.OneToOneField( + 'Project', on_delete=models.CASCADE, + verbose_name=_('Project'), + help_text=_('The project for this visibility.') + ) + sites = models.ManyToManyField( + Site, blank=True, + verbose_name=_('Sites'), + help_text=_('The sites for which the project is visible (in a multi site setup).') + ) + groups = models.ManyToManyField( + Group, blank=True, + verbose_name=_('Group'), + help_text=_('The groups for which the project is visible.') + ) + + class Meta: + ordering = ('project', ) + verbose_name = _('Visibility') + verbose_name_plural = _('Visibilities') + + def __str__(self): + return str(self.project) + + def is_visible(self, user): + return ( + not self.sites.exists() or self.sites.filter(id=settings.SITE_ID).exists() + ) and ( + not self.groups.exists() or self.groups.filter(id__in=[group.id for group in user.groups.all()]).exists() + ) + + def get_help_display(self): + sites = self.sites.values_list('domain', flat=True) + groups = self.groups.values_list('name', flat=True) + + if sites and groups: + return ngettext_lazy( + 'This project can be accessed by all users on %s or in the group %s.', + 'This project can be accessed by all users on %s or in the groups %s.', + len(groups) + ) % ( + ', '.join(sites), + ', '.join(groups) + ) + elif sites: + return _('This project can be accessed by all users on %s.') % ', '.join(sites) + elif groups: + return ngettext_lazy( + 'This project can be accessed by all users in the group %s.', + 'This project can be accessed by all users in the groups %s.', + len(groups) + ) % ', '.join(groups) + else: + return _('This project can be accessed by all users.') diff --git a/rdmo/projects/permissions.py b/rdmo/projects/permissions.py index 9fa7be5ffb..da693c8d0d 100644 --- a/rdmo/projects/permissions.py +++ b/rdmo/projects/permissions.py @@ -90,3 +90,25 @@ def get_required_object_permissions(self, method, model_cls): return ('projects.change_project_progress_object', ) else: return ('projects.view_project_object', ) + + +class HasProjectVisibilityModelPermission(HasModelPermission): + + def get_required_permissions(self, method, model_cls): + if method == 'POST': + return ('projects.change_visibility', ) + elif method == 'DELETE': + return ('projects.delete_visibility', ) + else: + return ('projects.view_visibility', ) + + +class HasProjectVisibilityObjectPermission(HasProjectPermission): + + def get_required_object_permissions(self, method, model_cls): + if method == 'POST': + return ('projects.change_visibility_object', ) + elif method == 'DELETE': + return ('projects.delete_visibility_object', ) + else: + return ('projects.view_visibility_object', ) diff --git a/rdmo/projects/rules.py b/rdmo/projects/rules.py index c0b7901bab..70e5f44326 100644 --- a/rdmo/projects/rules.py +++ b/rdmo/projects/rules.py @@ -1,5 +1,6 @@ from django.conf import settings from django.contrib.sites.shortcuts import get_current_site +from django.core.exceptions import ObjectDoesNotExist import rules from rules.predicates import is_superuser @@ -46,6 +47,17 @@ def is_project_guest(user, project): return user in project.guests or (project.parent and is_project_guest(user, project.parent)) +@rules.predicate +def is_visible(user, project): + if user.is_authenticated: + try: + return project.visibility.is_visible(user) + except ObjectDoesNotExist: + return False + else: + return False + + @rules.predicate def is_site_manager(user, project): if user.is_authenticated: @@ -67,7 +79,7 @@ def is_site_manager_for_current_site(user, request): rules.add_rule('projects.can_view_all_projects', is_site_manager_for_current_site | is_superuser) rules.add_perm('projects.add_project', can_add_project) -rules.add_perm('projects.view_project_object', is_project_member | is_site_manager) +rules.add_perm('projects.view_project_object', is_project_member | is_visible | is_site_manager) rules.add_perm('projects.change_project_object', is_project_manager | is_project_owner | is_site_manager) rules.add_perm('projects.change_project_progress_object', is_project_author | is_project_manager | is_project_owner | is_site_manager) # noqa: E501 rules.add_perm('projects.delete_project_object', is_project_owner | is_site_manager) @@ -75,7 +87,12 @@ def is_site_manager_for_current_site(user, request): rules.add_perm('projects.export_project_object', is_project_owner | is_project_manager | is_site_manager) rules.add_perm('projects.import_project_object', is_project_owner | is_project_manager | is_site_manager) -rules.add_perm('projects.view_membership_object', is_project_member | is_site_manager) +rules.add_perm('projects.view_visibility_object', is_site_manager) +rules.add_perm('projects.add_visibility_object', is_site_manager) +rules.add_perm('projects.change_visibility_object', is_site_manager) +rules.add_perm('projects.delete_visibility_object', is_site_manager) + +rules.add_perm('projects.view_membership_object', is_project_member | is_visible | is_site_manager) rules.add_perm('projects.add_membership_object', is_project_owner | is_site_manager) rules.add_perm('projects.change_membership_object', is_project_owner | is_site_manager) rules.add_perm('projects.delete_membership_object', is_project_owner | is_site_manager) @@ -85,28 +102,28 @@ def is_site_manager_for_current_site(user, request): rules.add_perm('projects.change_invite_object', is_project_owner | is_site_manager) rules.add_perm('projects.delete_invite_object', is_project_owner | is_site_manager) -rules.add_perm('projects.view_integration_object', is_project_member | is_site_manager) +rules.add_perm('projects.view_integration_object', is_project_member | is_visible | is_site_manager) rules.add_perm('projects.add_integration_object', is_project_owner | is_project_manager | is_site_manager) rules.add_perm('projects.change_integration_object', is_project_owner | is_project_manager | is_site_manager) rules.add_perm('projects.delete_integration_object', is_project_owner | is_project_manager | is_site_manager) -rules.add_perm('projects.view_issue_object', is_project_member | is_site_manager) +rules.add_perm('projects.view_issue_object', is_project_member | is_visible | is_site_manager) rules.add_perm('projects.add_issue_object', is_project_manager | is_project_owner | is_site_manager) rules.add_perm('projects.change_issue_object', is_project_author | is_project_manager | is_project_owner | is_site_manager) # noqa: E501 rules.add_perm('projects.delete_issue_object', is_project_manager | is_project_owner | is_site_manager) -rules.add_perm('projects.view_snapshot_object', is_project_member | is_site_manager) +rules.add_perm('projects.view_snapshot_object', is_project_member | is_visible | is_site_manager) rules.add_perm('projects.add_snapshot_object', is_project_manager | is_project_owner | is_site_manager) rules.add_perm('projects.change_snapshot_object', is_project_manager | is_project_owner | is_site_manager) rules.add_perm('projects.rollback_snapshot_object', is_project_manager | is_project_owner | is_site_manager) rules.add_perm('projects.export_snapshot_object', is_project_owner | is_project_manager | is_site_manager) -rules.add_perm('projects.view_value_object', is_project_member | is_site_manager) +rules.add_perm('projects.view_value_object', is_project_member | is_visible | is_site_manager) rules.add_perm('projects.add_value_object', is_project_author | is_project_manager | is_project_owner | is_site_manager) rules.add_perm('projects.change_value_object', is_project_author | is_project_manager | is_project_owner | is_site_manager) # noqa: E501 rules.add_perm('projects.delete_value_object', is_project_author | is_project_manager | is_project_owner | is_site_manager) # noqa: E501 -rules.add_perm('projects.view_page_object', is_project_member | is_site_manager) +rules.add_perm('projects.view_page_object', is_project_member | is_visible | is_site_manager) # TODO: use one of the permissions above rules.add_perm('projects.is_project_owner', is_project_owner) diff --git a/rdmo/projects/serializers/v1/__init__.py b/rdmo/projects/serializers/v1/__init__.py index fe72127295..8e61748193 100644 --- a/rdmo/projects/serializers/v1/__init__.py +++ b/rdmo/projects/serializers/v1/__init__.py @@ -8,7 +8,18 @@ from rdmo.questions.models import Catalog from rdmo.services.validators import ProviderValidator -from ...models import Integration, IntegrationOption, Invite, Issue, IssueResource, Membership, Project, Snapshot, Value +from ...models import ( + Integration, + IntegrationOption, + Invite, + Issue, + IssueResource, + Membership, + Project, + Snapshot, + Value, + Visibility, +) from ...validators import ProjectParentValidator, ValueConflictValidator, ValueQuotaValidator, ValueTypeValidator @@ -91,6 +102,17 @@ class Meta: read_only_fields = ProjectSerializer.Meta.read_only_fields +class ProjectVisibilitySerializer(serializers.ModelSerializer): + + class Meta: + model = Visibility + fields = ( + 'project', + 'sites', + 'groups' + ) + + class ProjectMembershipSerializer(serializers.ModelSerializer): class Meta: diff --git a/rdmo/projects/templates/projects/project_detail_header.html b/rdmo/projects/templates/projects/project_detail_header.html index 6302201657..c6ca098c90 100644 --- a/rdmo/projects/templates/projects/project_detail_header.html +++ b/rdmo/projects/templates/projects/project_detail_header.html @@ -4,6 +4,7 @@ {% load accounts_tags %} {% has_perm 'projects.change_project_object' request.user project as can_change_project %} +{% has_perm 'projects.change_visibility_object' request.user project as can_change_visibility %}+ + + +
+{% endif %} + + +{{ project.visibility.get_help_display }} + diff --git a/rdmo/projects/templates/projects/project_detail_sidebar.html b/rdmo/projects/templates/projects/project_detail_sidebar.html index 3943bcccd2..5c90ed7d5c 100644 --- a/rdmo/projects/templates/projects/project_detail_sidebar.html +++ b/rdmo/projects/templates/projects/project_detail_sidebar.html @@ -77,6 +77,21 @@+ {% blocktrans trimmed %} + Projects can be made visible to all users, for example to be used as a template. + When a project is made visible, users can access it as if they were in the guest role. + {% endblocktrans %} +
+ + {% if object.visibility and 'sites' in form.fields or 'groups' in form.fields %} + {% bootstrap_form submit=_('Update visibility') delete=_('Remove visibility') %} + {% elif object.visibility %} + {% bootstrap_form delete=_('Remove visibility') %} + {% else %} + {% bootstrap_form submit=_('Make visible') %} + {% endif %} + +{% endblock %} diff --git a/rdmo/projects/tests/test_view_integration.py b/rdmo/projects/tests/test_view_integration.py index cb20690f8d..711b7790ee 100644 --- a/rdmo/projects/tests/test_view_integration.py +++ b/rdmo/projects/tests/test_view_integration.py @@ -14,15 +14,6 @@ ('anonymous', None), ) -view_integration_permission_map = { - 'owner': [1, 2, 3, 4, 5], - 'manager': [1, 3, 5], - 'author': [1, 3, 5], - 'guest': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] -} - add_integration_permission_map = change_integration_permission_map = delete_integration_permission_map = { 'owner': [1, 2, 3, 4, 5], 'manager': [1, 3, 5], diff --git a/rdmo/projects/tests/test_view_issue.py b/rdmo/projects/tests/test_view_issue.py index 8780c7114f..cf4f6d745e 100644 --- a/rdmo/projects/tests/test_view_issue.py +++ b/rdmo/projects/tests/test_view_issue.py @@ -21,137 +21,113 @@ ) view_issue_permission_map = { - 'owner': [1, 2, 3, 4, 5], - 'manager': [1, 3, 5], - 'author': [1, 3, 5], - 'guest': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] + 'owner': [1, 2, 3, 4, 12], + 'manager': [1, 3, 5, 12], + 'author': [1, 3, 5, 12], + 'guest': [1, 3, 5, 12], + 'user': [12], + 'api': [1, 2, 3, 4, 5, 12], + 'site': [1, 2, 3, 4, 5, 12] } -change_issue_permission_map = { - 'owner': [1, 2, 3, 4, 5], - 'manager': [1, 3, 5], - 'author': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] -} - -delete_issue_permission_map = { - 'owner': [1, 2, 3, 4, 5], - 'manager': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] +change_issue_permission_map = delete_issue_permission_map = { + 'owner': [1, 2, 3, 4, 12], + 'manager': [1, 3], + 'author': [1, 3], + 'api': [1, 2, 3, 4, 12], + 'site': [1, 2, 3, 4, 12], } - -projects = [1, 2, 3, 4, 5] -issues = [1, 2, 3, 4] +issues = [1, 2, 3, 4, 9] integration_pk = 1 issue_status = ('open', 'in_progress', 'closed') @pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('project_id', projects) @pytest.mark.parametrize('issue_id', issues) -def test_issue(db, client, username, password, project_id, issue_id): +def test_issue(db, client, username, password, issue_id): client.login(username=username, password=password) - issue = Issue.objects.filter(project_id=project_id, id=issue_id).first() + issue = Issue.objects.get(id=issue_id) - url = reverse('issue', args=[project_id, issue_id]) + url = reverse('issue', args=[issue.project_id, issue_id]) response = client.get(url) - if issue: - if project_id in view_issue_permission_map.get(username, []): - assert response.status_code == 200 - elif password: - assert response.status_code == 403 - else: - assert response.status_code == 302 + if issue.project_id in view_issue_permission_map.get(username, []): + assert response.status_code == 200 + elif password: + assert response.status_code == 403 else: - assert response.status_code == 404 + assert response.status_code == 302 @pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('project_id', projects) @pytest.mark.parametrize('issue_id', issues) -def test_issue_update_get(db, client, username, password, project_id, issue_id): +def test_issue_update_get(db, client, username, password, issue_id): client.login(username=username, password=password) - issue = Issue.objects.filter(project_id=project_id, id=issue_id).first() + issue = Issue.objects.get(id=issue_id) - url = reverse('issue_update', args=[project_id, issue_id]) + url = reverse('issue_update', args=[issue.project_id, issue_id]) response = client.get(url) - if issue: - if project_id in change_issue_permission_map.get(username, []): - assert response.status_code == 200 - elif password: - assert response.status_code == 403 - else: - assert response.status_code == 302 + if issue.project_id in change_issue_permission_map.get(username, []): + assert response.status_code == 200 + elif password: + assert response.status_code == 403 else: - assert response.status_code == 404 + assert response.status_code == 302 @pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('project_id', projects) @pytest.mark.parametrize('issue_id', issues) @pytest.mark.parametrize('status', issue_status) -def test_issue_update_post(db, client, username, password, project_id, issue_id, status): +def test_issue_update_post(db, client, username, password, issue_id, status): client.login(username=username, password=password) - issue = Issue.objects.filter(project_id=project_id, id=issue_id).first() + issue = Issue.objects.get(id=issue_id) - url = reverse('issue_update', args=[project_id, issue_id]) + url = reverse('issue_update', args=[issue.project_id, issue_id]) data = { 'status': status } response = client.post(url, data) - if issue: - if project_id in change_issue_permission_map.get(username, []): - assert response.status_code == 302 - assert Issue.objects.get(id=issue_id).status == status + if issue.project_id in change_issue_permission_map.get(username, []): + assert response.status_code == 302 + assert Issue.objects.get(id=issue_id).status == status + else: + if password: + assert response.status_code == 403 else: - if password: - assert response.status_code == 403 - else: - assert response.status_code == 302 + assert response.status_code == 302 + + assert Issue.objects.get(id=issue_id).status == issue.status - assert Issue.objects.get(id=issue_id).status == issue.status - else: - assert response.status_code == 404 @pytest.mark.parametrize('username,password', users) @pytest.mark.parametrize('issue_id', issues) -@pytest.mark.parametrize('project_id', projects) -def test_issue_send_get(db, client, username, password, project_id, issue_id): +def test_issue_send_get(db, client, username, password, issue_id): client.login(username=username, password=password) - issue = Issue.objects.filter(project_id=project_id, id=issue_id).first() + issue = Issue.objects.get(id=issue_id) - url = reverse('issue_update', args=[project_id, issue_id]) + url = reverse('issue_update', args=[issue.project_id, issue_id]) response = client.get(url) - if issue: - if project_id in change_issue_permission_map.get(username, []): - assert response.status_code == 200 - elif password: - assert response.status_code == 403 - else: - assert response.status_code == 302 + if issue.project_id in change_issue_permission_map.get(username, []): + assert response.status_code == 200 + elif password: + assert response.status_code == 403 else: - assert response.status_code == 404 + assert response.status_code == 302 @pytest.mark.parametrize('username,password', users) @pytest.mark.parametrize('issue_id', issues) -@pytest.mark.parametrize('project_id', projects) -def test_issue_send_post_email(db, client, username, password, project_id, issue_id): +def test_issue_send_post_email(db, client, username, password, issue_id): client.login(username=username, password=password) - issue = Issue.objects.filter(project_id=project_id, id=issue_id).first() + issue = Issue.objects.get(id=issue_id) - url = reverse('issue_send', args=[project_id, issue_id]) + url = reverse('issue_send', args=[issue.project_id, issue_id]) data = { 'subject': 'Subject', 'message': 'Message', @@ -159,12 +135,57 @@ def test_issue_send_post_email(db, client, username, password, project_id, issue } response = client.post(url, data) - if issue: - if project_id in change_issue_permission_map.get(username, []): + if issue.project_id in change_issue_permission_map.get(username, []): + assert response.status_code == 302 + assert len(mail.outbox) == 1 + assert mail.outbox[0].subject == '[example.com] Subject' + assert mail.outbox[0].body == 'Message' + else: + if password: + assert response.status_code == 403 + else: + assert response.status_code == 302 + + assert len(mail.outbox) == 0 + + +@pytest.mark.parametrize('username,password', users) +@pytest.mark.parametrize('issue_id', issues) +def test_issue_send_post_attachments(db, client, files, username, password, issue_id): + client.login(username=username, password=password) + issue = Issue.objects.get(id=issue_id) + + view = issue.project.views.first() + file = issue.project.values.filter(snapshot=None, value_type=VALUE_TYPE_FILE).first() + + if file and view: + url = reverse('issue_send', args=[issue.project_id, issue_id]) + data = { + 'subject': 'Subject', + 'message': 'Message', + 'recipients': 'email@example.com', + 'attachments_answers': 'project_answers', + 'attachments_views': str(view.id), + 'attachments_files': str(file.id), + 'attachments_snapshot': '', + 'attachments_format': 'html' + } + response = client.post(url, data) + + if issue.project_id in change_issue_permission_map.get(username, []): assert response.status_code == 302 assert len(mail.outbox) == 1 assert mail.outbox[0].subject == '[example.com] Subject' assert mail.outbox[0].body == 'Message' + + attachments = mail.outbox[0].attachments + assert len(attachments) == 3 + assert attachments[0][0] == 'Test.html' + assert attachments[0][2] == 'text/html; charset=utf-8' + assert attachments[1][0] == 'Test.html' + assert attachments[1][2] == 'text/html; charset=utf-8' + assert attachments[2][0] == 'test.txt' + assert attachments[2][2] == 'text/plain' else: if password: assert response.status_code == 403 @@ -172,69 +193,18 @@ def test_issue_send_post_email(db, client, username, password, project_id, issue assert response.status_code == 302 assert len(mail.outbox) == 0 - else: - assert response.status_code == 404 - - -@pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('issue_id', issues) -@pytest.mark.parametrize('project_id', projects) -def test_issue_send_post_attachments(db, client, files, username, password, project_id, issue_id): - client.login(username=username, password=password) - issue = Issue.objects.filter(project_id=project_id, id=issue_id).first() - - if issue: - view = issue.project.views.first() - file = issue.project.values.filter(snapshot=None, value_type=VALUE_TYPE_FILE).first() - - if file and view: - url = reverse('issue_send', args=[project_id, issue_id]) - data = { - 'subject': 'Subject', - 'message': 'Message', - 'recipients': 'email@example.com', - 'attachments_answers': 'project_answers', - 'attachments_views': str(view.id), - 'attachments_files': str(file.id), - 'attachments_snapshot': '', - 'attachments_format': 'html' - } - response = client.post(url, data) - - if project_id in change_issue_permission_map.get(username, []): - assert response.status_code == 302 - assert len(mail.outbox) == 1 - assert mail.outbox[0].subject == '[example.com] Subject' - assert mail.outbox[0].body == 'Message' - - attachments = mail.outbox[0].attachments - assert len(attachments) == 3 - assert attachments[0][0] == 'Test.html' - assert attachments[0][2] == 'text/html; charset=utf-8' - assert attachments[1][0] == 'Test.html' - assert attachments[1][2] == 'text/html; charset=utf-8' - assert attachments[2][0] == 'test.txt' - assert attachments[2][2] == 'text/plain' - else: - if password: - assert response.status_code == 403 - else: - assert response.status_code == 302 - - assert len(mail.outbox) == 0 @pytest.mark.parametrize('username,password', users) @pytest.mark.parametrize('issue_id', issues) -@pytest.mark.parametrize('project_id', projects) -def test_issue_send_post_integration(db, client, mocker, username, password, project_id, issue_id): +def test_issue_send_post_integration(db, client, mocker, username, password, issue_id): mocked_send_issue = Mock(return_value=HttpResponseRedirect(redirect_to='https://example.com/login/oauth/authorize')) mocker.patch('rdmo.projects.providers.SimpleIssueProvider.send_issue', mocked_send_issue) client.login(username=username, password=password) - issue = Issue.objects.filter(project_id=project_id, id=issue_id).first() + issue = Issue.objects.get(id=issue_id) - url = reverse('issue_send', args=[project_id, issue_id]) + url = reverse('issue_send', args=[issue.project_id, issue_id]) data = { 'subject': 'Subject', 'message': 'Message', @@ -242,18 +212,15 @@ def test_issue_send_post_integration(db, client, mocker, username, password, pro } response = client.post(url, data) - if issue: - if project_id in change_issue_permission_map.get(username, []): - if integration_pk in Project.objects.get(pk=project_id).integrations.values_list('id', flat=True): - assert response.status_code == 302 - assert response.url.startswith('https://example.com') - else: - assert response.status_code == 200 + if issue.project_id in change_issue_permission_map.get(username, []): + if integration_pk in Project.objects.get(pk=issue.project_id).integrations.values_list('id', flat=True): + assert response.status_code == 302 + assert response.url.startswith('https://example.com') else: - if password: - assert response.status_code == 403 - else: - assert response.status_code == 302 - assert not response.url.startswith('https://example.com') + assert response.status_code == 200 else: - assert response.status_code == 404 + if password: + assert response.status_code == 403 + else: + assert response.status_code == 302 + assert not response.url.startswith('https://example.com') diff --git a/rdmo/projects/tests/test_view_project.py b/rdmo/projects/tests/test_view_project.py index 8a1465c765..8f22d0eb19 100644 --- a/rdmo/projects/tests/test_view_project.py +++ b/rdmo/projects/tests/test_view_project.py @@ -17,43 +17,46 @@ ('author', 'author'), ('guest', 'guest'), ('user', 'user'), - ('site', 'site'), - ('anonymous', None), ('editor', 'editor'), ('reviewer', 'reviewer'), ('api', 'api'), + ('site', 'site'), + ('anonymous', None) ) view_project_permission_map = { - 'owner': [1, 2, 3, 4, 5, 10], - 'manager': [1, 3, 5, 7], - 'author': [1, 3, 5, 8], - 'guest': [1, 3, 5, 9], - 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], - 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + 'owner': [1, 2, 3, 4, 5, 10, 12], + 'manager': [1, 3, 5, 7, 12], + 'author': [1, 3, 5, 8, 12], + 'guest': [1, 3, 5, 9, 12], + 'user': [12], + 'editor': [12], + 'reviewer': [12], + 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] } change_project_permission_map = { - 'owner': [1, 2, 3, 4, 5, 10], + 'owner': [1, 2, 3, 4, 5, 10, 12], 'manager': [1, 3, 5, 7], - 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], - 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] } delete_project_permission_map = { - 'owner': [1, 2, 3, 4, 5, 10], - 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], - 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + 'owner': [1, 2, 3, 4, 5, 10, 12], + 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], } export_project_permission_map = { - 'owner': [1, 2, 3, 4, 5, 10], + 'owner': [1, 2, 3, 4, 5, 10, 12], 'manager': [1, 3, 5, 7], - 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], - 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], } -projects = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +projects = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] export_formats = ('rtf', 'odt', 'docx', 'html', 'markdown', 'tex', 'pdf') @@ -332,6 +335,8 @@ def test_project_update_post_parent(db, client, username, password, project_id): client.login(username=username, password=password) project = Project.objects.get(pk=project_id) + print(project, project_id, Project.objects.get(pk=parent_id)) + url = reverse('project_update', args=[project_id]) data = { 'title': project.title, diff --git a/rdmo/projects/tests/test_view_project_leave.py b/rdmo/projects/tests/test_view_project_leave.py index fa0fd55bbd..b8ef2c07ca 100644 --- a/rdmo/projects/tests/test_view_project_leave.py +++ b/rdmo/projects/tests/test_view_project_leave.py @@ -15,14 +15,15 @@ ) leave_project_permission_map = { - 'owner': [1, 2, 3, 4, 5], + 'owner': [1, 2, 3, 4, 5, 12], 'manager': [1, 3], 'author': [1, 3], 'guest': [1, 3] } -projects = [1, 2, 3, 4, 5] +projects = [1, 2, 3, 4, 5, 12] +internal_projects = [12] @pytest.mark.parametrize('username,password', users) @pytest.mark.parametrize('project_id', projects) diff --git a/rdmo/projects/tests/test_view_project_update_visibility.py b/rdmo/projects/tests/test_view_project_update_visibility.py new file mode 100644 index 0000000000..fc67ef3184 --- /dev/null +++ b/rdmo/projects/tests/test_view_project_update_visibility.py @@ -0,0 +1,148 @@ +import pytest + +from django.urls import reverse + +from ..models import Project, Visibility + +users = ( + ('owner', 'owner'), + ('manager', 'manager'), + ('author', 'author'), + ('guest', 'guest'), + ('user', 'user'), + ('editor', 'editor'), + ('reviewer', 'reviewer'), + ('api', 'api'), + ('site', 'site'), + ('anonymous', None) +) + + +project_id = 12 + +@pytest.mark.parametrize('username,password', users) +def test_project_create_visibility_get(db, client, username, password): + client.login(username=username, password=password) + + project = Project.objects.get(id=project_id) + project.visibility.delete() + + url = reverse('project_update_visibility', args=[project_id]) + response = client.get(url) + + if username in ['admin', 'site', 'api']: + assert response.status_code == 200 + else: + if password: + assert response.status_code == 403 + else: + assert response.status_code == 302 + + +@pytest.mark.parametrize('username,password', users) +def test_project_create_visibility_post(db, client, username, password): + client.login(username=username, password=password) + + project = Project.objects.get(id=project_id) + project.visibility.delete() + + url = reverse('project_update_visibility', args=[project_id]) + data = {} + response = client.post(url, data) + + if username in ['admin', 'site', 'api']: + assert response.status_code == 302 + assert Project.objects.get(pk=project_id).visibility + else: + if password: + assert response.status_code == 403 + else: + assert response.status_code == 302 + + with pytest.raises(Visibility.DoesNotExist): + assert Project.objects.get(pk=project_id).visibility + + +def test_project_update_visibility_site_post(db, client, settings): + settings.MULTISITE = True + + client.login(username='site', password='site') + + url = reverse('project_update_visibility', args=[project_id]) + data = { + 'sites': [2] + } + response = client.post(url, data) + + assert response.status_code == 302 + + project = Project.objects.get(id=project_id) + + assert project.visibility + assert [site.id for site in project.visibility.sites.all()] == [2] + assert not project.visibility.groups.exists() + + +def test_project_update_visibility_group_post(db, client, settings): + settings.GROUPS = True + + client.login(username='site', password='site') + + url = reverse('project_update_visibility', args=[project_id]) + data = { + 'groups': [2] + } + response = client.post(url, data) + + assert response.status_code == 302 + + project = Project.objects.get(id=project_id) + + assert project.visibility + assert [site.id for site in project.visibility.sites.all()] == [1] # this site is in the fixture + assert [group.id for group in project.visibility.groups.all()] == [2] + + +def test_project_update_visibility_site_group_post(db, client, settings): + settings.MULTISITE = True + settings.GROUPS = True + + client.login(username='site', password='site') + + url = reverse('project_update_visibility', args=[project_id]) + data = { + 'sites': [2], + 'groups': [2] + } + response = client.post(url, data) + + assert response.status_code == 302 + + project = Project.objects.get(id=project_id) + + assert project.visibility + assert [site.id for site in project.visibility.sites.all()] == [2] + assert [group.id for group in project.visibility.groups.all()] == [2] + + +@pytest.mark.parametrize('username,password', users) +def test_project_delete_visibility_post(db, client, username, password): + client.login(username=username, password=password) + + url = reverse('project_update_visibility', args=[project_id]) + data = { + 'delete': 'some value' + } + response = client.post(url, data) + + if username in ['admin', 'site', 'api']: + assert response.status_code == 302 + with pytest.raises(Visibility.DoesNotExist): + assert Project.objects.get(pk=project_id).visibility + else: + if password: + assert response.status_code == 403 + else: + assert response.status_code == 302 + + assert Project.objects.get(pk=project_id).visibility diff --git a/rdmo/projects/tests/test_view_project_visibility.py b/rdmo/projects/tests/test_view_project_visibility.py new file mode 100644 index 0000000000..04b8c2a8c5 --- /dev/null +++ b/rdmo/projects/tests/test_view_project_visibility.py @@ -0,0 +1,147 @@ +from django.contrib.auth.models import Group, User +from django.contrib.sites.models import Site +from django.urls import reverse + +from ..models import Project + +user_username = 'user' +project_id = 12 + + +def test_detail_cleared(db, client): + client.login(username='user', password='user') + + project = Project.objects.get(id=project_id) + project.visibility.sites.clear() + project.visibility.groups.clear() + + url = reverse('project', args=[project_id]) + response = client.get(url) + assert response.status_code == 200 + + +def test_detail_deleted(db, client): + client.login(username='user', password='user') + + project = Project.objects.get(id=project_id) + project.visibility.delete() + + url = reverse('project', args=[project_id]) + response = client.get(url) + assert response.status_code == 403 + + +def test_detail_site(db, client): + client.login(username='user', password='user') + + project = Project.objects.get(id=project_id) + project.visibility.sites.set(Site.objects.filter(id=1)) + project.visibility.groups.clear() + + url = reverse('project', args=[project_id]) + response = client.get(url) + assert response.status_code == 200 + + +def test_detail_multiple_sites(db, client): + client.login(username='user', password='user') + + project = Project.objects.get(id=project_id) + project.visibility.sites.set(Site.objects.filter(id__in=[1, 2])) + project.visibility.groups.clear() + + url = reverse('project', args=[project_id]) + response = client.get(url) + assert response.status_code == 200 + + +def test_detail_site_forbidden(db, client): + client.login(username='user', password='user') + + project = Project.objects.get(id=project_id) + project.visibility.sites.set(Site.objects.filter(id=2)) + project.visibility.groups.clear() + + url = reverse('project', args=[project_id]) + response = client.get(url) + assert response.status_code == 403 + + +def test_detail_group(db, client): + client.login(username='user', password='user') + + group = Group.objects.create(name='test') + user = User.objects.get(username='user') + user.groups.add(group) + + project = Project.objects.get(id=project_id) + project.visibility.sites.clear() + project.visibility.groups.add(group) + + url = reverse('project', args=[project_id]) + response = client.get(url) + assert response.status_code == 200 + + +def test_detail_multiple_groups(db, client): + client.login(username='user', password='user') + + group = Group.objects.create(name='test') + user = User.objects.get(username='user') + user.groups.add(group) + + project = Project.objects.get(id=project_id) + project.visibility.sites.clear() + project.visibility.groups.set([ + group, Group.objects.create(name='test2') + ]) + + url = reverse('project', args=[project_id]) + response = client.get(url) + assert response.status_code == 200 + + +def test_detail_group_forbidden(db, client): + client.login(username='user', password='user') + + group = Group.objects.create(name='test') + + project = Project.objects.get(id=project_id) + project.visibility.sites.clear() + project.visibility.groups.set([group]) + + url = reverse('project', args=[project_id]) + response = client.get(url) + assert response.status_code == 403 + + +def test_detail_site_group(db, client): + client.login(username='user', password='user') + + group = Group.objects.create(name='test') + user = User.objects.get(username='user') + user.groups.add(group) + + project = Project.objects.get(id=project_id) + project.visibility.sites.set(Site.objects.filter(id=1)) + project.visibility.groups.set([group]) + + url = reverse('project', args=[project_id]) + response = client.get(url) + assert response.status_code == 200 + + +def test_detail_site_group_forbidden(db, client): + client.login(username='user', password='user') + + group = Group.objects.create(name='test') + user = User.objects.get(username='user') + user.groups.add(group) + + project = Project.objects.get(id=project_id) + project.visibility.sites.set(Site.objects.filter(id=2)) + project.visibility.groups.set([group]) + + url = reverse('project', args=[project_id]) + response = client.get(url) + assert response.status_code == 403 diff --git a/rdmo/projects/tests/test_view_snapshot.py b/rdmo/projects/tests/test_view_snapshot.py index 92a057bf73..dedb234929 100644 --- a/rdmo/projects/tests/test_view_snapshot.py +++ b/rdmo/projects/tests/test_view_snapshot.py @@ -14,35 +14,26 @@ ('manager', 'manager'), ('author', 'author'), ('guest', 'guest'), - ('user', 'user'), + ('api', 'api'), ('site', 'site'), ('anonymous', None), ) -view_snapshot_permission_map = { - 'owner': [1, 2, 3, 4, 5], - 'manager': [1, 3, 5], - 'author': [1, 3, 5], - 'guest': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] -} - add_snapshot_permission_map = change_snapshot_permission_map = rollback_snapshot_permission_map = { - 'owner': [1, 2, 3, 4, 5], + 'owner': [1, 2, 3, 4, 5, 12], 'manager': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] + 'api': [1, 2, 3, 4, 5, 12], + 'site': [1, 2, 3, 4, 5, 12] } export_snapshot_permission_map = { - 'owner': [1, 2, 3, 4, 5], - 'manager': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] + 'owner': [1, 2, 3, 4, 5, 12], + 'manager': [1, 3, 5, 12], + 'api': [1, 2, 3, 4, 5, 12], + 'site': [1, 2, 3, 4, 5, 12] } -projects = [1, 2, 3, 4, 5] +projects = [1, 2, 3, 4, 5, 12] snapshots = [1, 3, 7, 4, 5, 6] snapshots_project = 1 diff --git a/rdmo/projects/tests/test_viewset_issue.py b/rdmo/projects/tests/test_viewset_issue.py index 96f354478f..715821ad7b 100644 --- a/rdmo/projects/tests/test_viewset_issue.py +++ b/rdmo/projects/tests/test_viewset_issue.py @@ -16,12 +16,12 @@ ) view_issue_permission_map = { - 'owner': [1, 2, 3, 4, 5, 10], - 'manager': [1, 3, 5], - 'author': [1, 3, 5], - 'guest': [1, 3, 5], - 'api': [1, 2, 3, 4, 5, 10], - 'site': [1, 2, 3, 4, 5, 10] + 'owner': [1, 2, 3, 4, 5, 10, 12], + 'manager': [1, 3, 5, 12], + 'author': [1, 3, 5, 12], + 'guest': [1, 3, 5, 12], + 'api': [1, 2, 3, 4, 5, 10, 12], + 'site': [1, 2, 3, 4, 5, 10, 12] } urlnames = { @@ -31,6 +31,7 @@ projects = [1, 2, 3, 4, 5, 10] issues = [1, 2, 3, 4] +issues_internal = [8, 9] site_id = 1 project_id = 1 @@ -51,7 +52,7 @@ def test_list(db, client, username, password): assert isinstance(response.json(), list) if username == 'user': - assert sorted([item['id'] for item in response.json()]) == [] + assert sorted([item['id'] for item in response.json()]) == issues_internal else: values_list = Issue.objects.filter(project__in=view_issue_permission_map.get(username, [])) \ .order_by('id').values_list('id', flat=True) diff --git a/rdmo/projects/tests/test_viewset_membership.py b/rdmo/projects/tests/test_viewset_membership.py index 6bad007774..c099c4294b 100644 --- a/rdmo/projects/tests/test_viewset_membership.py +++ b/rdmo/projects/tests/test_viewset_membership.py @@ -16,12 +16,12 @@ ) view_membership_permission_map = { - 'owner': [1, 2, 3, 4, 5, 10], - 'manager': [1, 3, 5, 7], - 'author': [1, 3, 5, 8], - 'guest': [1, 3, 5, 9], - 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + 'owner': [1, 2, 3, 4, 5, 10, 12], + 'manager': [1, 3, 5, 7, 12], + 'author': [1, 3, 5, 8, 12], + 'guest': [1, 3, 5, 9, 12], + 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12], + 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12] } urlnames = { @@ -31,6 +31,7 @@ projects = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] memberships = [1, 2, 3, 4] +memberships_internal = [16] membership_roles = ('owner', 'manager', 'author', 'guest') @@ -46,7 +47,7 @@ def test_list(db, client, username, password): assert isinstance(response.json(), list) if username == 'user': - assert sorted([item['id'] for item in response.json()]) == [] + assert sorted([item['id'] for item in response.json()]) == memberships_internal else: values_list = Membership.objects.filter(project__in=view_membership_permission_map.get(username, [])) \ .order_by('id').values_list('id', flat=True) diff --git a/rdmo/projects/tests/test_viewset_project.py b/rdmo/projects/tests/test_viewset_project.py index 9f2d97fe8a..f45351743b 100644 --- a/rdmo/projects/tests/test_viewset_project.py +++ b/rdmo/projects/tests/test_viewset_project.py @@ -17,25 +17,26 @@ ) view_project_permission_map = { - 'owner': [1, 2, 3, 4, 5, 10], - 'manager': [1, 3, 5, 7], - 'author': [1, 3, 5, 8], - 'guest': [1, 3, 5, 9], - 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], - 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + 'owner': [1, 2, 3, 4, 5, 10, 12], + 'manager': [1, 3, 5, 7, 12], + 'author': [1, 3, 5, 8, 12], + 'guest': [1, 3, 5, 9, 12], + 'user': [12], + 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] } change_project_permission_map = { - 'owner': [1, 2, 3, 4, 5, 10], + 'owner': [1, 2, 3, 4, 5, 10, 12], 'manager': [1, 3, 5, 7], - 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12], + 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12] } delete_project_permission_map = { - 'owner': [1, 2, 3, 4, 5, 10], - 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + 'owner': [1, 2, 3, 4, 5, 10, 12], + 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12], + 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12] } urlnames = { @@ -50,7 +51,8 @@ 'imports': 'v1-projects:project-imports' } -projects = [1, 2, 3, 4, 5] +projects = [1, 2, 3, 4, 5, 12] +projects_internal = [12] conditions = [1] catalog_id = 1 @@ -80,8 +82,7 @@ def test_list(db, client, username, password): assert isinstance(response_data, dict) if username == 'user': - assert response_data['count'] == 0 - assert response_data['results'] == [] + assert sorted([item['id'] for item in response.json().get('results')]) == projects_internal else: values_list = Project.objects.filter(id__in=view_project_permission_map.get(username, [])) \ .values_list('id', flat=True) @@ -91,6 +92,21 @@ def test_list(db, client, username, password): assert response.status_code == 401 +def test_list_user(db, client): + client.login(username='user', password='user') + + url = reverse(urlnames['list']) + '?user=1' + response = client.get(url) + response_data = response.json() + + assert response.status_code == 200 + assert isinstance(response_data, dict) + + values_list = Project.objects.filter(id__in=projects_internal).values_list('id', flat=True) + assert response_data['count'] == len(values_list) + assert [item['id'] for item in response_data['results']] == list(values_list[:page_size]) + + @pytest.mark.parametrize('username,password', users) @pytest.mark.parametrize('project_id', projects) def test_detail(db, client, username, password, project_id): @@ -461,7 +477,6 @@ def test_navigation(db, client, username, password, project_id): client.login(username=username, password=password) url = reverse(urlnames['navigation'], args=[project_id]) - print(url) response = client.get(url) if project_id in view_project_permission_map.get(username, []): diff --git a/rdmo/projects/tests/test_viewset_project_integration.py b/rdmo/projects/tests/test_viewset_project_integration.py index d1271081cc..ca373a127c 100644 --- a/rdmo/projects/tests/test_viewset_project_integration.py +++ b/rdmo/projects/tests/test_viewset_project_integration.py @@ -18,19 +18,20 @@ ) view_integration_permission_map = { - 'owner': [1, 2, 3, 4, 5], - 'manager': [1, 3, 5], - 'author': [1, 3, 5], - 'guest': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] + 'owner': [1, 2, 3, 4, 5, 12], + 'manager': [1, 3, 5, 12], + 'author': [1, 3, 5, 12], + 'guest': [1, 3, 5, 12], + 'user': [12], + 'api': [1, 2, 3, 4, 5, 12], + 'site': [1, 2, 3, 4, 5, 12] } add_integration_permission_map = change_integration_permission_map = delete_integration_permission_map = { - 'owner': [1, 2, 3, 4, 5], + 'owner': [1, 2, 3, 4, 5, 12], 'manager': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] + 'api': [1, 2, 3, 4, 5, 12], + 'site': [1, 2, 3, 4, 5, 12] } urlnames = { @@ -38,8 +39,9 @@ 'detail': 'v1-projects:project-integration-detail' } -projects = [1, 2, 3, 4, 5] +projects = [1, 2, 3, 4, 5, 12] integrations = [1, 2] +integrations_internal = [] @pytest.mark.parametrize('username,password', users) @@ -54,7 +56,7 @@ def test_list(db, client, username, password, project_id): assert response.status_code == 200 if username == 'user': - assert sorted([item['id'] for item in response.json()]) == [] + assert sorted([item['id'] for item in response.json()]) == integrations_internal else: values_list = Integration.objects.filter(project_id=project_id) \ .order_by('id').values_list('id', flat=True) diff --git a/rdmo/projects/tests/test_viewset_project_issue.py b/rdmo/projects/tests/test_viewset_project_issue.py index 16bd036b41..62b9e21e85 100644 --- a/rdmo/projects/tests/test_viewset_project_issue.py +++ b/rdmo/projects/tests/test_viewset_project_issue.py @@ -16,27 +16,28 @@ ) view_issue_permission_map = { - 'owner': [1, 2, 3, 4, 5], - 'manager': [1, 3, 5], - 'author': [1, 3, 5], - 'guest': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] + 'owner': [1, 2, 3, 4, 5, 12], + 'manager': [1, 3, 5, 12], + 'author': [1, 3, 5, 12], + 'guest': [1, 3, 5, 12], + 'user': [12], + 'api': [1, 2, 3, 4, 5, 12], + 'site': [1, 2, 3, 4, 5, 12] } add_issue_permission_map = delete_issue_permission_map = { - 'owner': [1, 2, 3, 4, 5], + 'owner': [1, 2, 3, 4, 5, 12], 'manager': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] + 'api': [1, 2, 3, 4, 5, 12], + 'site': [1, 2, 3, 4, 5, 12] } change_issue_permission_map = { - 'owner': [1, 2, 3, 4, 5], + 'owner': [1, 2, 3, 4, 5, 12], 'manager': [1, 3, 5], 'author': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] + 'api': [1, 2, 3, 4, 5, 12], + 'site': [1, 2, 3, 4, 5, 12] } urlnames = { @@ -44,8 +45,9 @@ 'detail': 'v1-projects:project-issue-detail' } -projects = [1, 2, 3, 4, 5] -issues = [1, 2, 3, 4] +projects = [1, 2, 3, 4, 5, 12] +issues = [1, 2, 3, 4, 9] +issues_internal = [8, 9] issue_status = ('open', 'in_progress', 'closed') @@ -62,7 +64,7 @@ def test_list(db, client, username, password, project_id): assert response.status_code == 200 if username == 'user': - assert sorted([item['id'] for item in response.json()]) == [] + assert sorted([item['id'] for item in response.json()]) == issues_internal else: values_list = Issue.objects.filter(project_id=project_id) \ .order_by('id').values_list('id', flat=True) @@ -72,16 +74,15 @@ def test_list(db, client, username, password, project_id): @pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('project_id', projects) @pytest.mark.parametrize('issue_id', issues) -def test_detail(db, client, username, password, project_id, issue_id): +def test_detail(db, client, username, password, issue_id): client.login(username=username, password=password) - issue = Issue.objects.filter(project_id=project_id, id=issue_id).first() + issue = Issue.objects.get(id=issue_id) - url = reverse(urlnames['detail'], args=[project_id, issue_id]) + url = reverse(urlnames['detail'], args=[issue.project_id, issue_id]) response = client.get(url) - if issue and project_id in view_issue_permission_map.get(username, []): + if issue.project_id in view_issue_permission_map.get(username, []): assert response.status_code == 200 assert isinstance(response.json(), dict) assert response.json().get('id') == issue_id @@ -106,40 +107,39 @@ def test_create(db, client, username, password, project_id): @pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('project_id', projects) @pytest.mark.parametrize('issue_id', issues) @pytest.mark.parametrize('status', issue_status) -def test_update(db, client, username, password, project_id, issue_id, status): +def test_update(db, client, username, password, issue_id, status): client.login(username=username, password=password) - issue = Issue.objects.filter(project_id=project_id, id=issue_id).first() + issue = Issue.objects.get(id=issue_id) - url = reverse(urlnames['detail'], args=[project_id, issue_id]) + url = reverse(urlnames['detail'], args=[issue.project_id, issue_id]) data = { 'status': status } response = client.put(url, data, content_type='application/json') - if issue and project_id in change_issue_permission_map.get(username, []): + if issue.project_id in change_issue_permission_map.get(username, []): assert response.status_code == 200 assert response.json().get('status') == status - elif issue and project_id in view_issue_permission_map.get(username, []): + elif issue.project_id in view_issue_permission_map.get(username, []): assert response.status_code == 403 else: assert response.status_code == 404 @pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('project_id', projects) @pytest.mark.parametrize('issue_id', issues) -def test_delete(db, client, username, password, project_id, issue_id): +def test_delete(db, client, username, password, issue_id): client.login(username=username, password=password) + issue = Issue.objects.get(id=issue_id) - url = reverse(urlnames['detail'], args=[project_id, issue_id]) + url = reverse(urlnames['detail'], args=[issue.project_id, issue_id]) response = client.delete(url) - if project_id in delete_issue_permission_map.get(username, []): + if issue.project_id in delete_issue_permission_map.get(username, []): assert response.status_code == 405 - elif project_id in view_issue_permission_map.get(username, []): + elif issue.project_id in view_issue_permission_map.get(username, []): assert response.status_code == 405 else: assert response.status_code == 404 diff --git a/rdmo/projects/tests/test_viewset_project_membership.py b/rdmo/projects/tests/test_viewset_project_membership.py index d935bff060..65ef0029ab 100644 --- a/rdmo/projects/tests/test_viewset_project_membership.py +++ b/rdmo/projects/tests/test_viewset_project_membership.py @@ -17,18 +17,19 @@ ) view_membership_permission_map = { - 'owner': [1, 2, 3, 4, 5], - 'manager': [1, 3, 5], - 'author': [1, 3, 5], - 'guest': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] + 'owner': [1, 2, 3, 4, 5, 12], + 'manager': [1, 3, 5, 12], + 'author': [1, 3, 5, 12], + 'guest': [1, 3, 5, 12], + 'user': [12], + 'api': [1, 2, 3, 4, 5, 12], + 'site': [1, 2, 3, 4, 5, 12] } add_membership_permission_map = change_membership_permission_map = delete_membership_permission_map = { - 'owner': [1, 2, 3, 4, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] + 'owner': [1, 2, 3, 4, 5, 12], + 'api': [1, 2, 3, 4, 5, 12], + 'site': [1, 2, 3, 4, 5, 12] } urlnames = { @@ -36,8 +37,9 @@ 'detail': 'v1-projects:project-membership-detail' } -projects = [1, 2, 3, 4, 5] +projects = [1, 2, 3, 4, 5, 12] memberships = [1, 2, 3, 4] +memberships_internal = [16] membership_roles = ('owner', 'manager', 'author', 'guest') @@ -53,7 +55,7 @@ def test_list(db, client, username, password, project_id): assert response.status_code == 200 if username == 'user': - assert sorted([item['id'] for item in response.json()]) == [] + assert sorted([item['id'] for item in response.json()]) == memberships_internal else: values_list = Membership.objects.filter(project_id=project_id) \ .order_by('id').values_list('id', flat=True) @@ -63,16 +65,15 @@ def test_list(db, client, username, password, project_id): @pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('project_id', projects) @pytest.mark.parametrize('membership_id', memberships) -def test_detail(db, client, username, password, project_id, membership_id): +def test_detail(db, client, username, password, membership_id): client.login(username=username, password=password) - membership = Membership.objects.filter(project_id=project_id, id=membership_id).first() + membership = Membership.objects.get(id=membership_id) - url = reverse(urlnames['detail'], args=[project_id, membership_id]) + url = reverse(urlnames['detail'], args=[membership.project_id, membership_id]) response = client.get(url) - if membership and project_id in view_membership_permission_map.get(username, []): + if membership.project_id in view_membership_permission_map.get(username, []): assert response.status_code == 200 assert isinstance(response.json(), dict) assert response.json().get('id') == membership_id @@ -104,40 +105,38 @@ def test_create(db, client, username, password, project_id, membership_role): @pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('project_id', projects) @pytest.mark.parametrize('membership_id', memberships) @pytest.mark.parametrize('membership_role', membership_roles) -def test_update(db, client, username, password, project_id, membership_id, membership_role): +def test_update(db, client, username, password, membership_id, membership_role): client.login(username=username, password=password) - membership = Membership.objects.filter(project_id=project_id, id=membership_id).first() + membership = Membership.objects.get(id=membership_id) - url = reverse(urlnames['detail'], args=[project_id, membership_id]) + url = reverse(urlnames['detail'], args=[membership.project_id, membership_id]) data = { 'role': membership_role } response = client.put(url, data, content_type='application/json') - if membership and project_id in change_membership_permission_map.get(username, []): + if membership.project_id in change_membership_permission_map.get(username, []): assert response.status_code == 200 - elif membership and project_id in view_membership_permission_map.get(username, []): + elif membership.project_id in view_membership_permission_map.get(username, []): assert response.status_code == 403 else: assert response.status_code == 404 @pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('project_id', projects) @pytest.mark.parametrize('membership_id', memberships) -def test_delete(db, client, username, password, project_id, membership_id): +def test_delete(db, client, username, password, membership_id): client.login(username=username, password=password) - membership = Membership.objects.filter(project_id=project_id, id=membership_id).first() + membership = Membership.objects.get(id=membership_id) - url = reverse(urlnames['detail'], args=[project_id, membership_id]) + url = reverse(urlnames['detail'], args=[membership.project_id, membership_id]) response = client.delete(url) - if membership and project_id in delete_membership_permission_map.get(username, []): + if membership.project_id in change_membership_permission_map.get(username, []): assert response.status_code == 204 - elif membership and project_id in view_membership_permission_map.get(username, []): + elif membership.project_id in view_membership_permission_map.get(username, []): assert response.status_code == 403 else: assert response.status_code == 404 diff --git a/rdmo/projects/tests/test_viewset_project_page.py b/rdmo/projects/tests/test_viewset_project_page.py index 228ee2e3d3..b6c562692c 100644 --- a/rdmo/projects/tests/test_viewset_project_page.py +++ b/rdmo/projects/tests/test_viewset_project_page.py @@ -14,12 +14,13 @@ ) view_questionset_permission_map = { - 'owner': [1, 2, 3, 4, 5], - 'manager': [1, 3, 5], - 'author': [1, 3, 5], - 'guest': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] + 'owner': [1, 2, 3, 4, 5, 12], + 'manager': [1, 3, 5, 12], + 'author': [1, 3, 5, 12], + 'guest': [1, 3, 5, 12], + 'user': [12], + 'api': [1, 2, 3, 4, 5, 12], + 'site': [1, 2, 3, 4, 5, 12] } urlnames = { @@ -27,7 +28,7 @@ 'detail': 'v1-projects:project-page-detail' } -projects = [1, 2, 3, 4, 5] +projects = [1, 2, 3, 4, 5, 12] pages = [1] diff --git a/rdmo/projects/tests/test_viewset_project_progress.py b/rdmo/projects/tests/test_viewset_project_progress.py index 5d7529261f..f02c5366cd 100644 --- a/rdmo/projects/tests/test_viewset_project_progress.py +++ b/rdmo/projects/tests/test_viewset_project_progress.py @@ -16,27 +16,28 @@ ) view_progress_permission_map = { - 'owner': [1, 2, 3, 4, 5, 10], - 'manager': [1, 3, 5, 7], - 'author': [1, 3, 5, 8], - 'guest': [1, 3, 5, 9], - 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], - 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + 'owner': [1, 2, 3, 4, 5, 10, 12], + 'manager': [1, 3, 5, 7, 12], + 'author': [1, 3, 5, 8, 12], + 'guest': [1, 3, 5, 9, 12], + 'user': [12], + 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] } change_progress_permission_map = { - 'owner': [1, 2, 3, 4, 5, 10], + 'owner': [1, 2, 3, 4, 5, 10, 12], 'manager': [1, 3, 5, 7], - 'author': [1, 3, 5, 8], - 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], - 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + 'author': [1, 3, 5, 7], + 'api': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + 'site': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] } urlnames = { 'progress': 'v1-projects:project-progress' } -projects = [1, 2, 3, 4, 5] +projects = [1, 2, 3, 4, 5, 12] @pytest.mark.parametrize('username,password', users) @pytest.mark.parametrize('project_id', projects) diff --git a/rdmo/projects/tests/test_viewset_project_snapshot.py b/rdmo/projects/tests/test_viewset_project_snapshot.py index 54a91c3f90..3ea22dda3e 100644 --- a/rdmo/projects/tests/test_viewset_project_snapshot.py +++ b/rdmo/projects/tests/test_viewset_project_snapshot.py @@ -21,19 +21,20 @@ ) view_snapshot_permission_map = { - 'owner': [1, 2, 3, 4, 5], - 'manager': [1, 3, 5], - 'author': [1, 3, 5], - 'guest': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] + 'owner': [1, 2, 3, 4, 5, 12], + 'manager': [1, 3, 5, 12], + 'author': [1, 3, 5, 12], + 'guest': [1, 3, 5, 12], + 'user': [12], + 'api': [1, 2, 3, 4, 5, 12], + 'site': [1, 2, 3, 4, 5, 12] } add_snapshot_permission_map = change_snapshot_permission_map = delete_snapshot_permission_map = { - 'owner': [1, 2, 3, 4, 5], + 'owner': [1, 2, 3, 4, 5, 12], 'manager': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] + 'api': [1, 2, 3, 4, 5, 12], + 'site': [1, 2, 3, 4, 5, 12] } urlnames = { @@ -41,9 +42,9 @@ 'detail': 'v1-projects:project-snapshot-detail' } -projects = [1, 2, 3, 4, 5] +projects = [1, 2, 3, 4, 5, 12] snapshots = [1, 3, 7, 4, 5, 6] - +snapshots_internal = [8] @pytest.mark.parametrize('username,password', users) @pytest.mark.parametrize('project_id', projects) @@ -58,7 +59,7 @@ def test_list(db, client, username, password, project_id): assert isinstance(response.json(), list) if username == 'user': - assert sorted([item['id'] for item in response.json()]) == [] + assert sorted([item['id'] for item in response.json()]) == snapshots_internal else: values_list = Snapshot.objects.filter(project_id=project_id) \ .order_by('id').values_list('id', flat=True) @@ -69,16 +70,15 @@ def test_list(db, client, username, password, project_id): @pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('project_id', projects) @pytest.mark.parametrize('snapshot_id', snapshots) -def test_detail(db, client, username, password, project_id, snapshot_id): +def test_detail(db, client, username, password, snapshot_id): client.login(username=username, password=password) - snapshot = Snapshot.objects.filter(project_id=project_id, id=snapshot_id).filter() + snapshot = Snapshot.objects.get(id=snapshot_id) - url = reverse(urlnames['detail'], args=[project_id, snapshot_id]) + url = reverse(urlnames['detail'], args=[snapshot.project_id, snapshot_id]) response = client.get(url) - if snapshot and project_id in view_snapshot_permission_map.get(username, []): + if snapshot.project_id in view_snapshot_permission_map.get(username, []): assert response.status_code == 200 assert isinstance(response.json(), dict) assert response.json().get('id') == snapshot_id @@ -123,59 +123,56 @@ def test_create(db, client, files, username, password, project_id): @pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('project_id', projects) @pytest.mark.parametrize('snapshot_id', snapshots) -def test_update(db, client, files, username, password, project_id, snapshot_id): +def test_update(db, client, files, username, password, snapshot_id): client.login(username=username, password=password) - project = Project.objects.get(id=project_id) - snapshot = Snapshot.objects.filter(project_id=project_id, id=snapshot_id).first() + snapshot = Snapshot.objects.get(id=snapshot_id) - snapshot_count = project.snapshots.count() - values_count = project.values.count() - values_files = [value.file.name for value in project.values.filter(value_type=VALUE_TYPE_FILE)] + snapshot_count = snapshot.project.snapshots.count() + values_count = snapshot.project.values.count() + values_files = [value.file.name for value in snapshot.project.values.filter(value_type=VALUE_TYPE_FILE)] - url = reverse(urlnames['detail'], args=[project_id, snapshot_id]) + url = reverse(urlnames['detail'], args=[snapshot.project_id, snapshot_id]) data = { 'title': 'A new title', 'description': 'A new description' } response = client.put(url, data, content_type='application/json') - if snapshot and project_id in change_snapshot_permission_map.get(username, []): + if snapshot.project_id in change_snapshot_permission_map.get(username, []): assert response.status_code == 200 assert isinstance(response.json(), dict) - assert response.json().get('id') in project.snapshots.values_list('id', flat=True) - elif snapshot and project_id in view_snapshot_permission_map.get(username, []): + assert response.json().get('id') in snapshot.project.snapshots.values_list('id', flat=True) + elif snapshot.project_id in view_snapshot_permission_map.get(username, []): assert response.status_code == 403 else: assert response.status_code == 404 - assert project.snapshots.count() == snapshot_count - assert project.values.count() == values_count + assert snapshot.project.snapshots.count() == snapshot_count + assert snapshot.project.values.count() == values_count for file_value in values_files: assert Path(settings.MEDIA_ROOT).joinpath(file_value).exists() @pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('project_id', projects) @pytest.mark.parametrize('snapshot_id', snapshots) -def test_delete(db, client, files, username, password, project_id, snapshot_id): +def test_delete(db, client, files, username, password, snapshot_id): client.login(username=username, password=password) - project = Project.objects.get(id=project_id) + snapshot = Snapshot.objects.get(id=snapshot_id) - snapshot_count = project.snapshots.count() - values_count = project.values.count() - values_files = [value.file.name for value in project.values.filter(value_type=VALUE_TYPE_FILE)] + snapshot_count = snapshot.project.snapshots.count() + values_count = snapshot.project.values.count() + values_files = [value.file.name for value in snapshot.project.values.filter(value_type=VALUE_TYPE_FILE)] - url = reverse(urlnames['detail'], args=[project_id, snapshot_id]) + url = reverse(urlnames['detail'], args=[snapshot.project_id, snapshot_id]) response = client.delete(url) - if project_id in view_snapshot_permission_map.get(username, []): + if snapshot.project_id in view_snapshot_permission_map.get(username, []): assert response.status_code == 405 else: assert response.status_code == 404 - assert project.snapshots.count() == snapshot_count - assert project.values.count() == values_count + assert snapshot.project.snapshots.count() == snapshot_count + assert snapshot.project.values.count() == values_count for file_value in values_files: assert Path(settings.MEDIA_ROOT).joinpath(file_value).exists() diff --git a/rdmo/projects/tests/test_viewset_project_value.py b/rdmo/projects/tests/test_viewset_project_value.py index e221b82214..aaa1fb2693 100644 --- a/rdmo/projects/tests/test_viewset_project_value.py +++ b/rdmo/projects/tests/test_viewset_project_value.py @@ -21,20 +21,21 @@ ) view_value_permission_map = { - 'owner': [1, 2, 3, 4, 5], - 'manager': [1, 3, 5], - 'author': [1, 3, 5], - 'guest': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] + 'owner': [1, 2, 3, 4, 5, 12], + 'manager': [1, 3, 5, 12], + 'author': [1, 3, 5, 12], + 'guest': [1, 3, 5, 12], + 'user': [12], + 'api': [1, 2, 3, 4, 5, 12], + 'site': [1, 2, 3, 4, 5, 12] } add_value_permission_map = change_value_permission_map = delete_value_permission_map = { - 'owner': [1, 2, 3, 4, 5], + 'owner': [1, 2, 3, 4, 5, 12], 'manager': [1, 3, 5], 'author': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] + 'api': [1, 2, 3, 4, 5, 12], + 'site': [1, 2, 3, 4, 5, 12] } urlnames = { @@ -44,8 +45,16 @@ 'file': 'v1-projects:project-value-file' } -projects = [1, 2, 3, 4, 5] -values = [1, 2, 3, 4, 5, 6, 7, 238, 242, 247, 248, 249] +projects = [1, 2, 3, 4, 5, 12] +values = [ + 1, 2, 3, 4, 5, 6, 7, 238, # from Test <1> + 242, # from Parent <2> + 247, # from Child1 <3> + 248, # from Child2 <4> + 249, # from Child11 <5> + 456 # from Internal <12> +] +values_internal = [456] attribute_id = 1 option_id = 1 @@ -80,7 +89,7 @@ def test_list(db, client, username, password, project_id): assert isinstance(response.json(), list) if username == 'user': - assert sorted([item['id'] for item in response.json()]) == [] + assert sorted([item['id'] for item in response.json()]) == values_internal else: values_list = Value.objects.filter(project_id=project_id) \ .filter(snapshot_id=None) \ @@ -92,16 +101,15 @@ def test_list(db, client, username, password, project_id): @pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('project_id', projects) @pytest.mark.parametrize('value_id', values) -def test_detail(db, client, username, password, project_id, value_id): +def test_detail(db, client, username, password, value_id): client.login(username=username, password=password) - value = Value.objects.filter(project_id=project_id, id=value_id).filter() + value = Value.objects.get(id=value_id) - url = reverse(urlnames['detail'], args=[project_id, value_id]) + url = reverse(urlnames['detail'], args=[value.project_id, value_id]) response = client.get(url) - if value and project_id in view_value_permission_map.get(username, []): + if value.project_id in view_value_permission_map.get(username, []): assert response.status_code == 200 assert isinstance(response.json(), dict) assert response.json().get('id') == value_id @@ -193,13 +201,12 @@ def test_create_external(db, client, username, password, project_id, value_type, @pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('project_id', projects) @pytest.mark.parametrize('value_id', values) -def test_update(db, client, username, password, project_id, value_id): +def test_update(db, client, username, password, value_id): client.login(username=username, password=password) - value = Value.objects.filter(project_id=project_id, id=value_id).first() + value = Value.objects.get(id=value_id) - url = reverse(urlnames['detail'], args=[project_id, value_id]) + url = reverse(urlnames['detail'], args=[value.project_id, value_id]) data = { 'attribute': attribute_id, 'set_index': 0, @@ -210,30 +217,30 @@ def test_update(db, client, username, password, project_id, value_id): } response = client.put(url, data, content_type='application/json') - if value and project_id in change_value_permission_map.get(username, []): + if value.project_id in change_value_permission_map.get(username, []): assert response.status_code == 200 assert isinstance(response.json(), dict) - assert response.json().get('id') in Value.objects.filter(project_id=project_id).values_list('id', flat=True) - elif value and project_id in view_value_permission_map.get(username, []): + assert response.json().get('id') in Value.objects.filter(project_id=value.project_id) \ + .values_list('id', flat=True) + elif value.project_id in view_value_permission_map.get(username, []): assert response.status_code == 403 else: assert response.status_code == 404 @pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('project_id', projects) @pytest.mark.parametrize('value_id', values) -def test_delete(db, client, username, password, project_id, value_id): +def test_delete(db, client, username, password, value_id): client.login(username=username, password=password) - value = Value.objects.filter(project_id=project_id, id=value_id).first() + value = Value.objects.get(id=value_id) - url = reverse(urlnames['detail'], args=[project_id, value_id]) + url = reverse(urlnames['detail'], args=[value.project_id, value_id]) response = client.delete(url) - if value and project_id in delete_value_permission_map.get(username, []): + if value.project_id in delete_value_permission_map.get(username, []): assert response.status_code == 204 assert not Value.objects.filter(pk=value_id).exists() - elif value and project_id in view_value_permission_map.get(username, []): + elif value.project_id in view_value_permission_map.get(username, []): assert response.status_code == 403 assert Value.objects.filter(pk=value_id).exists() else: @@ -267,16 +274,15 @@ def test_set(db, client, username, password, project_id, value_id, set_values_co @pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('project_id', projects) @pytest.mark.parametrize('value_id', values) -def test_file_get(db, client, files, username, password, project_id, value_id): +def test_file_get(db, client, files, username, password, value_id): client.login(username=username, password=password) - value = Value.objects.filter(project_id=project_id, id=value_id).first() + value = Value.objects.get(id=value_id) - url = reverse(urlnames['file'], args=[project_id, value_id]) + url = reverse(urlnames['file'], args=[value.project_id, value_id]) response = client.get(url) - if value and value.value_type == VALUE_TYPE_FILE and project_id in view_value_permission_map.get(username, []): + if value.value_type == VALUE_TYPE_FILE and value.project_id in view_value_permission_map.get(username, []): assert response.status_code == 200 assert response['Content-Type'] == value.file_type assert response['Content-Disposition'] == f'attachment; filename={value.file_name}' @@ -286,22 +292,21 @@ def test_file_get(db, client, files, username, password, project_id, value_id): @pytest.mark.parametrize('username,password', users) -@pytest.mark.parametrize('project_id', projects) @pytest.mark.parametrize('value_id', values) -def test_file_put(db, client, files, username, password, project_id, value_id): +def test_file_put(db, client, files, username, password, value_id): client.login(username=username, password=password) - value = Value.objects.filter(project_id=project_id, id=value_id).first() + value = Value.objects.get(id=value_id) - url = reverse(urlnames['file'], args=[project_id, value_id]) + url = reverse(urlnames['file'], args=[value.project_id, value_id]) file_path = Path(settings.MEDIA_ROOT) / 'test_file.txt' with file_path.open() as fp: response = client.post(url, {'name': 'test_file.txt', 'file': fp}) - if value and project_id in change_value_permission_map.get(username, []): + if value.project_id in change_value_permission_map.get(username, []): assert response.status_code == 200 assert response.json().get('file_name') == 'test_file.txt' - elif value and project_id in view_value_permission_map.get(username, []): + elif value.project_id in view_value_permission_map.get(username, []): assert response.status_code == 403 else: assert response.status_code == 404 diff --git a/rdmo/projects/tests/test_viewset_project_visibility.py b/rdmo/projects/tests/test_viewset_project_visibility.py new file mode 100644 index 0000000000..967ad27f88 --- /dev/null +++ b/rdmo/projects/tests/test_viewset_project_visibility.py @@ -0,0 +1,139 @@ +import pytest + +from django.urls import reverse + +from ..models import Project, Visibility + +users = ( + ('owner', 'owner'), + ('manager', 'manager'), + ('author', 'author'), + ('guest', 'guest'), + ('api', 'api'), + ('user', 'user'), + ('site', 'site'), + ('anonymous', None), +) + +project_id = 12 + +@pytest.mark.parametrize('username,password', users) +def test_project_visibility_get(db, client, username, password): + client.login(username=username, password=password) + + # project = Project.objects.get(id=project_id) + + url = reverse('v1-projects:project-visibility', args=[project_id]) + response = client.get(url) + + if username in ['admin', 'site', 'api']: + assert response.status_code == 200 + else: + if password: + assert response.status_code == 404 + else: + assert response.status_code == 401 + + +@pytest.mark.parametrize('username,password', users) +def test_project_visibility_get_not_found(db, client, username, password): + client.login(username=username, password=password) + + project = Project.objects.get(id=project_id) + project.visibility.delete() + + url = reverse('v1-projects:project-visibility', args=[project_id]) + response = client.get(url) + + if password: + assert response.status_code == 404 + else: + assert response.status_code == 401 + + +@pytest.mark.parametrize('username,password', users) +def test_project_visibility_post_create(db, client, username, password): + client.login(username=username, password=password) + + project = Project.objects.get(id=project_id) + project.visibility.delete() + + url = reverse('v1-projects:project-visibility', args=[project_id]) + data = {} + response = client.post(url, data) + + if username in ['admin', 'site', 'api']: + assert response.status_code == 200 + assert Project.objects.get(pk=project_id).visibility + else: + if password: + assert response.status_code == 404 + else: + assert response.status_code == 401 + + with pytest.raises(Visibility.DoesNotExist): + assert Project.objects.get(pk=project_id).visibility + + +@pytest.mark.parametrize('username,password', users) +def test_project_visibility_post_update(db, client, username, password): + client.login(username=username, password=password) + + url = reverse('v1-projects:project-visibility', args=[project_id]) + data = { + 'sites': [2] + } + response = client.post(url, data) + + project = Project.objects.get(pk=project_id) + + if username in ['admin', 'site', 'api']: + assert response.status_code == 200 + assert project.visibility + assert [site.id for site in project.visibility.sites.all()] == [2] + else: + if password: + assert response.status_code == 404 + else: + assert response.status_code == 401 + + assert project.visibility + assert [site.id for site in project.visibility.sites.all()] == [1] # from the fixture + + +@pytest.mark.parametrize('username,password', users) +def test_project_visibility_post_delete(db, client, username, password): + client.login(username=username, password=password) + + url = reverse('v1-projects:project-visibility', args=[project_id]) + response = client.delete(url) + + project = Project.objects.get(pk=project_id) + + if username in ['admin', 'site', 'api']: + assert response.status_code == 204 + with pytest.raises(Visibility.DoesNotExist): + assert project.visibility + else: + if password: + assert response.status_code == 404 + else: + assert response.status_code == 401 + + assert project.visibility + + +@pytest.mark.parametrize('username,password', users) +def test_project_visibility_post_delete_not_found(db, client, username, password): + client.login(username=username, password=password) + + project = Project.objects.get(pk=project_id) + project.visibility.delete() + + url = reverse('v1-projects:project-visibility', args=[project_id]) + response = client.delete(url) + + if password: + assert response.status_code == 404 + else: + assert response.status_code == 401 diff --git a/rdmo/projects/tests/test_viewset_snapshot.py b/rdmo/projects/tests/test_viewset_snapshot.py index 40fedc1bda..49f8b38450 100644 --- a/rdmo/projects/tests/test_viewset_snapshot.py +++ b/rdmo/projects/tests/test_viewset_snapshot.py @@ -16,12 +16,13 @@ ) view_snapshot_permission_map = { - 'owner': [1, 2, 3, 4, 5], - 'manager': [1, 3, 5], - 'author': [1, 3, 5], - 'guest': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] + 'owner': [1, 2, 3, 4, 5, 12], + 'manager': [1, 3, 5, 12], + 'author': [1, 3, 5, 12], + 'guest': [1, 3, 5, 12], + 'user': [12], + 'api': [1, 2, 3, 4, 5, 12], + 'site': [1, 2, 3, 4, 5, 12] } urlnames = { @@ -29,7 +30,15 @@ 'detail': 'v1-projects:snapshot-detail' } -snapshots = [1, 3, 7, 4, 5, 6] +snapshots = [ + 1, 7, # from Test <1> + 3, # from Parent <2> + 4, # from Child1 <3> + 5, # from Child2 <4> + 6, # from Child11 <5> + 8 # from Internal <12> +] +snapshots_internal = [8] @pytest.mark.parametrize('username,password', users) @@ -44,7 +53,7 @@ def test_list(db, client, username, password): assert isinstance(response.json(), list) if username == 'user': - assert sorted([item['id'] for item in response.json()]) == [] + assert sorted([item['id'] for item in response.json()]) == snapshots_internal else: values_list = Snapshot.objects.filter(project__in=view_snapshot_permission_map.get(username, [])) \ .order_by('id').values_list('id', flat=True) diff --git a/rdmo/projects/tests/test_viewset_value.py b/rdmo/projects/tests/test_viewset_value.py index a2b3dfbf80..6a26af33ec 100644 --- a/rdmo/projects/tests/test_viewset_value.py +++ b/rdmo/projects/tests/test_viewset_value.py @@ -18,12 +18,13 @@ ) view_value_permission_map = { - 'owner': [1, 2, 3, 4, 5], - 'manager': [1, 3, 5], - 'author': [1, 3, 5], - 'guest': [1, 3, 5], - 'api': [1, 2, 3, 4, 5], - 'site': [1, 2, 3, 4, 5] + 'owner': [1, 2, 3, 4, 5, 12], + 'manager': [1, 3, 5, 12], + 'author': [1, 3, 5, 12], + 'guest': [1, 3, 5, 12], + 'user': [12], + 'api': [1, 2, 3, 4, 5, 12], + 'site': [1, 2, 3, 4, 5, 12] } urlnames = { @@ -32,9 +33,18 @@ 'file': 'v1-projects:value-file' } -values = [1, 2, 3, 4, 5, 6, 7, 238, 242, 243, 244, 245] -snapshots = [1, 3, 7, 4, 5, 6] - +values = [ + 1, 2, 3, 4, 5, 6, 7, 238, # from Test <1> + 242, 243, # from Parent <2> + 247, # from Child1 <3> + 248, # from Child2 <4> + 249, # from Child11 <5> + 456, 457 # from Internal <12> +] +values_internal = [456] +values_snapshot_internal = [457] +snapshots = [1, 3, 7, 4, 5, 6, 8] +snapshots_internal = [8] @pytest.mark.parametrize('username,password', users) def test_list(db, client, username, password): @@ -48,7 +58,7 @@ def test_list(db, client, username, password): assert isinstance(response.json(), list) if username == 'user': - assert sorted([item['id'] for item in response.json()]) == [] + assert sorted([item['id'] for item in response.json()]) == values_internal else: values_list = Value.objects.filter(project__in=view_value_permission_map.get(username, [])) \ .filter(snapshot_id=None) \ @@ -71,7 +81,10 @@ def test_list_snapshot(db, client, username, password, snapshot_id): assert isinstance(response.json(), list) if username == 'user': - assert sorted([item['id'] for item in response.json()]) == [] + if snapshot_id in snapshots_internal: + assert sorted([item['id'] for item in response.json()]) == values_snapshot_internal + else: + assert sorted([item['id'] for item in response.json()]) == [] else: values_list = Value.objects.filter(project__in=view_value_permission_map.get(username, [])) \ .filter(snapshot_id=snapshot_id) \ diff --git a/rdmo/projects/urls/__init__.py b/rdmo/projects/urls/__init__.py index 7fa4a27705..ce8d1a95a6 100644 --- a/rdmo/projects/urls/__init__.py +++ b/rdmo/projects/urls/__init__.py @@ -34,6 +34,7 @@ ProjectUpdateTasksView, ProjectUpdateView, ProjectUpdateViewsView, + ProjectUpdateVisibilityView, ProjectViewExportView, ProjectViewView, SnapshotCreateView, @@ -64,6 +65,8 @@ ProjectUpdateView.as_view(), name='project_update'), re_path(r'^(?P