Source code for akvo.rsr.management.commands.single_period_indicators

# -*- coding: utf-8 -*-

# Akvo Reporting is covered by the GNU Affero General Public License.
# See more details in the license.txt file located at the root folder of the Akvo RSR module.
# For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.


import tablib

from django.conf import settings
from django.core.management.base import BaseCommand

from akvo.rsr.models import IndicatorPeriodData
from akvo.utils import single_period_dates
from ...models import Project, Indicator, IndicatorPeriod


[docs]class Command(BaseCommand): help = ('Convert a project hierarchy to use single period reporting')
[docs] def add_arguments(self, parser): parser.add_argument( action='store', dest='name', help='Name of the project hierarchy' ) parser.add_argument( action='store', nargs='+', dest='project_ids', help='Name of the project hierarchy' ) parser.add_argument( '--live', action='store_true', dest='live', default=False, help='Live run, changes made to the models' )
[docs] def handle(self, *args, **options): def set_period_dates(period): period.period_start = PERIOD_START period.period_end = PERIOD_END period.save() def get_parent_periods(indicator, PERIOD_START, PERIOD_END): return indicator.parent_indicator.periods.filter( period_start=PERIOD_START, period_end=PERIOD_END ) def find_periods_with_data(periods): """ Separate a list of periods into two groups: those tht have been modified from an original, "empty" state and those that haven't. To be counted as modified a period has data in at least one of the fields target_value, target_comment, actual_value or actual_comment or has at least one update. """ modified_periods = [] unmodified_periods = [] for period in periods: updates_exist = IndicatorPeriodData.objects.filter(period=period).exists() if (period.target_value or period.target_comment or period.actual_value or period.actual_comment or updates_exist): modified_periods += [period] else: unmodified_periods += [period] return modified_periods, unmodified_periods name = options['name'] config = settings.SINGLE_PERIOD_INDICATORS[name] live = options['live'] _, PERIOD_START, PERIOD_END = single_period_dates(name) assert PERIOD_START, "No start date configured. Aborting." assert PERIOD_END, "No end date configured. Aborting." pk = config['pk'] root = Project.objects.get(pk=pk) hierarchy_projects = root.descendants() project_ids = options['project_ids'] if project_ids[0] == 'all': projects = hierarchy_projects else: projects = Project.objects.filter(id__in=map(int, project_ids)) in_hierarchy = set(projects) < set(hierarchy_projects) assert in_hierarchy, "Not all projects part of the hierarchy. Aborting" period_data = tablib.Dataset( headers=[ 'Project ID', 'Result ID', 'Result title', 'Indicator ID', 'Indicator title', 'Comment', ]) for project in projects: for indicator in Indicator.objects.filter( result__project=project ).select_related('result'): periods = IndicatorPeriod.objects.filter( indicator=indicator).order_by('-period_end') period_count = periods.count() row_data = [ project.pk, indicator.result.pk, indicator.result.title.replace('\t', ' ').replace('\n', ' '), indicator.pk, indicator.title.replace('\t', ' ').replace('\n', ' '), ] # No period present. Create one and link it if indicator has a parent if period_count == 0: # If the indicator has a parent we need to find and link to the parent period if indicator.parent_indicator: parent_periods = get_parent_periods(indicator, PERIOD_START, PERIOD_END) if parent_periods.count() != 1: row_data += ["ERROR: Can't find parent period."] continue else: parent_period = parent_periods[0] else: parent_period = None if live: period = IndicatorPeriod.objects.create( indicator=indicator, parent_period=parent_period, period_start=PERIOD_START, period_end=PERIOD_END, ) row_data += ['Created period: {}'.format(period.pk)] else: row_data += ['Would create period'] # One period. Change its dates. elif period_count == 1: period = periods[0] if live: set_period_dates(period) row_data += ['Modified period: {}'.format(period.pk)] else: row_data += ['Would modify period: {}'.format(period.pk)] # Multiple periods, try to keep one. else: # Check if more than one period "has data" modified_periods, unmodified_periods = find_periods_with_data(periods) # If it does, report and don't touch! if len(modified_periods) > 1: row_data += ['ERROR: Multiple periods, more than one has data'] # Otherwise keep the period with data, delete the others else: if len(modified_periods) == 1: period_to_keep = modified_periods[0] else: period_to_keep = unmodified_periods[0] unmodified_periods = unmodified_periods[1:] if live: for period in unmodified_periods: period.delete() set_period_dates(period_to_keep) row_data += ['Deleted {} periods, set dates on period {}'.format( len(unmodified_periods), period_to_keep.pk )] else: row_data += ['Would delete {} periods, set dates on period {}'.format( len(unmodified_periods), period_to_keep.pk )] period_data.append(row_data) # Exporting as tsv results in errors I can't explain in som cases, columns get merged :-( # Looks like a bug in tablib print(period_data.export('csv'))