diff --git a/rdmo/projects/assets/js/interview/actions/interviewActions.js b/rdmo/projects/assets/js/interview/actions/interviewActions.js index 3053b803b7..2bbfe4ed22 100644 --- a/rdmo/projects/assets/js/interview/actions/interviewActions.js +++ b/rdmo/projects/assets/js/interview/actions/interviewActions.js @@ -563,7 +563,7 @@ export function deleteSetError(errors) { return {type: DELETE_SET_ERROR, errors} } -export function copySet(currentSet, attrs) { +export function copySet(currentSet, currentSetValue, attrs) { const pendingId = `copySet/${currentSet.set_prefix}/${currentSet.set_index}` return (dispatch, getState) => { @@ -592,18 +592,19 @@ export function copySet(currentSet, attrs) { return dispatch({type: COPY_SET_SUCCESS, values, sets}) } + let promise if (isNil(value)) { // gather all values for the currentSet and it's descendants const currentValues = getDescendants(getState().interview.values, currentSet) // store each value in currentSet with the new set_index - return Promise.all( + promise = Promise.all( currentValues.filter((currentValue) => !isEmptyValue(currentValue)).map((currentValue) => { const value = {...currentValue} const setPrefixLength = set.set_prefix.split('|').length if (value.set_prefix == set.set_prefix) { - value.set_index == set.set_index + value.set_index = set.set_index } else { value.set_prefix = value.set_prefix.split('|').reduce((acc, cur, idx) => { return [...acc, (idx == setPrefixLength - 1) ? set.set_index : cur] @@ -613,16 +614,18 @@ export function copySet(currentSet, attrs) { delete value.id return ValueApi.storeValue(projectId, value) }) - ).then((values) => { - dispatch(removeFromPending(pendingId)) - dispatch(copySetCallback(values)) - }).catch((errors) => { - dispatch(removeFromPending(pendingId)) - dispatch(copySetError(errors)) - }) + ) } else { - console.log(value) + promise = ValueApi.copySet(projectId, currentSetValue, value) } + + return promise.then((values) => { + dispatch(removeFromPending(pendingId)) + dispatch(copySetCallback(values)) + }).catch((errors) => { + dispatch(removeFromPending(pendingId)) + dispatch(copySetError(errors)) + }) } } diff --git a/rdmo/projects/assets/js/interview/api/ValueApi.js b/rdmo/projects/assets/js/interview/api/ValueApi.js index 208bbca611..b925e5acac 100644 --- a/rdmo/projects/assets/js/interview/api/ValueApi.js +++ b/rdmo/projects/assets/js/interview/api/ValueApi.js @@ -29,8 +29,12 @@ class ValueApi extends BaseApi { } } - static deleteSet(projectId, value) { - return this.delete(`/api/v1/projects/projects/${projectId}/values/${value.id}/set/`) + static copySet(projectId, currentSetValue, setValue) { + return this.post(`/api/v1/projects/projects/${projectId}/values/${currentSetValue.id}/set/`, setValue) + } + + static deleteSet(projectId, setValue) { + return this.delete(`/api/v1/projects/projects/${projectId}/values/${setValue.id}/set/`) } } diff --git a/rdmo/projects/assets/js/interview/components/main/page/PageHead.js b/rdmo/projects/assets/js/interview/components/main/page/PageHead.js index 3087be1fc1..6f59cc2492 100644 --- a/rdmo/projects/assets/js/interview/components/main/page/PageHead.js +++ b/rdmo/projects/assets/js/interview/components/main/page/PageHead.js @@ -56,7 +56,7 @@ const PageHead = ({ templates, page, sets, values, currentSet, } const handleCopySet = (text) => { - copySet(currentSet, { + copySet(currentSet, currentSetValue, { attribute: page.attribute, set_index: last(sets) ? last(sets).set_index + 1 : 0, set_collection: page.is_collection, diff --git a/rdmo/projects/managers.py b/rdmo/projects/managers.py index 988303ed92..62b6600ea3 100644 --- a/rdmo/projects/managers.py +++ b/rdmo/projects/managers.py @@ -150,6 +150,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() + + # collect the attributes of all questions of all pages or questionsets + # of this catalog, which have the attribute of this value + attributes = set() + elements = catalog.pages + catalog.questions + for element in elements: + if element.attribute == set_value.attribute: + attributes.update([descendant.attribute for descendant in element.descendants]) + + # construct the set_prefix for decendants for this set + decendants_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 decendants + return self.filter(attribute__in=attributes).filter( + Q(set_prefix=set_value.set_prefix, set_index=set_value.set_index) | + Q(set_prefix__startswith=decendants_set_prefix) + ) + class ProjectManager(CurrentSiteManagerMixin, TreeManager): diff --git a/rdmo/projects/viewsets.py b/rdmo/projects/viewsets.py index 046306ff34..d9a8ed5a86 100644 --- a/rdmo/projects/viewsets.py +++ b/rdmo/projects/viewsets.py @@ -1,13 +1,12 @@ 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 Q from django.http import Http404, HttpResponseRedirect from django.utils.translation import gettext_lazy as _ -from rest_framework import serializers +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.response import Response from rest_framework.reverse import reverse @@ -376,34 +375,61 @@ 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 decendants + 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=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 = [] + set_prefix_length = len(set_value.set_prefix.split('|')) + 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 = '|'.join([ + str(set_value.set_index) if (index == set_prefix_length - 1) else value + for index, value in enumerate(value.set_prefix.split('|')) + ]) + 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 decendants for this set - decendants_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 decendants - values = self.get_queryset().filter(attribute__in=attributes) \ - .filter( - Q(set_prefix=value.set_prefix, set_index=value.set_index) | - Q(set_prefix__startswith=decendants_set_prefix) - ) + set_value = self.get_object() + set_value.delete() + + # collect all values for this set and all decendants and delete them + values = self.get_queryset().filter_set(set_value) values.delete() return Response(status=204)