diff --git a/rdmo/projects/progress.py b/rdmo/projects/progress.py index 6271c11bbd..1afe1049f4 100644 --- a/rdmo/projects/progress.py +++ b/rdmo/projects/progress.py @@ -6,10 +6,7 @@ from rdmo.questions.models import Catalog, Section, Page, QuestionSet, Question -def compute_progress(project, snapshot=None): - # get all values for this project and snapshot - project_values = project.values.filter(snapshot=snapshot).select_related('attribute', 'option') - +def resolve_conditions(project, values): # get all conditions for this catalog pages_conditions_subquery = Page.objects.filter_by_catalog(project.catalog).filter(conditions=OuterRef('pk')) questionsets_conditions_subquery = QuestionSet.objects.filter_by_catalog(project.catalog).filter(conditions=OuterRef('pk')) @@ -24,24 +21,88 @@ def compute_progress(project, snapshot=None): # evaluate conditions conditions = set() for condition in catalog_conditions: - if condition.resolve(project_values): + if condition.resolve(values): conditions.add(condition.id) - # compute sets from values + # return all true conditions for this project + return conditions + + +def compute_sets(values): sets = defaultdict(list) - for attribute, set_index in project_values.values_list('attribute', 'set_index').distinct(): + for attribute, set_index in values.values_list('attribute', 'set_index').distinct(): sets[attribute].append(set_index) + return sets + + +def compute_navigation(section, project, snapshot=None): + # get all values for this project and snapshot + values = project.values.filter(snapshot=snapshot).select_related('attribute', 'option') + + # get true conditions + conditions = resolve_conditions(project, values) + + # compute sets from values + sets = compute_sets(values) + + # query non empty values + values_list = values.exclude((Q(text='') | Q(text=None)) & Q(option=None) & + (Q(file='') | Q(file=None))) \ + .values_list('attribute', 'set_index').distinct() \ + .values_list('attribute', flat=True) + + navigation = [] + for catalog_section in project.catalog.elements: + navigation_section = { + 'id': catalog_section.id, + 'title': catalog_section.title, + 'first': catalog_section.elements[0].id if section.elements else None + } + if catalog_section.id == section.id: + navigation_section['pages'] = [] + for page in catalog_section.elements: + pages_conditions = set(page.id for page in page.conditions.all()) + show = bool(not pages_conditions or pages_conditions.intersection(conditions)) + + # count the total number of questions, taking sets and conditions into account + total, attributes = count_questions(page, sets, conditions) + + # filter the project values for the counted questions and exclude empty values + count = len(tuple(filter(lambda attribute: attribute in attributes, values_list))) + + navigation_section['pages'].append({ + 'id': page.id, + 'title': page.title, + 'show': show, + 'count': count, + 'total': total + }) + + navigation.append(navigation_section) + + return navigation + + +def compute_progress(project, snapshot=None): + # get all values for this project and snapshot + values = project.values.filter(snapshot=snapshot).select_related('attribute', 'option') + + # get true conditions + conditions = resolve_conditions(project, values) + + # compute sets from values + sets = compute_sets(values) # count the total number of questions, taking sets and conditions into account - total_count, attributes = count_questions(project.catalog, sets, conditions) + total, attributes = count_questions(project.catalog, sets, conditions) # filter the project values for the counted questions and exclude empty values - values_count = project_values.filter(attribute__in=attributes) \ - .exclude((Q(text='') | Q(text=None)) & Q(option=None) & - (Q(file='') | Q(file=None))) \ - .count() + count = values.filter(attribute_id__in=attributes) \ + .exclude((Q(text='') | Q(text=None)) & Q(option=None) & + (Q(file='') | Q(file=None))) \ + .values_list('attribute', 'set_index').distinct().count() - return values_count, total_count + return count, total def count_questions(parent_element, sets, conditions): @@ -58,11 +119,11 @@ def count_questions(parent_element, sets, conditions): if not element_conditions or element_conditions.intersection(conditions): if isinstance(element, Question): if not element.is_optional: - attributes.append(element.attribute) + attributes.append(element.attribute_id) count += 1 else: - if element.attribute: - attributes.append(element.attribute) + if element.attribute_id: + attributes.append(element.attribute_id) element_count, element_attributes = count_questions(element, sets, conditions) set_count = count_sets(element, sets) @@ -75,7 +136,7 @@ def count_questions(parent_element, sets, conditions): def count_sets(parent_element, sets): if parent_element.is_collection: - if parent_element.attribute: + if parent_element.attribute_id: count = len(sets[parent_element.attribute_id]) else: count = 0 diff --git a/rdmo/projects/serializers/v1/overview.py b/rdmo/projects/serializers/v1/overview.py index a0428b9836..cc5cfdf3e2 100644 --- a/rdmo/projects/serializers/v1/overview.py +++ b/rdmo/projects/serializers/v1/overview.py @@ -1,51 +1,18 @@ from rest_framework import serializers from rdmo.projects.models import Project -from rdmo.questions.models import Catalog, Page, Section - - -class PageSerializer(serializers.ModelSerializer): - - class Meta: - model = Page - fields = ( - 'id', - 'title', - 'has_conditions' - ) - - -class SectionSerializer(serializers.ModelSerializer): - - pages = serializers.SerializerMethodField() - - class Meta: - model = Section - fields = ( - 'id', - 'title', - 'pages' - ) - - def get_pages(self, obj): - return PageSerializer(obj.elements, many=True, read_only=True).data +from rdmo.questions.models import Catalog class CatalogSerializer(serializers.ModelSerializer): - sections = serializers.SerializerMethodField() - class Meta: model = Catalog fields = ( 'id', - 'title', - 'sections' + 'title' ) - def get_sections(self, obj): - return SectionSerializer(obj.elements, many=True, read_only=True).data - class ProjectOverviewSerializer(serializers.ModelSerializer): diff --git a/rdmo/projects/serializers/v1/page.py b/rdmo/projects/serializers/v1/page.py index 838cfe4591..33f0412ca8 100644 --- a/rdmo/projects/serializers/v1/page.py +++ b/rdmo/projects/serializers/v1/page.py @@ -171,6 +171,7 @@ def get_section(self, obj): return { 'id': section.id, 'title': section.title, + 'first': section.elements[0].id if section.elements else None } if section else {} def get_prev_page(self, obj): diff --git a/rdmo/projects/static/projects/js/project_questions/services.js b/rdmo/projects/static/projects/js/project_questions/services.js index 9303fed91f..e57a3616ae 100644 --- a/rdmo/projects/static/projects/js/project_questions/services.js +++ b/rdmo/projects/static/projects/js/project_questions/services.js @@ -9,7 +9,7 @@ angular.module('project_questions') /* configure resources */ var resources = { - projects: $resource(baseurl + 'api/v1/projects/projects/:id/:detail_action/'), + projects: $resource(baseurl + 'api/v1/projects/projects/:id/:detail_action/:detail_id/'), values: $resource(baseurl + 'api/v1/projects/projects/:project/values/:id/:detail_action/'), pages: $resource(baseurl + 'api/v1/projects/projects/:project/pages/:list_action/:id/'), settings: $resource(baseurl + 'api/v1/core/settings/') @@ -147,13 +147,14 @@ angular.module('project_questions') } return service.fetchPage(page_id) + .then(service.fetchNavigation) .then(service.fetchOptions) .then(service.fetchValues) .then(service.fetchConditions) .then(function () { // copy future objects angular.forEach([ - 'page', 'progress', 'attributes', 'questionsets', 'questions', 'valuesets', 'values' + 'page', 'progress', 'attributes', 'questionsets', 'questions', 'valuesets', 'values', 'navigation' ], function (key) { service[key] = angular.copy(future[key]); }); @@ -276,6 +277,16 @@ angular.module('project_questions') }); }; + service.fetchNavigation = function() { + future.navigation = resources.projects.query({ + id: service.project.id, + detail_id: future.page.section.id, + detail_action: 'navigation' + }); + + return future.navigation.$promise + }; + service.initPage = function(page) { // store attributes in a separate array if (page.attribute !== null) future.attributes.push(page.attribute); @@ -868,16 +879,7 @@ angular.module('project_questions') } else if (angular.isDefined(page)) { service.initView(page.id); } else if (angular.isDefined(section)) { - if (angular.isDefined(section.pages)) { - service.initView(section.pages[0].id); - } else { - // jump to first page of the section in breadcrumb - // let section_from_service = service.project.catalog.sections.find(x => x.id === section.id) - var section_from_service = $filter('filter')(service.project.catalog.sections, { - id: section.id - })[0] - service.initView(section_from_service.pages[0].id); - } + service.initView(section.first); } else { service.initView(null); } @@ -886,16 +888,7 @@ angular.module('project_questions') if (angular.isDefined(page)) { service.initView(page.id); } else if (angular.isDefined(section)) { - if (angular.isDefined(section.pages)) { - service.initView(section.pages[0].id); - } else { - // jump to first page of the section in breadcrumb - // let section_from_service = service.project.catalog.sections.find(x => x.id === section.id) - var section_from_service = $filter('filter')(service.project.catalog.sections, { - id: section.id - })[0] - service.initView(section_from_service.pages[0].id); - } + service.initView(section.first); } else { service.initView(null); } diff --git a/rdmo/projects/templates/projects/project_questions_navigation.html b/rdmo/projects/templates/projects/project_questions_navigation.html index f5945d8db2..6f9a1f2eb9 100644 --- a/rdmo/projects/templates/projects/project_questions_navigation.html +++ b/rdmo/projects/templates/projects/project_questions_navigation.html @@ -3,22 +3,27 @@ {% include 'projects/project_questions_navigation_help.html' %}