Skip to content

Commit

Permalink
Transporter 2.4.7 Release
Browse files Browse the repository at this point in the history
Transporter 2.4.7 Release
  • Loading branch information
blakelong authored May 31, 2022
2 parents ec8a95b + 2ca9bed commit 978f085
Show file tree
Hide file tree
Showing 61 changed files with 1,899 additions and 1,078 deletions.
9 changes: 7 additions & 2 deletions indicators/export_renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@ def percent_cell(value):
return round(value, 3), '0.0%'
return value, '0.00%'

def percent_met_cell(self, value):
if value == 'N/A':
return self.str_cell(value)
return self.percent_cell(value)

@staticmethod
def percent_value_cell(value):
if not value and value != 0:
Expand All @@ -226,7 +231,7 @@ def get_period_report_data_columns(self, indicator_periods, values_func):
yield (period_data['target'], values_func, None, None)
yield (period_data['actual'], values_func, None, None)
if period_header.tva:
yield (period_data['met'], self.percent_cell, None, None)
yield (period_data['met'], self.percent_met_cell, RIGHT_ALIGN, None)

@staticmethod
def write_indicator_row(sheet, current_row, indicator_columns):
Expand Down Expand Up @@ -317,7 +322,7 @@ def add_indicator_data(self, indicator, sheet, current_row):
indicator_columns += [
(indicator['report_data']['lop_period']['target'], values_func, None, None),
(indicator['report_data']['lop_period']['actual'], values_func, None, None),
(indicator['report_data']['lop_period']['met'], self.percent_cell, None, None),
(indicator['report_data']['lop_period']['met'], self.percent_met_cell, RIGHT_ALIGN, None),
]
for period_column in self.get_period_report_data_columns(indicator['report_data']['periods'], values_func):
indicator_columns.append(period_column)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def add_arguments(self, parser):
parser.add_argument('--clean', action='store_true')
parser.add_argument('--delete_pilot_pc_indicators', action='store_true')
parser.add_argument('--change_label_name', action='store_true')
parser.add_argument('--change_outcome_themes', action='store_true')

@transaction.atomic
def handle(self, *args, **options):
Expand All @@ -58,7 +59,8 @@ def handle(self, *args, **options):
return

if options['delete_pilot_pc_indicators']:
pcind_id_list = [15872]
# Add ids to delete to list
pcind_id_list = []
# Call indicators on an object to object basis to force cascading delete.
deleted_ind = 0
for pcid in pcind_id_list:
Expand All @@ -70,9 +72,23 @@ def handle(self, *args, **options):
if not options['suppress_output']:
print(f'{deleted_ind} pilot pc indicators deleted, {len(pcind_id_list)-deleted_ind} not found')

if options['change_outcome_themes']:
theme_change_list = [{'old_name': 'Good Governance and Peace / Bonne gouvernance et paix / Buena gobernanza y paz', 'new_name': 'Good Governance and Peace'},
{'old_name': 'Economic Opportunity / Opportunité économique / Oportunidad económica', 'new_name': 'Economic Opportunity'},
{'old_name': 'Resilience', 'new_name': 'Resilience approach (tick this box if the program used a resilience approach)'}]
for theme_change in theme_change_list:
old_name = theme_change['old_name']
new_name = theme_change['new_name']
outcome_themes = OutcomeTheme.objects.filter(name=old_name)
for theme in outcome_themes:
theme.name = new_name
theme.save()
if not options['suppress_output']:
print(f' Change outcome theme name {old_name} to {new_name}')

if options['change_label_name']:
old_label = 'Health (non - nutrition)'
new_label = 'Public Health (non - nutrition, non - WASH)'
old_label = 'Governance and Partnership'
new_label = 'Peace, Governance and Partnership'
labels = DisaggregationLabel.objects.filter(label=old_label)
for label in labels:
label.label = new_label
Expand All @@ -86,7 +102,7 @@ def handle(self, *args, **options):

sector_list = sorted([
'Agriculture', 'Cash and Voucher Assistance', 'Environment (DRR, Energy and Water)',
'Infrastructure (non - WASH, non - energy)', 'Governance and Partnership', 'Employment', 'WASH',
'Infrastructure (non - WASH, non - energy)', 'Peace, Governance and Partnership', 'Employment', 'WASH',
'Financial Services', 'Nutrition', 'Public Health (non - nutrition, non - WASH)']
)

Expand All @@ -106,7 +122,7 @@ def handle(self, *args, **options):

created_counts = 0
outcome_theme_names = sorted([
'Food Security', 'Water Security', 'Economic Opportunities', 'Peace and Stability', 'Resilience'])
'Food Security', 'Water Security', 'Economic Opportunity', 'Good Governance and Peace', 'Resilience approach (tick this box if the program used a resilience approach)'])
for theme_name in outcome_theme_names:
theme_obj, created = OutcomeTheme.objects.get_or_create(name=theme_name, defaults={'is_active': True})
theme_obj.save()
Expand Down
3 changes: 0 additions & 3 deletions indicators/management/commands/create_qa_programs.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,10 @@ def handle(self, *args, **options):
'Alex': 'atran@mercycorps.org',
'Andre': 'anthomas@mercycorps.org',
'Blake': 'blong@mercycorps.org',
'Carly': 'colenick@mercycorps.org',
'Karen': 'kbarkemeyer@mercycorps.org',
'Margaux': 'mtroiano@mercycorps.org',
'Marie': 'mbakke@mercycorps.org',
'Marco': 'mscagliusi@mercycorps.org',
'PaQ': None,
'Sanjuro': 'sjogdeo@mercycorps.org',
}

program_factory = ProgramFactory(tolaland)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from datetime import datetime, date
from django.core.management.base import BaseCommand
from django.db import transaction
from django.db.models import Count
from indicators.models import Indicator
from workflow.models import Program
from indicators.utils import add_additional_periodic_targets


class Command(BaseCommand):
help = """
Add all relevant periodic targets to participant count indicators. It will create additional periodic targets
for participant count indicators with program dates that extent the current FY.that doesn't already have one.
"""

def add_arguments(self, parser):
parser.add_argument(
'--execute', action='store_true', help='Without this flag, the command will only be a dry run')
parser.add_argument(
'--change_customsort_to_fy', action='store_true', help='Changes customsort value of periodic targets to match the fiscal year')
parser.add_argument(
'--suppress_output', action='store_true',
help="Suppresses the output so tests don't get too messy")

@transaction.atomic
def handle(self, *args, **options):
if options['change_customsort_to_fy']:
pc_indicators = Indicator.objects.filter(admin_type=Indicator.ADMIN_PARTICIPANT_COUNT).prefetch_related('periodictargets')
for indicator in pc_indicators:
self.add_fy_customsort(indicator)

counts = {'eligible_programs': 0, 'ineligible_programs': 0, 'programs_pts_created': 0,}

today = datetime.utcnow().date()
reporting_end_date = date(today.year, 6, 30)

eligible_programs = Program.objects.filter(indicator__admin_type=Indicator.ADMIN_PARTICIPANT_COUNT, reporting_period_end__gt=reporting_end_date)
pc_indicators_multiple_pts = Indicator.objects.filter(admin_type=Indicator.ADMIN_PARTICIPANT_COUNT)\
.prefetch_related('periodictargets').annotate(num_pts=Count('periodictargets')).filter(num_pts__gte=2)
ineligible_programs = eligible_programs.filter(indicator__id__in=[ind.pk for ind in pc_indicators_multiple_pts])
counts['ineligible_programs'] = ineligible_programs.count()
eligible_programs = eligible_programs.exclude(indicator__id__in=[ind.pk for ind in pc_indicators_multiple_pts])
counts['eligible_programs'] = eligible_programs.count()

if options['execute']:
for program in eligible_programs:
add_additional_periodic_targets(program)
counts['programs_pts_created'] += 1

if not options['suppress_output']:
print('')
if options['verbosity'] > 1:
template = '{p.name}|{p.countries}|{p.reporting_period_start}|{p.reporting_period_end}|{p.funding_status}'
print('Programs with PC Indicators that have multiple pts already.')
for p in ineligible_programs:
print(template.format(p=p))
print('Created indicators for these programs')
for p in eligible_programs:
print(template.format(p=p))
print('\nStats')
for key in counts:
print(f'{key} count: {counts[key]}')
if not options['execute']:
print('\nINDICATOR CREATION WAS A DRY RUN\n')


@staticmethod
def add_fy_customsort(indicator):
for pt in indicator.periodictargets.all():
year = int(''.join(filter(str.isdigit, pt.period)))
pt.customsort = year
pt.save()


12 changes: 6 additions & 6 deletions indicators/management/commands/qa_program_widgets/qa_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def create_program(
})
program.country.add(self.country)
if multi_country:
country2 = Country.objects.get(country="United States")
country2 = Country.objects.get(country="United States - MCNW")
program.country.add(country2)

if create_levels:
Expand Down Expand Up @@ -578,7 +578,7 @@ def clean_programs():
print('\nPrograms not deleted')


standard_countries = ['Afghanistan', 'Haiti', 'Jordan', 'Tolaland', 'United States']
standard_countries = ['Afghanistan', 'Haiti', 'Jordan', 'Tolaland', 'United States - MCNW']
TEST_ORG, created = Organization.objects.get_or_create(name='Test')
MC_ORG = Organization.objects.get(name='Mercy Corps')
user_profiles = {
Expand All @@ -587,31 +587,31 @@ def clean_programs():
'email': 'tolatestone@mercycorps.org',
'accessible_countries': standard_countries,
'permission_level': 'low',
'home_country': 'United States',
'home_country': 'United States - MCNW',
'org': MC_ORG,
},
'mc-medium': {
'first_last': ['mc-med-first', 'mc-med-last'],
'email': 'tolatesttwo@mercycorps.org',
'accessible_countries': standard_countries,
'permission_level': 'medium',
'home_country': 'United States',
'home_country': 'United States - MCNW',
'org': MC_ORG,
},
'mc-high': {
'first_last': ['mc-high-first', 'mc-high-last'],
'email': 'tolatestthree@mercycorps.org',
'accessible_countries': standard_countries,
'permission_level': 'high',
'home_country': 'United States',
'home_country': 'United States - MCNW',
'org': MC_ORG,
},
'mc-basicadmin': {
'first_last': ['mc-basicadmin-first', 'mc-basicadmin-last'],
'email': 'mcbasicadmin@example.com',
'accessible_countries': standard_countries,
'permission_level': 'high',
'home_country': 'United States',
'home_country': 'United States - MCNW',
'org': MC_ORG,
'admin': 'all'
},
Expand Down
32 changes: 32 additions & 0 deletions indicators/management/commands/rf_hotfix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from django.core.management.base import BaseCommand
from django.db.utils import IntegrityError
from indicators.models import LevelTier


class Command(BaseCommand):
"""
Command to resolve errors related to a RF program missing level tiers
"""
help = "Creates level tiers for Nawiri"

def add_arguments(self, parser):
parser.add_argument('--execute', action='store_true', help='Without this flag, the command will only be a dry run')

def handle(self, *args, **options):
program_id = 819
level_tiers = [
{'name': 'Goal', 'tier_depth': 1, 'program_id': program_id},
{'name': 'Outcome', 'tier_depth': 2, 'program_id': program_id},
{'name': 'Output', 'tier_depth': 3, 'program_id': program_id},
{'name': 'Activity', 'tier_depth': 4, 'program_id': program_id},
]

for level_tier in level_tiers:
if options['execute']:
try:
new_level_tier = LevelTier(**level_tier)
new_level_tier.save()
print(f'Created level {level_tier["name"]}')
except IntegrityError:
# IntegrityError is raised if the unique constraint fails
continue
35 changes: 34 additions & 1 deletion indicators/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import string
import uuid
import os
from datetime import timedelta, date
from datetime import timedelta, date, datetime
from decimal import Decimal
import dateparser
from functools import total_ordering
Expand Down Expand Up @@ -1782,6 +1782,29 @@ def current_periodic_target(self, date_=None):
today = date_ or timezone.localdate()
return self.periodictargets.filter(start_date__lte=today, end_date__gte=today).first()

@property
def is_fiscal_year(self):
"""Checks whether any pc indicator results are editable at current date"""
# Always true for non pc indicators
is_fiscal_year = True
if self.admin_type == Indicator.ADMIN_PARTICIPANT_COUNT:
# Fiscal year end: June 30
# Reporting period for pc indicators: July, August
# For pc indicators boolean is to set to false unless the current month < September and current fiscal year
# periodic target is associated with indicator, or current months > June and next year's fiscal year
# periodic target is associated with indicator
is_fiscal_year = False
today = datetime.utcnow().date()
current_year = today.year
current_month = today.month
last_month_to_report = settings.REPORTING_PERIOD_LAST_MONTH
targets = self.periodictargets.all()
target_list = [target.customsort for target in targets]
if (current_year in target_list and current_month <= last_month_to_report) or (
current_year + 1 in target_list and current_month > 6):
is_fiscal_year = True
return is_fiscal_year

@property
def last_ended_periodic_target(self):
"""
Expand Down Expand Up @@ -2320,6 +2343,16 @@ def date_collected_formatted(self):
return formats.date_format(self.date_collected, "MEDIUM_DATE_FORMAT")
return self.date_collected

@property
def pt_start_date(self):
year = self.periodic_target.customsort
return date(year - 1, 7, 1)

@property
def pt_end_date(self):
year = self.periodic_target.customsort
return date(year, 6, 30)

@property
def disaggregated_values(self):
return self.disaggregatedvalue_set.all().order_by(
Expand Down
7 changes: 7 additions & 0 deletions indicators/queries/targets_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,13 @@ def target_percent_met_annotation():
),
then=models.Value(None)
),
models.When(
models.Q(indicator__direction_of_change=Indicator.DIRECTION_OF_CHANGE_NEGATIVE),
then=models.ExpressionWrapper(
models.F('target') / models.F('actual'),
output_field=models.FloatField()
)
),
default=models.ExpressionWrapper(
models.F('actual') / models.F('target'),
output_field=models.FloatField()
Expand Down
7 changes: 7 additions & 0 deletions indicators/queries/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,13 @@ def indicator_lop_percent_met_annotation():
models.Q(lop_target_calculated=0),
then=models.Value(None)
),
models.When(
models.Q(direction_of_change=Indicator.DIRECTION_OF_CHANGE_NEGATIVE),
then=models.ExpressionWrapper(
models.F('lop_target_calculated') / models.F('lop_actual'),
output_field=models.FloatField()
)
),
default=models.ExpressionWrapper(
models.F('lop_actual') / models.F('lop_target_calculated'),
output_field=models.FloatField()
Expand Down
11 changes: 9 additions & 2 deletions indicators/serializers_new/iptt_indicator_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class IPTTIndicatorFiltersMixin:
"""

sector_pk = serializers.IntegerField(source='sector_id')
is_fiscal_year = serializers.BooleanField()
indicator_type_pks = serializers.SerializerMethodField()
site_pks = serializers.SerializerMethodField()
disaggregation_pks = serializers.SerializerMethodField()
Expand All @@ -67,6 +68,7 @@ class Meta:
'site_pks',
'disaggregation_pks',
'admin_type',
'is_fiscal_year',
]

@classmethod
Expand Down Expand Up @@ -385,7 +387,8 @@ def _get_period(self, indicator, period_dict):
return {
'count': period_dict.get('customsort', None),
'actual': actual,
'disaggregations': disaggregations
'disaggregations': disaggregations,
'decreasing': indicator.direction_of_change == Indicator.DIRECTION_OF_CHANGE_NEGATIVE
}

# serializer method fields (populate fields on serializer):
Expand All @@ -397,6 +400,7 @@ def get_lop_period(self, indicator):
return self._lop_period_serializer.from_dict(
{'target': indicator.lop_target_calculated,
'actual': actual,
'decreasing': indicator.direction_of_change == Indicator.DIRECTION_OF_CHANGE_NEGATIVE,
'disaggregations': disaggregations},
context=self._get_period_context(indicator)).data

Expand Down Expand Up @@ -558,7 +562,10 @@ def _get_period(self, indicator, period_dict):
]
period['target'] = targets[0].target if targets else None
if period['target'] and period['actual']:
period['met'] = make_quantized_decimal(period['actual'] / period['target'], places=4)
if indicator.direction_of_change == Indicator.DIRECTION_OF_CHANGE_NEGATIVE:
period['met'] = make_quantized_decimal(period['target'] / period['actual'], places=4)
else:
period['met'] = make_quantized_decimal(period['actual'] / period['target'], places=4)
else:
period['met'] = None
return period
Expand Down
Loading

0 comments on commit 978f085

Please sign in to comment.