Skip to content

Commit

Permalink
[#5112] Optimize add_project_to_program API endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
zuhdil committed Jul 10, 2023
1 parent 62b617b commit ac930dd
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 29 deletions.
3 changes: 2 additions & 1 deletion akvo/rest/views/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
)
from akvo.rsr.models import ExternalProject, IndicatorPeriodData, OrganisationCustomField, Project, ProjectRole
from akvo.rsr.usecases.iati_validation import schedule_iati_activity_validation
from akvo.rsr.usecases.add_project_to_program import add_new_project_to_program
from akvo.rsr.views.my_rsr import user_viewable_projects
from akvo.utils import codelist_choices, get_thumbnail, single_period_dates
from ..viewsets import PublicProjectViewSet
Expand Down Expand Up @@ -488,7 +489,7 @@ def add_project_to_program(request, program_pk):
project = Project.objects.create()
Project.new_project_created(project.id, request.user) # Log creation
schedule_iati_activity_validation(project)
project.add_to_program(program)
add_new_project_to_program(project, program)
# Set user's primary org as accountable partner
org = request.user.first_organisation()
if org is not None and org != program.reporting_org:
Expand Down
117 changes: 89 additions & 28 deletions akvo/rsr/tests/rest/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,25 @@
For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.
"""

import datetime
from datetime import datetime
import json

from django.conf import settings
from django.contrib.admin.models import LogEntry
from django.contrib.auth.models import Group
from django.test import TestCase, Client
from akvo.iati.exports.utils import make_datetime_aware

from akvo.codelists.store import default_codelists as codelists
from akvo.rsr.factories.external_project import ExternalProjectFactory
from akvo.rsr.models import (
ExternalProject, Project, Organisation, Partnership, User,
Employment, ProjectLocation, ProjectEditorValidationSet,
OrganisationCustomField, ProjectCustomField, Result,
Sector
Sector, IndicatorDimensionValue, Indicator, IndicatorPeriod, IndicatorReference
)
from akvo.rsr.usecases.add_project_to_program import add_new_project_to_program
from akvo.rsr.tests.utils import ProjectFixtureBuilder
from akvo.utils import check_auth_groups
from akvo.rsr.tests.base import BaseTestCase

Expand Down Expand Up @@ -69,8 +72,8 @@ def test_rest_patch_project(self):
self.assertEqual(response.status_code, 200)
content = json.loads(response.content)
self.assertEqual(
datetime.datetime.strptime(content['date_start_actual'], '%Y-%m-%d'),
datetime.datetime.strptime(data['date_start_actual'], '%d-%m-%Y'),
datetime.strptime(content['date_start_actual'], '%Y-%m-%d'),
datetime.strptime(data['date_start_actual'], '%d-%m-%Y'),
)
self.assertEqual(content['date_start_planned'], data['date_start_planned'])

Expand Down Expand Up @@ -464,58 +467,116 @@ def setUp(self):
email = 'foo@bar.com'
self.user = self.create_user(email, 'password', is_superuser=True)
self.c.login(username=email, password='password')
org = self.create_organisation('Organisation')
program_fixture = ProjectFixtureBuilder()\
.as_program_of(org)\
.with_validations([v for v in ProjectEditorValidationSet.objects.all()])\
.with_disaggregations({
'Gender': ['Male', 'Female'],
})\
.with_default_periods([{
'period_start': make_datetime_aware(datetime(2010, 1, 1)),
'period_end': make_datetime_aware(datetime(2010, 12, 31)),
}])\
.with_results([
{
'title': 'Result #1',
'indicators': [{
'title': 'Indicator #1.1',
'references': [{'reference': 'test'}],
'periods': [{
'period_start': make_datetime_aware(datetime(2011, 1, 1)),
'period_end': make_datetime_aware(datetime(2011, 12, 31)),
}]
}]
},
{
'title': 'Result #2',
'indicators': [
{'title': 'Indicator #2.1'},
{'title': 'Indicator #2.2'}
]
},
])\
.build()
self.program = program_fixture.object

def test_add_project_to_program(self):
org = self.create_organisation('Organisation')
program = self.create_project('Program')
self.make_partner(program, org, Partnership.IATI_REPORTING_ORGANISATION)
self.create_project_hierarchy(org, program, 2)
Result.objects.create(project=program)
for validation_set in ProjectEditorValidationSet.objects.all():
program.add_validation_set(validation_set)
org2 = self.create_organisation('Delegation')
self.make_employment(self.user, org2, 'Admins')

data = {
'parent': program.pk
'parent': self.program.pk
}
response = self.c.post('/rest/v1/program/{}/add-project/?format=json'.format(program.id), data=data)
response = self.c.post('/rest/v1/program/{}/add-project/?format=json'.format(self.program.id), data=data)

self.assertEqual(response.status_code, 201)
child_project = Project.objects.get(id=response.data['id'])
self.assertEqual(child_project.reporting_org, program.reporting_org)
self.assertEqual(child_project.parent(), program)
self.assertEqual(child_project.results.count(), program.results.count())
self.assertEqual(child_project.validations.count(), program.validations.count())
self.assertEqual(child_project.parent(), self.program)
self.assertEqual(child_project.reporting_org, self.program.reporting_org)
partnership = child_project.partnerships.get(organisation=org2)
self.assertIsNotNone(partnership.iati_organisation_role, Partnership.IATI_ACCOUNTABLE_PARTNER)

self.assertEqual(child_project.validations.count(), self.program.validations.count())

self.assertEqual(child_project.default_periods.count(), self.program.default_periods.count())
self.assertEqual(child_project.default_periods.first().parent.project, self.program)

self.assertEqual(child_project.dimension_names.count(), self.program.dimension_names.count())
self.assertEqual(child_project.dimension_names.first().parent_dimension_name.project, self.program)
self.assertEqual(
IndicatorDimensionValue.objects.filter(name__project=child_project).count(),
IndicatorDimensionValue.objects.filter(name__project=self.program).count()
)
self.assertEqual(
IndicatorDimensionValue.objects.filter(name__project=child_project).first().parent_dimension_value.name.project,
self.program
)

self.assertEqual(child_project.results.count(), self.program.results.count())
self.assertEqual(child_project.results.first().parent_result.project, self.program)

self.assertEqual(
Indicator.objects.filter(result__project=child_project).count(),
Indicator.objects.filter(result__project=self.program).count()
)
first_child_indicator = Indicator.objects.filter(result__project=child_project).first()
self.assertEqual(first_child_indicator.parent_indicator.result.project, self.program)
self.assertEqual(first_child_indicator.dimension_names.count(), first_child_indicator.parent_indicator.dimension_names.count())

self.assertEqual(
IndicatorReference.objects.filter(indicator__result__project=child_project).count(),
IndicatorReference.objects.filter(indicator__result__project=self.program).count()
)

self.assertEqual(
IndicatorPeriod.objects.filter(indicator__result__project=child_project).count(),
IndicatorPeriod.objects.filter(indicator__result__project=self.program).count()
)
self.assertEqual(
IndicatorPeriod.objects.filter(indicator__result__project=child_project).first().parent_period.indicator.result.project,
self.program
)

def test_add_project_to_sub_program(self):
org = self.create_organisation('Organisation')
program = self.create_project('Program')
self.make_partner(program, org, Partnership.IATI_REPORTING_ORGANISATION)
self.create_project_hierarchy(org, program, 2)
Result.objects.create(project=program)
for validation_set in ProjectEditorValidationSet.objects.all():
program.add_validation_set(validation_set)
org2 = self.create_organisation('Delegation')
self.make_employment(self.user, org2, 'Admins')
sub_program = self.create_project('Sub-Program')
sub_program.add_to_program(program)
add_new_project_to_program(sub_program, self.program)
# Add an extra result at the sub program level
Result.objects.create(project=sub_program)

data = {
'parent': sub_program.pk
}
response = self.c.post('/rest/v1/program/{}/add-project/?format=json'.format(program.id), data=data)
response = self.c.post('/rest/v1/program/{}/add-project/?format=json'.format(self.program.id), data=data)

self.assertEqual(response.status_code, 201)
child_project = Project.objects.get(id=response.data['id'])
self.assertEqual(child_project.reporting_org, program.reporting_org)
self.assertEqual(child_project.reporting_org, self.program.reporting_org)
self.assertEqual(child_project.parent(), sub_program)
self.assertEqual(child_project.results.count(), sub_program.results.count())
self.assertEqual(child_project.validations.count(), program.validations.count())
self.assertEqual(child_project.validations.count(), self.program.validations.count())
partnership = child_project.partnerships.get(organisation=org2)
self.assertIsNotNone(partnership.iati_organisation_role, Partnership.IATI_ACCOUNTABLE_PARTNER)

Expand Down
31 changes: 31 additions & 0 deletions akvo/rsr/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
from akvo.rsr.models import (
Project, Result, Indicator, IndicatorPeriod, IndicatorPeriodData, Disaggregation, DisaggregationTarget,
IndicatorDimensionName, IndicatorDimensionValue, IndicatorPeriodLabel, IndicatorPeriodDataComment,
Partnership,
)
from akvo.rsr.models.project_hierarchy import ProjectHierarchy
from akvo.rsr.models.result.default_period import DefaultPeriod
from akvo.rsr.models.result.indicator_reference import IndicatorReference
from akvo.rsr.tests.base import BaseTestCase
from datetime import date, timedelta
import random
Expand All @@ -32,9 +36,12 @@ class ProjectFixtureBuilder(object):
"""
def __init__(self):
self.title = random_string()
self.validations = []
self.is_program = False
self.results = []
self.disaggregations = {}
self.period_labels = []
self.default_periods = []
self.partners = []
self.contributors = []
self._disaggregations_map = {}
Expand All @@ -43,10 +50,18 @@ def with_title(self, title):
self.title = title
return self

def with_validations(self, validations):
self.validations = validations
return self

def with_period_labels(self, labels):
self.period_labels = labels
return self

def with_default_periods(self, default_periods):
self.default_periods = default_periods
return self

def with_disaggregations(self, disaggregations):
self.disaggregations = disaggregations
return self
Expand All @@ -59,16 +74,27 @@ def with_partner(self, org, role=None):
self.partners.append((org, role))
return self

def as_program_of(self, org):
self.is_program = True
self.with_partner(org, Partnership.IATI_REPORTING_ORGANISATION)
return self

def with_contributors(self, contributors):
self.contributors = contributors
return self

def build(self):
project = BaseTestCase.create_project(self.title)
for validation in self.validations:
project.add_validation_set(validation)
for default_period in self.default_periods:
DefaultPeriod.objects.create(project=project, period_start=default_period['period_start'], period_end=default_period['period_end'])
for label in self.period_labels:
IndicatorPeriodLabel.objects.create(label=label, project=project)
for partner, role in self.partners:
BaseTestCase.make_partner(project, partner, role)
if self.is_program:
ProjectHierarchy.objects.create(root_project=project, max_depth=5)
self._build_disaggregations(project)
for params in self.results:
self._build_result(project, params)
Expand All @@ -87,16 +113,21 @@ def _build_result(self, project, params):

def _build_indicator(self, project, result, params):
periods = params.get('periods', [{'period_start': date.today(), 'period_end': date.today() + timedelta(days=30)}])
references = params.get('references', [])
kwargs = params.copy()
if 'periods' in kwargs:
del kwargs['periods']
if 'references' in kwargs:
del kwargs['references']
enumerators = kwargs.pop('enumerators', [])
kwargs['result'] = result
indicator = Indicator.objects.create(**kwargs)
for dimension_name in project.dimension_names.all():
indicator.dimension_names.add(dimension_name)
for params in periods:
self._build_period(project, indicator, params)
for reference in references:
IndicatorReference.objects.create(indicator=indicator, **reference)
for enumerator in enumerators:
indicator.enumerators.add(enumerator)

Expand Down
Loading

0 comments on commit ac930dd

Please sign in to comment.