From 5027283c3b31b472bb62cdac238d899aa53556e6 Mon Sep 17 00:00:00 2001
From: Jochen Klar
Date: Thu, 19 Oct 2023 17:04:57 +0200
Subject: [PATCH 1/7] Add permissions to project creation and
PROJECT_CREATE_RESTRICTED and PROJECT_CREATE_GROUPS to settings
---
rdmo/core/settings.py | 3 +
rdmo/projects/permissions.py | 20 ++++--
rdmo/projects/rules.py | 14 +++-
.../projects/templates/projects/projects.html | 8 ++-
rdmo/projects/tests/test_view_project.py | 65 +++++++++++++++++++
.../tests/test_view_project_create_import.py | 61 +++++++++++++++++
rdmo/projects/tests/test_viewset_project.py | 38 +++++++++++
rdmo/projects/views/project_create.py | 10 ++-
8 files changed, 209 insertions(+), 10 deletions(-)
diff --git a/rdmo/core/settings.py b/rdmo/core/settings.py
index c99f1a2a51..78741dada6 100644
--- a/rdmo/core/settings.py
+++ b/rdmo/core/settings.py
@@ -306,6 +306,9 @@
PROJECT_REMOVE_VIEWS = True
+PROJECT_CREATE_RESTRICTED = False
+PROJECT_CREATE_GROUPS = []
+
NESTED_PROJECTS = True
OPTIONSET_PROVIDERS = []
diff --git a/rdmo/projects/permissions.py b/rdmo/projects/permissions.py
index cb924a9c57..749a94449a 100644
--- a/rdmo/projects/permissions.py
+++ b/rdmo/projects/permissions.py
@@ -8,11 +8,21 @@ def has_permission(self, request, view):
if not (request.user and request.user.is_authenticated):
return False
- # always return True:
- # for retrieve, update, partial_update, the permission will be checked on the
- # object level (in the next step), list and create is allowed for every user since
- # the filtering is done in the queryset
- return True
+ if view.detail:
+ # for retrieve, update, partial_update, the permission will be checked on the
+ # object level (in the next step)
+ return True
+ else:
+ if view.action == 'list':
+ # list is allowed for every user since the filtering is done in the queryset
+ return True
+ else:
+ if 'create' in view.action_map.values():
+ # for create, check the permission (from rules.py),
+ # but only if it is not a ReadOnlyValueSet (i.e. only for ProjectViewSet)
+ return super().has_permission(request, view)
+ else:
+ return True
@log_result
def has_object_permission(self, request, view, obj):
diff --git a/rdmo/projects/rules.py b/rdmo/projects/rules.py
index f11061e064..15baa9230b 100644
--- a/rdmo/projects/rules.py
+++ b/rdmo/projects/rules.py
@@ -1,9 +1,21 @@
+from django.conf import settings
from django.contrib.sites.shortcuts import get_current_site
import rules
from rules.predicates import is_superuser
+@rules.predicate
+def can_add_project(user):
+ if settings.PROJECT_CREATE_RESTRICTED:
+ if settings.PROJECT_CREATE_GROUPS:
+ return user.groups.filter(name__in=settings.PROJECT_CREATE_GROUPS).exists()
+ else:
+ return False
+ else:
+ return True
+
+
@rules.predicate
def is_project_member(user, project):
return user in project.member or (project.parent and is_project_member(user, project.parent))
@@ -54,7 +66,7 @@ def is_site_manager_for_current_site(user, request):
# Add rule for check in template
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.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
diff --git a/rdmo/projects/templates/projects/projects.html b/rdmo/projects/templates/projects/projects.html
index b2aed864a1..e577025781 100644
--- a/rdmo/projects/templates/projects/projects.html
+++ b/rdmo/projects/templates/projects/projects.html
@@ -22,8 +22,13 @@
{% endblock %}
{% block sidebar %}
+ {% has_perm 'projects.can_add_project' request.user project as can_add_project %}
+ {% test_rule 'projects.can_view_all_projects' request.user request as can_view_all_projects %}
+
+ {% if can_add_project or can_view_all_projects %}
{% trans 'Options' %}
+ {% if can_add_project %}
-
@@ -33,8 +38,8 @@
{% trans 'Options' %}
+ {% endif %}
- {% test_rule 'projects.can_view_all_projects' request.user request as can_view_all_projects %}
{% if can_view_all_projects %}
-
@@ -44,6 +49,7 @@
{% trans 'Options' %}
{% endif %}
+ {% endif %}
{% trans 'Filter projects' %}
diff --git a/rdmo/projects/tests/test_view_project.py b/rdmo/projects/tests/test_view_project.py
index 7d63410f9c..6f277ed987 100644
--- a/rdmo/projects/tests/test_view_project.py
+++ b/rdmo/projects/tests/test_view_project.py
@@ -2,6 +2,7 @@
import pytest
+from django.contrib.auth.models import Group, User
from django.urls import reverse
from pytest_django.asserts import assertContains, assertNotContains, assertTemplateUsed
@@ -145,6 +146,33 @@ def test_project_create_get(db, client, username, password):
assert response.status_code == 302
+def test_project_create_restricted_get(db, client, settings):
+ settings.PROJECT_CREATE_RESTRICTED = True
+ settings.PROJECT_CREATE_GROUPS = ['projects']
+
+ group = Group.objects.create(name='projects')
+ user = User.objects.get(username='user')
+ user.groups.add(group)
+
+ client.login(username='user', password='user')
+
+ url = reverse('project_create')
+ response = client.get(url)
+
+ assert response.status_code == 200
+
+
+def test_project_create_forbidden_get(db, client, settings):
+ settings.PROJECT_CREATE_RESTRICTED = True
+
+ client.login(username='user', password='user')
+
+ url = reverse('project_create')
+ response = client.get(url)
+
+ assert response.status_code == 403
+
+
@pytest.mark.parametrize('username,password', users)
def test_project_create_get_for_extra_users_and_unavailable_catalogs(db, client, username, password):
client.login(username=username, password=password)
@@ -215,6 +243,43 @@ def test_project_create_post(db, client, username, password):
assert Project.objects.count() == project_count
+def test_project_create_post_restricted(db, client, settings):
+ settings.PROJECT_CREATE_RESTRICTED = True
+ settings.PROJECT_CREATE_GROUPS = ['projects']
+
+ group = Group.objects.create(name='projects')
+ user = User.objects.get(username='user')
+ user.groups.add(group)
+
+ client.login(username='user', password='user')
+
+ url = reverse('project_create')
+ data = {
+ 'title': 'A new project',
+ 'description': 'Some description',
+ 'catalog': catalog_id
+ }
+ response = client.post(url, data)
+
+ assert response.status_code == 302
+
+
+def test_project_create_post_forbidden(db, client, settings):
+ settings.PROJECT_CREATE_RESTRICTED = True
+
+ client.login(username='user', password='user')
+
+ url = reverse('project_create')
+ data = {
+ 'title': 'A new project',
+ 'description': 'Some description',
+ 'catalog': catalog_id
+ }
+ response = client.post(url, data)
+
+ assert response.status_code == 403
+
+
@pytest.mark.parametrize('username,password', users)
def test_project_create_parent_post(db, client, username, password):
client.login(username=username, password=password)
diff --git a/rdmo/projects/tests/test_view_project_create_import.py b/rdmo/projects/tests/test_view_project_create_import.py
index a1c3880719..f8af896dd5 100644
--- a/rdmo/projects/tests/test_view_project_create_import.py
+++ b/rdmo/projects/tests/test_view_project_create_import.py
@@ -4,6 +4,7 @@
import pytest
+from django.contrib.auth.models import Group, User
from django.urls import reverse
from rdmo.core.constants import VALUE_TYPE_FILE
@@ -43,6 +44,31 @@ def test_project_create_import_get(db, client, username, password):
assert response.url.startswith('/account/login/')
+def test_project_create_import_get_restricted(db, client, settings):
+ settings.PROJECT_CREATE_RESTRICTED = True
+ settings.PROJECT_CREATE_GROUPS = ['projects']
+
+ group = Group.objects.create(name='projects')
+ user = User.objects.get(username='user')
+ user.groups.add(group)
+
+ client.login(username='user', password='user')
+
+ url = reverse('project_create_import')
+ response = client.get(url)
+ assert response.status_code == 400
+
+
+def test_project_create_import_get_forbidden(db, client, settings):
+ settings.PROJECT_CREATE_RESTRICTED = True
+
+ client.login(username='user', password='user')
+
+ url = reverse('project_create_import')
+ response = client.get(url)
+ assert response.status_code == 403
+
+
@pytest.mark.parametrize('username,password', users)
def test_project_create_import_post_empty(db, settings, client, username, password):
client.login(username=username, password=password)
@@ -122,6 +148,41 @@ def test_project_create_import_post_upload_file_empty(db, client, username, pass
assert response.url.startswith('/account/login/')
+def test_project_create_import_post_upload_file_restricted(db, client, settings):
+ settings.PROJECT_CREATE_RESTRICTED = True
+ settings.PROJECT_CREATE_GROUPS = ['projects']
+
+ group = Group.objects.create(name='projects')
+ user = User.objects.get(username='user')
+ user.groups.add(group)
+
+ client.login(username='user', password='user')
+
+ url = reverse('project_create_import')
+ xml_file = os.path.join(settings.BASE_DIR, 'xml', 'project.xml')
+ with open(xml_file, encoding='utf8') as f:
+ response = client.post(url, {
+ 'method': 'upload_file',
+ 'uploaded_file': f
+ })
+ assert response.status_code == 302
+
+
+def test_project_create_import_post_upload_file_forbidden(db, client, settings):
+ settings.PROJECT_CREATE_RESTRICTED = True
+
+ client.login(username='user', password='user')
+
+ url = reverse('project_create_import')
+ xml_file = os.path.join(settings.BASE_DIR, 'xml', 'project.xml')
+ with open(xml_file, encoding='utf8') as f:
+ response = client.post(url, {
+ 'method': 'upload_file',
+ 'uploaded_file': f
+ })
+ assert response.status_code == 403
+
+
@pytest.mark.parametrize('username,password', users)
def test_project_create_import_post_import_file(db, settings, client, files, username, password):
client.login(username=username, password=password)
diff --git a/rdmo/projects/tests/test_viewset_project.py b/rdmo/projects/tests/test_viewset_project.py
index 3e523882f4..adf83dc608 100644
--- a/rdmo/projects/tests/test_viewset_project.py
+++ b/rdmo/projects/tests/test_viewset_project.py
@@ -1,5 +1,6 @@
import pytest
+from django.contrib.auth.models import Group, User
from django.urls import reverse
from ..models import Project
@@ -113,6 +114,43 @@ def test_create(db, client, username, password):
assert response.status_code == 401
+def test_create_restricted(db, client, settings):
+ settings.PROJECT_CREATE_RESTRICTED = True
+ settings.PROJECT_CREATE_GROUPS = ['projects']
+
+ group = Group.objects.create(name='projects')
+ user = User.objects.get(username='user')
+ user.groups.add(group)
+
+ client.login(username='user', password='user')
+
+ url = reverse(urlnames['list'])
+ data = {
+ 'title': 'Lorem ipsum dolor sit amet',
+ 'description': 'At vero eos et accusam et justo duo dolores et ea rebum.',
+ 'catalog': catalog_id
+ }
+ response = client.post(url, data)
+
+ assert response.status_code == 201
+
+
+def test_create_forbidden(db, client, settings):
+ settings.PROJECT_CREATE_RESTRICTED = True
+
+ client.login(username='user', password='user')
+
+ url = reverse(urlnames['list'])
+ data = {
+ 'title': 'Lorem ipsum dolor sit amet',
+ 'description': 'At vero eos et accusam et justo duo dolores et ea rebum.',
+ 'catalog': catalog_id
+ }
+ response = client.post(url, data)
+
+ assert response.status_code == 403
+
+
def test_create_catalog_missing(db, client):
client.login(username='user', password='user')
diff --git a/rdmo/projects/views/project_create.py b/rdmo/projects/views/project_create.py
index eb1cac6f50..171bb19156 100644
--- a/rdmo/projects/views/project_create.py
+++ b/rdmo/projects/views/project_create.py
@@ -5,7 +5,7 @@
from django.urls import reverse_lazy
from django.views.generic import CreateView, TemplateView
-from rdmo.core.views import RedirectViewMixin
+from rdmo.core.views import ObjectPermissionMixin, RedirectViewMixin
from rdmo.questions.models import Catalog
from rdmo.tasks.models import Task
from rdmo.views.models import View
@@ -17,9 +17,11 @@
logger = logging.getLogger(__name__)
-class ProjectCreateView(LoginRequiredMixin, RedirectViewMixin, CreateView):
+class ProjectCreateView(ObjectPermissionMixin, LoginRequiredMixin,
+ RedirectViewMixin, CreateView):
model = Project
form_class = ProjectForm
+ permission_required = 'projects.add_project_object'
def get_form_kwargs(self):
catalogs = Catalog.objects.filter_current_site() \
@@ -65,8 +67,10 @@ def form_valid(self, form):
return response
-class ProjectCreateImportView(ProjectImportMixin, LoginRequiredMixin, TemplateView):
+class ProjectCreateImportView(ObjectPermissionMixin, LoginRequiredMixin,
+ ProjectImportMixin, TemplateView):
success_url = reverse_lazy('projects')
+ permission_required = 'projects.add_project_object'
def get(self, request, *args, **kwargs):
self.object = None
From 9d0793e186def793b136c93644f086ba7b1be5e1 Mon Sep 17 00:00:00 2001
From: Jochen Klar
Date: Thu, 19 Oct 2023 18:02:14 +0200
Subject: [PATCH 2/7] Fix can_add_project in template
---
rdmo/projects/templates/projects/projects.html | 6 +++++-
rdmo/projects/views/project_create.py | 4 ++--
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/rdmo/projects/templates/projects/projects.html b/rdmo/projects/templates/projects/projects.html
index e577025781..37d29a499d 100644
--- a/rdmo/projects/templates/projects/projects.html
+++ b/rdmo/projects/templates/projects/projects.html
@@ -22,7 +22,7 @@
{% endblock %}
{% block sidebar %}
- {% has_perm 'projects.can_add_project' request.user project as can_add_project %}
+ {% has_perm 'projects.add_project' request.user as can_add_project %}
{% test_rule 'projects.can_view_all_projects' request.user request as can_view_all_projects %}
{% if can_add_project or can_view_all_projects %}
@@ -68,6 +68,8 @@ {% trans 'Filter projects' %}
+ {% if can_add_project %}
+
{% trans 'Import existing project' %}
@@ -97,6 +99,8 @@ {% trans 'Import existing project' %}
{% endif %}
+ {% endif %}
+
{% if invites %}
{% trans 'Pending invitations' %}
diff --git a/rdmo/projects/views/project_create.py b/rdmo/projects/views/project_create.py
index 171bb19156..c909e9a719 100644
--- a/rdmo/projects/views/project_create.py
+++ b/rdmo/projects/views/project_create.py
@@ -21,7 +21,7 @@ class ProjectCreateView(ObjectPermissionMixin, LoginRequiredMixin,
RedirectViewMixin, CreateView):
model = Project
form_class = ProjectForm
- permission_required = 'projects.add_project_object'
+ permission_required = 'projects.add_project'
def get_form_kwargs(self):
catalogs = Catalog.objects.filter_current_site() \
@@ -70,7 +70,7 @@ def form_valid(self, form):
class ProjectCreateImportView(ObjectPermissionMixin, LoginRequiredMixin,
ProjectImportMixin, TemplateView):
success_url = reverse_lazy('projects')
- permission_required = 'projects.add_project_object'
+ permission_required = 'projects.add_project'
def get(self, request, *args, **kwargs):
self.object = None
From e59d286dbea44090b0c101dc4d754db019b06f42 Mon Sep 17 00:00:00 2001
From: Jochen Klar
Date: Thu, 19 Oct 2023 18:02:36 +0200
Subject: [PATCH 3/7] Add ACCOUNT_GROUPS and SOCIALACCOUNT_GROUPS to settings
---
rdmo/accounts/adapter.py | 19 +++++++++++++++++++
rdmo/core/settings.py | 21 ++++++++++-----------
2 files changed, 29 insertions(+), 11 deletions(-)
diff --git a/rdmo/accounts/adapter.py b/rdmo/accounts/adapter.py
index ccc7bf4d25..efa7902ce4 100644
--- a/rdmo/accounts/adapter.py
+++ b/rdmo/accounts/adapter.py
@@ -1,4 +1,5 @@
from django.conf import settings
+from django.contrib.auth.models import Group
from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
@@ -9,8 +10,26 @@ class AccountAdapter(DefaultAccountAdapter):
def is_open_for_signup(self, request):
return settings.ACCOUNT_SIGNUP
+ def save_user(self, request, user, form, commit=True):
+ user = super().save_user(request, user, form, commit)
+
+ if settings.ACCOUNT_GROUPS:
+ groups = Group.objects.filter(name__in=settings.ACCOUNT_GROUPS)
+ user.groups.set(groups)
+
+ return user
class SocialAccountAdapter(DefaultSocialAccountAdapter):
def is_open_for_signup(self, request, sociallogin):
return settings.SOCIALACCOUNT_SIGNUP
+
+ def save_user(self, request, sociallogin, form=None):
+ user = super().save_user(request, sociallogin, form)
+
+ if settings.SOCIALACCOUNT_GROUPS:
+ provider = str(sociallogin.account.provider)
+ groups = Group.objects.filter(name__in=settings.SOCIALACCOUNT_GROUPS.get(provider, []))
+ user.groups.set(groups)
+
+ return user
diff --git a/rdmo/core/settings.py b/rdmo/core/settings.py
index 78741dada6..5b529518cb 100644
--- a/rdmo/core/settings.py
+++ b/rdmo/core/settings.py
@@ -98,15 +98,9 @@
ACCOUNT = False
ACCOUNT_SIGNUP = False
+ACCOUNT_GROUPS = []
ACCOUNT_TERMS_OF_USE = False
-
-SOCIALACCOUNT = False
-
-SHIBBOLETH = False
-SHIBBOLETH_LOGIN_URL = '/Shibboleth.sso/Login'
-SHIBBOLETH_LOGOUT_URL = '/Shibboleth.sso/Logout'
-SHIBBOLETH_USERNAME_PATTERN = None
-
+ACCOUNT_ADAPTER = 'rdmo.accounts.adapter.AccountAdapter'
ACCOUNT_SIGNUP_FORM_CLASS = 'rdmo.accounts.forms.SignupForm'
ACCOUNT_USER_DISPLAY = 'rdmo.accounts.utils.get_full_name'
ACCOUNT_EMAIL_REQUIRED = True
@@ -120,11 +114,16 @@
ACCOUNT_PREVENT_ENUMERATION = False
ACCOUNT_ALLOW_USER_TOKEN = False
-ACCOUNT_ADAPTER = 'rdmo.accounts.adapter.AccountAdapter'
-
-SOCIALACCOUNT_ADAPTER = 'rdmo.accounts.adapter.SocialAccountAdapter'
+SOCIALACCOUNT = False
SOCIALACCOUNT_SIGNUP = False
+SOCIALACCOUNT_GROUPS = []
SOCIALACCOUNT_AUTO_SIGNUP = False
+SOCIALACCOUNT_ADAPTER = 'rdmo.accounts.adapter.SocialAccountAdapter'
+
+SHIBBOLETH = False
+SHIBBOLETH_LOGIN_URL = '/Shibboleth.sso/Login'
+SHIBBOLETH_LOGOUT_URL = '/Shibboleth.sso/Logout'
+SHIBBOLETH_USERNAME_PATTERN = None
LANGUAGE_CODE = 'en-us'
From deba05efec4b5ea4518b36b604d396543adc65cc Mon Sep 17 00:00:00 2001
From: Jochen Klar
Date: Fri, 24 Nov 2023 14:27:19 +0100
Subject: [PATCH 4/7] Flatten if/else blocks
---
rdmo/projects/permissions.py | 20 ++++++++++----------
rdmo/projects/rules.py | 12 ++++++------
2 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/rdmo/projects/permissions.py b/rdmo/projects/permissions.py
index 749a94449a..c7c87a5f91 100644
--- a/rdmo/projects/permissions.py
+++ b/rdmo/projects/permissions.py
@@ -12,17 +12,17 @@ def has_permission(self, request, view):
# for retrieve, update, partial_update, the permission will be checked on the
# object level (in the next step)
return True
+
+ if view.action == 'list':
+ # list is allowed for every user since the filtering is done in the queryset
+ return True
+
+ if 'create' in view.action_map.values():
+ # for create, check the permission (from rules.py),
+ # but only if it is not a ReadOnlyValueSet (i.e. only for ProjectViewSet)
+ return super().has_permission(request, view)
else:
- if view.action == 'list':
- # list is allowed for every user since the filtering is done in the queryset
- return True
- else:
- if 'create' in view.action_map.values():
- # for create, check the permission (from rules.py),
- # but only if it is not a ReadOnlyValueSet (i.e. only for ProjectViewSet)
- return super().has_permission(request, view)
- else:
- return True
+ return True
@log_result
def has_object_permission(self, request, view, obj):
diff --git a/rdmo/projects/rules.py b/rdmo/projects/rules.py
index 15baa9230b..fbab7c065f 100644
--- a/rdmo/projects/rules.py
+++ b/rdmo/projects/rules.py
@@ -7,14 +7,14 @@
@rules.predicate
def can_add_project(user):
- if settings.PROJECT_CREATE_RESTRICTED:
- if settings.PROJECT_CREATE_GROUPS:
- return user.groups.filter(name__in=settings.PROJECT_CREATE_GROUPS).exists()
- else:
- return False
- else:
+ if not settings.PROJECT_CREATE_RESTRICTED:
return True
+ if settings.PROJECT_CREATE_GROUPS:
+ return user.groups.filter(name__in=settings.PROJECT_CREATE_GROUPS).exists()
+ else:
+ return False
+
@rules.predicate
def is_project_member(user, project):
From ceeccd80d5a6421de73a67c91b8d38afe65977c7 Mon Sep 17 00:00:00 2001
From: Jochen Klar
Date: Sun, 26 Nov 2023 09:37:07 +0100
Subject: [PATCH 5/7] Fix testing
---
pyproject.toml | 2 +-
testing/export/project.csv | 4 ++--
testing/export/project.html | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index e5c838a042..bd02299fac 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -183,7 +183,7 @@ rest_framework = ["rest_framework"]
[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "config.settings"
testpaths = ["rdmo"]
-python_files = "test_*[!.txt].py"
+python_files = "test_*.py"
pythonpath = [".", "testing"]
addopts = '-p no:randomly -m "not e2e"'
markers = [
diff --git a/testing/export/project.csv b/testing/export/project.csv
index 1092d726f6..6d1bf98884 100644
--- a/testing/export/project.csv
+++ b/testing/export/project.csv
@@ -1,7 +1,7 @@
Text?,,Lorem ipsum dolor sit amet
Textarea?,,"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est. Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est. Lorem ipsum dolor sit amet."
Yes or no?,,Yes
-Radio buttons?,,Other: Lorem ipsum
+Radio buttons?,,Text: Lorem ipsum
Select drop-down?,,One
Range slider?,,37
File?,,rdmo-logo.svg
@@ -18,7 +18,7 @@ Checkbox?,,One; Three
Text?,,Lorem ipsum dolor sit amet
Textarea?,,"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est. Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est. Lorem ipsum dolor sit amet."
Yes or no?,,Yes
-Radio buttons?,,Other: Lorem ipsum
+Radio buttons?,,Text: Lorem ipsum
Select drop-down?,,One
Range slider?,,37
Date picker?,,2018-01-01
diff --git a/testing/export/project.html b/testing/export/project.html
index aba80183d7..b961e65aa4 100644
--- a/testing/export/project.html
+++ b/testing/export/project.html
@@ -23,7 +23,7 @@ Yes or no
Radio buttons
Radio buttons?
-Other: Lorem ipsum
+Text: Lorem ipsum
Select drop-down
Select drop-down?
@@ -171,7 +171,7 @@ Individual sets I
Radio buttons?
-Other: Lorem ipsum
+Text: Lorem ipsum
Select drop-down?
From 389680b2397388dcc1c372cc8e3d1374562249d4 Mon Sep 17 00:00:00 2001
From: Jochen Klar
Date: Sun, 26 Nov 2023 10:10:41 +0100
Subject: [PATCH 6/7] Fix project progress permissions
---
rdmo/projects/permissions.py | 19 +++++++++++++------
rdmo/projects/viewsets.py | 5 +++--
2 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/rdmo/projects/permissions.py b/rdmo/projects/permissions.py
index c7c87a5f91..9fa7be5ffb 100644
--- a/rdmo/projects/permissions.py
+++ b/rdmo/projects/permissions.py
@@ -1,4 +1,4 @@
-from rdmo.core.permissions import HasObjectPermission, log_result
+from rdmo.core.permissions import HasModelPermission, HasObjectPermission, log_result
class HasProjectsPermission(HasObjectPermission):
@@ -74,12 +74,19 @@ def get_required_object_permissions(self, method, model_cls):
return ('projects.view_page_object', )
-class HasProjectProgressPermission(HasProjectPermission):
+class HasProjectProgressModelPermission(HasModelPermission):
+
+ def get_required_permissions(self, method, model_cls):
+ if method == 'POST':
+ return ('projects.change_project', )
+ else:
+ return ('projects.view_project', )
+
+
+class HasProjectProgressObjectPermission(HasProjectPermission):
def get_required_object_permissions(self, method, model_cls):
- if method == 'GET':
- return ('projects.view_project_object', )
- elif method == 'POST':
+ if method == 'POST':
return ('projects.change_project_progress_object', )
else:
- raise RuntimeError('Unsupported method for HasProjectProgressPermission')
+ return ('projects.view_project_object', )
diff --git a/rdmo/projects/viewsets.py b/rdmo/projects/viewsets.py
index 0d3ab9e29e..378a97c68b 100644
--- a/rdmo/projects/viewsets.py
+++ b/rdmo/projects/viewsets.py
@@ -28,7 +28,8 @@
from .permissions import (
HasProjectPagePermission,
HasProjectPermission,
- HasProjectProgressPermission,
+ HasProjectProgressModelPermission,
+ HasProjectProgressObjectPermission,
HasProjectsPermission,
)
from .progress import compute_navigation, compute_progress
@@ -174,7 +175,7 @@ def options(self, request, pk=None):
raise NotFound()
@action(detail=True, methods=['get', 'post'],
- permission_classes=(HasModelPermission | HasProjectProgressPermission, ))
+ permission_classes=(HasProjectProgressModelPermission | HasProjectProgressObjectPermission, ))
def progress(self, request, pk=None):
project = self.get_object()
From 1fb0af4c01be22404aa49f9978d63b11988ace8c Mon Sep 17 00:00:00 2001
From: Jochen Klar
Date: Sun, 26 Nov 2023 11:48:00 +0100
Subject: [PATCH 7/7] Fix tests
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 38e80f4b73..55283b2312 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -64,7 +64,7 @@ jobs:
- name: Install Dependencies
run: |
sudo apt update
- sudo apt install --yes pandoc texlive-xetex
+ sudo apt install --yes pandoc texlive-xetex librsvg2-bin
python -m pip install --upgrade pip setuptools wheel
pandoc --version
- name: Install rdmo[mysql] and start mysql