{
@@ -55,6 +57,7 @@ const QuestionSet = ({ templates, questionset, sets, values, disabled, isManager
createSet={createSet}
updateSet={updateSet}
deleteSet={deleteSet}
+ copySet={copySet}
createValue={createValue}
updateValue={updateValue}
deleteValue={deleteValue}
@@ -115,6 +118,7 @@ QuestionSet.propTypes = {
createSet: PropTypes.func.isRequired,
updateSet: PropTypes.func.isRequired,
deleteSet: PropTypes.func.isRequired,
+ copySet: PropTypes.func.isRequired,
createValue: PropTypes.func.isRequired,
updateValue: PropTypes.func.isRequired,
deleteValue: PropTypes.func.isRequired,
diff --git a/rdmo/projects/assets/js/interview/components/main/questionset/QuestionSetCopyModal.js b/rdmo/projects/assets/js/interview/components/main/questionset/QuestionSetCopyModal.js
new file mode 100644
index 0000000000..151059eb60
--- /dev/null
+++ b/rdmo/projects/assets/js/interview/components/main/questionset/QuestionSetCopyModal.js
@@ -0,0 +1,21 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+import Modal from 'rdmo/core/assets/js/components/Modal'
+
+const QuestionSetCopyModal = ({ title, show, onClose, onSubmit }) => {
+ return (
+
+
+ )
+}
+
+QuestionSetCopyModal.propTypes = {
+ title: PropTypes.string.isRequired,
+ show: PropTypes.bool.isRequired,
+ onClose: PropTypes.func.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+}
+
+export default QuestionSetCopyModal
diff --git a/rdmo/projects/assets/js/interview/components/main/questionset/QuestionSetCopySet.js b/rdmo/projects/assets/js/interview/components/main/questionset/QuestionSetCopySet.js
new file mode 100644
index 0000000000..cc2e5492d5
--- /dev/null
+++ b/rdmo/projects/assets/js/interview/components/main/questionset/QuestionSetCopySet.js
@@ -0,0 +1,43 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { capitalize, last } from 'lodash'
+
+import useModal from 'rdmo/core/assets/js/hooks/useModal'
+
+import QuestionSetCopyModal from './QuestionSetCopyModal'
+
+const QuestionCopySet = ({ questionset, sets, currentSet, copySet }) => {
+ const modal = useModal()
+
+ const handleCopySet = () => {
+ copySet(currentSet, null, {
+ set_prefix: currentSet.set_prefix,
+ set_index: last(sets) ? last(sets).set_index + 1 : 0,
+ })
+ modal.close()
+ }
+
+ return questionset.is_collection && (
+ <>
+
+
+
+ >
+ )
+}
+
+QuestionCopySet.propTypes = {
+ questionset: PropTypes.object.isRequired,
+ sets: PropTypes.array.isRequired,
+ currentSet: PropTypes.object.isRequired,
+ copySet: PropTypes.func.isRequired
+}
+
+export default QuestionCopySet
diff --git a/rdmo/projects/assets/js/interview/components/main/questionset/QuestionSetRemoveSet.js b/rdmo/projects/assets/js/interview/components/main/questionset/QuestionSetRemoveSet.js
index a7364a8909..06a0dcae61 100644
--- a/rdmo/projects/assets/js/interview/components/main/questionset/QuestionSetRemoveSet.js
+++ b/rdmo/projects/assets/js/interview/components/main/questionset/QuestionSetRemoveSet.js
@@ -6,12 +6,12 @@ import useModal from 'rdmo/core/assets/js/hooks/useModal'
import QuestionSetDeleteModal from './QuestionSetDeleteModal'
-const QuestionAddSet = ({ questionset, set, deleteSet }) => {
+const QuestionRemoveSet = ({ questionset, currentSet, deleteSet }) => {
const {show: showDeleteModal, open: openDeleteModal, close: closeDeleteModal} = useModal()
const handleDeleteSet = () => {
- deleteSet(set)
+ deleteSet(currentSet)
closeDeleteModal()
}
@@ -31,10 +31,10 @@ const QuestionAddSet = ({ questionset, set, deleteSet }) => {
)
}
-QuestionAddSet.propTypes = {
+QuestionRemoveSet.propTypes = {
questionset: PropTypes.object.isRequired,
- set: PropTypes.object.isRequired,
+ currentSet: PropTypes.object.isRequired,
deleteSet: PropTypes.func.isRequired
}
-export default QuestionAddSet
+export default QuestionRemoveSet
diff --git a/rdmo/projects/assets/js/interview/components/main/widget/SelectInput.js b/rdmo/projects/assets/js/interview/components/main/widget/SelectInput.js
index a0d694957e..b53f1f6baf 100644
--- a/rdmo/projects/assets/js/interview/components/main/widget/SelectInput.js
+++ b/rdmo/projects/assets/js/interview/components/main/widget/SelectInput.js
@@ -22,14 +22,10 @@ import OptionText from './common/OptionText'
const SelectInput = ({ question, value, options, disabled, creatable, updateValue, buttons }) => {
const [inputValue, setInputValue] = useState('')
- // const [isOpen, setIsOpen] = useState(false)
const handleChange = (option) => {
if (isNil(option)) {
- // close the select input when the value is reset
- // setIsOpen(false)
setInputValue('')
-
updateValue(value, {})
} else if (option.__isNew__ === true) {
updateValue(value, {
@@ -85,6 +81,7 @@ const SelectInput = ({ question, value, options, disabled, creatable, updateValu
const isAsync = question.optionsets.some((optionset) => optionset.has_search)
const selectProps = {
+ key: value.id,
classNamePrefix: 'react-select',
className: classnames,
backspaceRemovesValue: false,
diff --git a/rdmo/projects/assets/js/interview/containers/Main.js b/rdmo/projects/assets/js/interview/containers/Main.js
index a8f6189fe2..be0b98f316 100644
--- a/rdmo/projects/assets/js/interview/containers/Main.js
+++ b/rdmo/projects/assets/js/interview/containers/Main.js
@@ -54,6 +54,7 @@ const Main = ({ config, settings, templates, user, project, interview, configAct
updateSet={interviewActions.updateSet}
deleteSet={interviewActions.deleteSet}
copyValue={interviewActions.copyValue}
+ copySet={interviewActions.copySet}
/>
)
}
diff --git a/rdmo/projects/assets/js/interview/reducers/interviewReducer.js b/rdmo/projects/assets/js/interview/reducers/interviewReducer.js
index 39ac156bc2..d62bcd60cf 100644
--- a/rdmo/projects/assets/js/interview/reducers/interviewReducer.js
+++ b/rdmo/projects/assets/js/interview/reducers/interviewReducer.js
@@ -25,7 +25,10 @@ import {
CREATE_SET,
DELETE_SET_INIT,
DELETE_SET_SUCCESS,
- DELETE_SET_ERROR
+ DELETE_SET_ERROR,
+ COPY_SET_INIT,
+ COPY_SET_SUCCESS,
+ COPY_SET_ERROR
} from '../actions/actionTypes'
const initialState = {
@@ -76,6 +79,8 @@ export default function interviewReducer(state = initialState, action) {
values: state.values.filter((value) => !action.values.includes(value)),
sets: state.sets.filter((set) => !action.sets.includes(set))
}
+ case COPY_SET_SUCCESS:
+ return { ...state, values: action.values, sets: action.sets }
case FETCH_PAGE_INIT:
case FETCH_NAVIGATION_INIT:
case FETCH_OPTIONS_INIT:
@@ -97,6 +102,7 @@ export default function interviewReducer(state = initialState, action) {
))
}
case DELETE_SET_INIT:
+ case COPY_SET_INIT:
return { ...state, errors: [] }
case FETCH_PAGE_ERROR:
case FETCH_NAVIGATION_ERROR:
@@ -117,6 +123,8 @@ export default function interviewReducer(state = initialState, action) {
case DELETE_VALUE_ERROR:
case DELETE_SET_ERROR:
return { ...state, errors: [...state.errors, { actionType: action.type, ...action.error }] }
+ case COPY_SET_ERROR:
+ return { ...state, errors: [...state.errors, { actionType: action.type, ...action.error }] }
default:
return state
}
diff --git a/rdmo/projects/assets/js/interview/utils/set.js b/rdmo/projects/assets/js/interview/utils/set.js
index 282fa90b21..9a576875f9 100644
--- a/rdmo/projects/assets/js/interview/utils/set.js
+++ b/rdmo/projects/assets/js/interview/utils/set.js
@@ -1,4 +1,4 @@
-import { isEmpty, isNil, toNumber, toString, last } from 'lodash'
+import { isEmpty, isNil, toNumber, toString, last, sortBy } from 'lodash'
import SetFactory from '../factories/SetFactory'
@@ -27,7 +27,7 @@ const getDescendants = (items, set) => {
}
const gatherSets = (values) => {
- return values.reduce((sets, value) => {
+ const sets = values.reduce((sets, value) => {
if (sets.find((set) => (
(set.set_prefix === value.set_prefix) &&
(set.set_index === value.set_index)
@@ -40,6 +40,8 @@ const gatherSets = (values) => {
})]
}
}, [])
+
+ return sortBy(sets, ['set_prefix', 'set_index'])
}
const initSets = (sets, element, setPrefix) => {
diff --git a/rdmo/projects/assets/js/projects/components/main/Projects.js b/rdmo/projects/assets/js/projects/components/main/Projects.js
index 6f15f8e7b8..fb9b672e5a 100644
--- a/rdmo/projects/assets/js/projects/components/main/Projects.js
+++ b/rdmo/projects/assets/js/projects/components/main/Projects.js
@@ -116,20 +116,36 @@ const Projects = ({ config, configActions, currentUserObject, projectsActions, p
if (myProjects) {
visibleColumns.splice(2, 0, 'role')
- columnWidths = ['35%', '20%', '20%', '20%', '5%']
+ columnWidths = ['40%', '18%', '18%', '18%', '6%']
} else {
visibleColumns.splice(2, 0, 'created')
visibleColumns.splice(2, 0, 'owner')
- columnWidths = ['35%', '10%', '20%', '20%', '20%', '5%']
+ columnWidths = ['30%', '10%', '18%', '18%', '18%', '6%']
}
const cellFormatters = {
title: (content, row) => renderTitle(content, row),
role: (_content, row) => {
const { rolesString } = getUserRoles(row, currentUserId)
- return rolesString
+ return <>
+ {
+ rolesString &&
{rolesString}
+ }
+ {
+ row.visibility &&
{row.visibility}
+ }
+ >
},
- owner: (_content, row) => row.owners.map(owner => `${owner.first_name} ${owner.last_name}`).join('; '),
+ owner: (_content, row) => (
+ <>
+
+ {row.owners.map(owner => `${owner.first_name} ${owner.last_name}`).join('; ')}
+
+ {
+ row.visibility &&
{row.visibility}
+ }
+ >
+ ),
progress: (_content, row) => getProgressString(row),
created: content => useFormattedDateTime(content, language),
last_changed: content => useFormattedDateTime(content, language),
diff --git a/rdmo/projects/assets/scss/interview.scss b/rdmo/projects/assets/scss/interview.scss
index 20eb128e67..4fa5b2a1c4 100644
--- a/rdmo/projects/assets/scss/interview.scss
+++ b/rdmo/projects/assets/scss/interview.scss
@@ -100,22 +100,20 @@
.interview-block-options {
position: absolute;
- top: 0;
- right: 0;
+ top: 6px;
+ right: 8px;
z-index: 5;
+ display: flex;
+ gap: 4px;
+
+ .btn-copy-set,
.btn-remove-set {
opacity: 0.8;
line-height: 20px;
font-size: 14px;
- position: absolute;
- z-index: 5;
- top: 0;
- right: 0;
-
- padding-left: 8px;
- padding-right: 8px;
+ padding: 0;
&:hover {
opacity: 1;
@@ -372,6 +370,10 @@
display: inline;
}
}
+
+ .react-select {
+ max-width: 100%;
+ }
}
.badge-optional {
diff --git a/rdmo/projects/managers.py b/rdmo/projects/managers.py
index a6d49e4ff6..20764ae3b6 100644
--- a/rdmo/projects/managers.py
+++ b/rdmo/projects/managers.py
@@ -155,6 +155,29 @@ def exclude_empty(self):
def distinct_list(self):
return self.order_by('attribute').values_list('attribute', 'set_prefix', 'set_index').distinct()
+ def filter_set(self, set_value):
+ # get the catalog and prefetch most elements of the catalog
+ catalog = set_value.project.catalog
+ catalog.prefetch_elements()
+
+ # Get all attributes from matching elements and their descendants
+ attributes = {
+ descendant.attribute
+ for element in (catalog.pages + catalog.questions)
+ if element.attribute == set_value.attribute
+ for descendant in element.descendants
+ }
+
+ # construct the set_prefix for descendants for this set
+ descendants_set_prefix = \
+ f'{set_value.set_prefix}|{set_value.set_index}' if set_value.set_prefix else str(set_value.set_index)
+
+ # collect all values for this set and all descendants
+ return self.filter(attribute__in=attributes).filter(
+ Q(set_prefix=set_value.set_prefix, set_index=set_value.set_index) |
+ Q(set_prefix__startswith=descendants_set_prefix)
+ )
+
class ProjectManager(CurrentSiteManagerMixin, TreeManager):
diff --git a/rdmo/projects/serializers/v1/__init__.py b/rdmo/projects/serializers/v1/__init__.py
index 8e61748193..26ea38402d 100644
--- a/rdmo/projects/serializers/v1/__init__.py
+++ b/rdmo/projects/serializers/v1/__init__.py
@@ -5,6 +5,7 @@
from rest_framework import serializers
+from rdmo.domain.models import Attribute
from rdmo.questions.models import Catalog
from rdmo.services.validators import ProviderValidator
@@ -64,6 +65,8 @@ def get_queryset(self):
last_changed = serializers.DateTimeField(read_only=True)
+ visibility = serializers.CharField(source='visibility.get_help_display', read_only=True)
+
class Meta:
model = Project
fields = (
@@ -84,7 +87,8 @@ class Meta:
'site',
'views',
'progress_total',
- 'progress_count'
+ 'progress_count',
+ 'visibility'
)
read_only_fields = (
'snapshots',
@@ -404,6 +408,8 @@ class Meta:
class ValueSerializer(serializers.ModelSerializer):
+ attribute = serializers.PrimaryKeyRelatedField(queryset=Attribute.objects.all(), required=True)
+
class Meta:
model = Value
fields = (
diff --git a/rdmo/projects/templates/projects/project_form_visibility.html b/rdmo/projects/templates/projects/project_form_visibility.html
index 2356b821ee..4c5a736c5c 100644
--- a/rdmo/projects/templates/projects/project_form_visibility.html
+++ b/rdmo/projects/templates/projects/project_form_visibility.html
@@ -19,12 +19,12 @@
{% endblocktrans %}
- {% if object.visibility and 'sites' in form.fields or 'groups' in form.fields %}
+ {% if not object.visibility %}
+ {% bootstrap_form submit=_('Make visible') %}
+ {% elif 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') %}
+ {% bootstrap_form delete=_('Remove visibility') %}
{% endif %}
{% endblock %}
diff --git a/rdmo/projects/tests/test_utils.py b/rdmo/projects/tests/test_utils.py
index db2bfe2c51..a3a182c33f 100644
--- a/rdmo/projects/tests/test_utils.py
+++ b/rdmo/projects/tests/test_utils.py
@@ -7,8 +7,8 @@
from rdmo.core.tests.utils import compute_checksum
from ..filters import ProjectFilter
-from ..models import Project
-from ..utils import copy_project, set_context_querystring_with_filter_and_page
+from ..models import Project, Value
+from ..utils import compute_set_prefix_from_set_value, copy_project, set_context_querystring_with_filter_and_page
GET_queries = [
'page=2&title=project',
@@ -17,6 +17,18 @@
''
]
+SET_VALUES = [
+ ({'set_prefix': '' , 'set_index': 1}, {'set_prefix': '0'}, '1'),
+ ({'set_prefix': '' , 'set_index': 1}, {'set_prefix': '0|0'}, '1|0'),
+ ({'set_prefix': '' , 'set_index': 1}, {'set_prefix': '0|0|0'}, '1|0|0'),
+ ({'set_prefix': '' , 'set_index': 2}, {'set_prefix': '0'}, '2'),
+ ({'set_prefix': '' , 'set_index': 2}, {'set_prefix': '0|0'}, '2|0'),
+ ({'set_prefix': '' , 'set_index': 2}, {'set_prefix': '0|0|0'}, '2|0|0'),
+ ({'set_prefix': '0' , 'set_index': 1}, {'set_prefix': '0|0'}, '0|1'),
+ ({'set_prefix': '0' , 'set_index': 1}, {'set_prefix': '0|0|0'}, '0|1|0'),
+ ({'set_prefix': '0|0', 'set_index': 1}, {'set_prefix': '0|0|0'}, '0|0|1'),
+]
+
@pytest.mark.parametrize('GET_query', GET_queries)
def test_set_context_querystring_with_filter_and_page(GET_query):
querydict = QueryDict(GET_query)
@@ -128,3 +140,8 @@ def test_copy_project(db, files):
compute_checksum(value.file.open('rb').read())
else:
assert not value.file
+
+
+@pytest.mark.parametrize('set_value, value, result', SET_VALUES)
+def test_compute_set_prefix_from_set_value(set_value, value, result):
+ assert compute_set_prefix_from_set_value(Value(**set_value), Value(**value)) == result
diff --git a/rdmo/projects/tests/test_viewset_project_value.py b/rdmo/projects/tests/test_viewset_project_value.py
index aaa1fb2693..3359527fb9 100644
--- a/rdmo/projects/tests/test_viewset_project_value.py
+++ b/rdmo/projects/tests/test_viewset_project_value.py
@@ -1,3 +1,4 @@
+import json
from pathlib import Path
import pytest
@@ -30,7 +31,7 @@
'site': [1, 2, 3, 4, 5, 12]
}
-add_value_permission_map = change_value_permission_map = delete_value_permission_map = {
+add_value_permission_map = change_value_permission_map = delete_value_permission_map = copy_value_permission_map = {
'owner': [1, 2, 3, 4, 5, 12],
'manager': [1, 3, 5],
'author': [1, 3, 5],
@@ -76,6 +77,7 @@
('phone', '+49 (0) 1337 12345678')
)
+
@pytest.mark.parametrize('username,password', users)
@pytest.mark.parametrize('project_id', projects)
def test_list(db, client, username, password, project_id):
@@ -248,10 +250,50 @@ def test_delete(db, client, username, password, value_id):
assert Value.objects.filter(pk=value_id).exists()
+@pytest.mark.parametrize('username,password', users)
+@pytest.mark.parametrize('value_id, set_values_count', set_values)
+def test_copy_set(db, client, username, password, value_id, set_values_count):
+ client.login(username=username, password=password)
+ set_value = Value.objects.get(id=value_id)
+ values_count = Value.objects.count()
+
+ url = reverse(urlnames['set'], args=[set_value.project_id, value_id])
+ data = {
+ 'attribute': set_value.attribute.id,
+ 'set_prefix': set_value.set_prefix,
+ 'set_index': 2,
+ 'text': 'new'
+ }
+ response = client.post(url, data=json.dumps(data), content_type="application/json")
+
+ if set_value.project_id in copy_value_permission_map.get(username, []):
+ assert response.status_code == 201
+ assert len(response.json()) == set_values_count + 1
+ assert Value.objects.get(
+ project=set_value.project_id,
+ snapshot=None,
+ **data
+ )
+ assert Value.objects.count() == values_count + set_values_count + 1 # one is for set/id
+ for value_data in response.json():
+ if value_data['set_prefix'] == data['set_prefix']:
+ assert value_data['set_index'] == data['set_index']
+ else:
+ set_prefix_split = value_data['set_prefix'].split('|')
+ assert set_prefix_split[0] == str(data['set_index'])
+
+ elif set_value.project_id in view_value_permission_map.get(username, []):
+ assert response.status_code == 403
+ assert Value.objects.count() == values_count
+ else:
+ assert response.status_code == 404
+ assert Value.objects.count() == values_count
+
+
@pytest.mark.parametrize('username,password', users)
@pytest.mark.parametrize('project_id', projects)
-@pytest.mark.parametrize('value_id,set_values_count', set_values)
-def test_set(db, client, username, password, project_id, value_id, set_values_count):
+@pytest.mark.parametrize('value_id, set_values_count', set_values)
+def test_delete_set(db, client, username, password, project_id, value_id, set_values_count):
client.login(username=username, password=password)
value_exists = Value.objects.filter(project_id=project_id, snapshot=None, id=value_id).exists()
values_count = Value.objects.count()
diff --git a/rdmo/projects/utils.py b/rdmo/projects/utils.py
index ab619b54b4..06153b855e 100644
--- a/rdmo/projects/utils.py
+++ b/rdmo/projects/utils.py
@@ -261,3 +261,11 @@ def get_upload_accept():
else:
return None
return ','.join(accept)
+
+
+def compute_set_prefix_from_set_value(set_value, value):
+ set_prefix_length = len(set_value.set_prefix.split('|')) if set_value.set_prefix else 0
+ return '|'.join([
+ str(set_value.set_index) if (index == set_prefix_length) else value
+ for index, value in enumerate(value.set_prefix.split('|'))
+ ])
diff --git a/rdmo/projects/viewsets.py b/rdmo/projects/viewsets.py
index a0221ee894..9eccecfa28 100644
--- a/rdmo/projects/viewsets.py
+++ b/rdmo/projects/viewsets.py
@@ -1,14 +1,14 @@
from django.conf import settings
from django.contrib.sites.shortcuts import get_current_site
from django.core.exceptions import ObjectDoesNotExist
-from django.db.models import OuterRef, Prefetch, Q, Subquery
+from django.db.models import OuterRef, Prefetch, Subquery
from django.db.models.functions import Coalesce, Greatest
from django.http import Http404, HttpResponseRedirect
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers, status
from rest_framework.decorators import action
-from rest_framework.exceptions import NotFound
+from rest_framework.exceptions import MethodNotAllowed, NotFound
from rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.pagination import PageNumberPagination
from rest_framework.permissions import IsAuthenticated
@@ -75,7 +75,13 @@
)
from .serializers.v1.overview import CatalogSerializer, ProjectOverviewSerializer
from .serializers.v1.page import PageSerializer
-from .utils import check_conditions, copy_project, get_upload_accept, send_invite_email
+from .utils import (
+ check_conditions,
+ compute_set_prefix_from_set_value,
+ copy_project,
+ get_upload_accept,
+ send_invite_email,
+)
class ProjectPagination(PageNumberPagination):
@@ -115,7 +121,7 @@ def get_queryset(self):
'snapshots',
'views',
Prefetch('memberships', queryset=Membership.objects.select_related('user'), to_attr='memberships_list')
- ).select_related('catalog')
+ ).select_related('catalog', 'visibility')
# prepare subquery for last_changed
last_changed_subquery = Subquery(
@@ -497,34 +503,60 @@ def get_queryset(self):
# this is needed for the swagger ui
return Value.objects.none()
- @action(detail=True, methods=['DELETE'],
+ @action(detail=True, methods=['POST', 'DELETE'], url_path='set',
permission_classes=(HasModelPermission | HasProjectPermission, ))
def set(self, request, parent_lookup_project, pk=None):
+ if request.method == 'POST':
+ return self.copy_set(request, parent_lookup_project, pk)
+ elif request.method == 'DELETE':
+ return self.delete_set(request, parent_lookup_project, pk)
+ else:
+ raise MethodNotAllowed
+
+ def copy_set(self, request, parent_lookup_project, pk=None):
+ # copy all values for questions in questionset collections with the attribute
+ # for this value and the same set_prefix and set_index
+ currentValue = self.get_object()
+
+ # collect all values for this set and all descendants
+ currentValues = self.get_queryset().filter_set(currentValue)
+
+ # de-serialize the posted new set value and save it, use the ValueSerializer
+ # instead of ProjectValueSerializer, since the latter does not include project
+ set_value_serializer = ValueSerializer(data={
+ 'project': parent_lookup_project,
+ **request.data
+ })
+ set_value_serializer.is_valid(raise_exception=True)
+ set_value = set_value_serializer.save()
+ set_value_data = set_value_serializer.data
+
+ # create new values for the new set
+ values = []
+ for value in currentValues:
+ value.id = None
+ if value.set_prefix == set_value.set_prefix:
+ value.set_index = set_value.set_index
+ else:
+ value.set_prefix = compute_set_prefix_from_set_value(set_value, value)
+ values.append(value)
+
+ # bulk create the new values
+ values = Value.objects.bulk_create(values)
+ values_data = [ValueSerializer(instance=value).data for value in values]
+
+ # return all new values
+ headers = self.get_success_headers(set_value_serializer.data)
+ return Response([set_value_data, *values_data], status=status.HTTP_201_CREATED, headers=headers)
+
+ def delete_set(self, request, parent_lookup_project, pk=None):
# delete all values for questions in questionset collections with the attribute
# for this value and the same set_prefix and set_index
- value = self.get_object()
- value.delete()
-
- # prefetch most elements of the catalog
- self.project.catalog.prefetch_elements()
-
- # collect the attributes of all questions of all pages or questionsets
- # of this catalog, which have the attribute of this value
- attributes = set()
- elements = self.project.catalog.pages + self.project.catalog.questions
- for element in elements:
- if element.attribute == value.attribute:
- attributes.update([descendant.attribute for descendant in element.descendants])
-
- # construct the set_prefix for descendants for this set
- descendants_set_prefix = f'{value.set_prefix}|{value.set_index}' if value.set_prefix else str(value.set_index)
-
- # delete all values for this set and all descendants
- values = self.get_queryset().filter(attribute__in=attributes) \
- .filter(
- Q(set_prefix=value.set_prefix, set_index=value.set_index) |
- Q(set_prefix__startswith=descendants_set_prefix)
- )
+ set_value = self.get_object()
+ set_value.delete()
+
+ # collect all values for this set and all descendants and delete them
+ values = self.get_queryset().filter_set(set_value)
values.delete()
return Response(status=204)