Skip to content

Commit 4b44a3b

Browse files
authored
Merge pull request #1864 from unicef/staging
Staging
2 parents b2520fd + cd35a25 commit 4b44a3b

File tree

83 files changed

+1338
-874
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+1338
-874
lines changed

src/etools/applications/EquiTrack/utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44
import codecs
55
import csv
6+
import hashlib
67
import json
78
from datetime import datetime
89

@@ -173,3 +174,7 @@ def get_quarter(retrieve_date=None):
173174
else:
174175
quarter = 'q4'
175176
return quarter
177+
178+
179+
def h11(w):
180+
return hashlib.md5(w).hexdigest()[:9]

src/etools/applications/action_points/export/serializers.py

Lines changed: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,21 @@
33

44
class ActionPointExportSerializer(serializers.Serializer):
55
ref = serializers.CharField(source='reference_number', read_only=True)
6-
cp_output = serializers.SerializerMethodField()
7-
partner = serializers.SerializerMethodField()
8-
office = serializers.SerializerMethodField()
9-
section = serializers.SerializerMethodField()
10-
category = serializers.SerializerMethodField()
11-
assigned_to = serializers.CharField(source='assigned_to.get_full_name')
6+
cp_output = serializers.CharField(source='cp_output.__str__', allow_null=True)
7+
partner = serializers.CharField(source='partner.name', allow_null=True)
8+
office = serializers.CharField(source='office.name', allow_null=True)
9+
section = serializers.CharField(source='section.name', allow_null=True)
10+
category = serializers.CharField(source='category.description', allow_null=True)
11+
assigned_to = serializers.CharField(source='assigned_to.get_full_name', allow_null=True)
1212
due_date = serializers.DateField(format='%d/%m/%Y')
1313
status = serializers.CharField(source='get_status_display')
1414
description = serializers.CharField()
15-
intervention = serializers.CharField(source='intervention.reference_number', read_only=True)
16-
pd_ssfa = serializers.SerializerMethodField()
17-
location = serializers.SerializerMethodField()
15+
intervention = serializers.CharField(source='intervention.reference_number', read_only=True, allow_null=True)
16+
pd_ssfa = serializers.CharField(source='intervention.title', allow_null=True)
17+
location = serializers.CharField(source='location.__str__', allow_null=True)
1818
related_module = serializers.CharField()
19-
assigned_by = serializers.CharField(source='assigned_by.get_full_name')
19+
assigned_by = serializers.CharField(source='assigned_by.get_full_name', allow_null=True)
2020
date_of_completion = serializers.DateTimeField(format='%d/%m/%Y')
21-
related_ref = serializers.CharField(source='related_object.reference_number', read_only=True)
21+
related_ref = serializers.CharField(source='related_object.reference_number', read_only=True, allow_null=True)
2222
related_object_str = serializers.CharField()
2323
related_object_url = serializers.CharField()
24-
25-
def get_cp_output(self, obj):
26-
return obj.cp_output.__str__ if obj.cp_output else ""
27-
28-
def get_partner(self, obj):
29-
return obj.partner.name if obj.partner else ""
30-
31-
def get_office(self, obj):
32-
return obj.office.name if obj.office else ""
33-
34-
def get_section(self, obj):
35-
return obj.section.name if obj.section else ""
36-
37-
def get_category(self, obj):
38-
return obj.category.description if obj.category else ""
39-
40-
def get_pd_ssfa(self, obj):
41-
return obj.intervention.title if obj.intervention else ""
42-
43-
def get_location(self, obj):
44-
return obj.location.__str__ if obj.location else ""

src/etools/applications/action_points/models.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

1414
from etools.applications.action_points.categories.models import Category
1515
from etools.applications.action_points.transitions.conditions import ActionPointCompleteActionsTakenCheck
16-
from etools.applications.EquiTrack.utils import get_environment
1716
from etools.applications.action_points.transitions.serializers.serializers import ActionPointCompleteSerializer
17+
from etools.applications.EquiTrack.utils import get_environment
1818
from etools.applications.permissions2.fsm import has_action_permission
1919
from etools.applications.utils.common.urlresolvers import build_frontend_url
2020
from etools.applications.utils.groups.wrappers import GroupWrapper
@@ -23,9 +23,12 @@
2323
class ActionPoint(TimeStampedModel):
2424
MODULE_CHOICES = Category.MODULE_CHOICES
2525

26+
STATUS_OPEN = 'open'
27+
STATUS_COMPLETED = 'completed'
28+
2629
STATUSES = Choices(
27-
('open', _('Open')),
28-
('completed', _('Completed')),
30+
(STATUS_OPEN, _('Open')),
31+
(STATUS_COMPLETED, _('Completed')),
2932
)
3033

3134
STATUSES_DATES = {

src/etools/applications/vision/adapters/purchase_order.py renamed to src/etools/applications/audit/purchase_order/synchronizers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from etools.applications.audit.purchase_order.models import AuditorFirm, PurchaseOrder, PurchaseOrderItem
55
from etools.applications.funds.models import Donor, Grant
6-
from etools.applications.vision.adapters.manual import ManualVisionSynchronizer
6+
from etools.applications.vision.synchronizers import ManualVisionSynchronizer
77

88

99
class POSynchronizer(ManualVisionSynchronizer):
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from celery.utils.log import get_task_logger
2+
3+
# Not scheduled by any code in this repo, but by other means, so keep it around.
4+
# Continues on to the next country on any VisionException, so no need to have
5+
# celery retry it in that case.
6+
from etools.applications.audit.purchase_order.synchronizers import POSynchronizer
7+
from etools.applications.users.models import Country
8+
from etools.applications.vision.exceptions import VisionException
9+
from etools.config.celery import app
10+
11+
logger = get_task_logger(__name__)
12+
13+
14+
@app.task
15+
def update_purchase_orders(country_name=None):
16+
logger.info(u'Starting update values for purchase order')
17+
countries = Country.objects.filter(vision_sync_enabled=True)
18+
processed = []
19+
if country_name is not None:
20+
countries = countries.filter(name=country_name)
21+
for country in countries:
22+
try:
23+
logger.info(u'Starting purchase order update for country {}'.format(
24+
country.name
25+
))
26+
POSynchronizer(country).sync()
27+
processed.append(country.name)
28+
logger.info(u"Update finished successfully for {}".format(country.name))
29+
except VisionException:
30+
logger.exception(u"{} sync failed".format(POSynchronizer.__name__))
31+
# Keep going to the next country
32+
logger.info(u'Purchase orders synced successfully for {}.'.format(u', '.join(processed)))

src/etools/applications/vision/tests/test_adapter_purchase_order.py renamed to src/etools/applications/audit/purchase_order/tests/test_synchronizers.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11

22
import json
3+
from unittest import mock
34

5+
from etools.applications.audit.purchase_order import synchronizers
46
from etools.applications.audit.purchase_order.models import AuditorFirm, PurchaseOrder, PurchaseOrderItem
7+
from etools.applications.audit.purchase_order.tasks import update_purchase_orders
58
from etools.applications.EquiTrack.tests.cases import BaseTenantTestCase
69
from etools.applications.funds.models import Donor, Grant
710
from etools.applications.users.models import Country
8-
from etools.applications.vision.adapters import purchase_order as adapter
911

1012

1113
class TestPSynchronizer(BaseTenantTestCase):
@@ -25,14 +27,14 @@ def setUp(self):
2527
"GRANT_REF": "Grantor",
2628
"PO_ITEM": "456",
2729
}
28-
self.adapter = adapter.POSynchronizer(self.country)
30+
self.adapter = synchronizers.POSynchronizer(self.country)
2931

3032
def test_init_no_object_number(self):
31-
a = adapter.POSynchronizer(self.country)
33+
a = synchronizers.POSynchronizer(self.country)
3234
self.assertEqual(a.country, self.country)
3335

3436
def test_init(self):
35-
a = adapter.POSynchronizer(self.country, object_number="123")
37+
a = synchronizers.POSynchronizer(self.country, object_number="123")
3638
self.assertEqual(a.country, self.country)
3739

3840
def test_convert_records_list(self):
@@ -78,3 +80,23 @@ def test_save_records(self):
7880
self.assertTrue(auditor_qs.exists())
7981
self.assertTrue(donor_qs.exists())
8082
self.assertTrue(grant_qs.exists())
83+
84+
85+
class TestUpdatePurchaseOrders(BaseTenantTestCase):
86+
@classmethod
87+
def setUpTestData(cls):
88+
cls.country = Country.objects.first()
89+
90+
@mock.patch("etools.applications.vision.tasks.logger.exception")
91+
def test_update_purchase_orders_no_country(self, mock_logger_exception):
92+
"""Ensure no exceptions if no countries"""
93+
update_purchase_orders(country_name="Wrong")
94+
self.assertEqual(mock_logger_exception.call_count, 0)
95+
96+
@mock.patch("etools.applications.vision.tasks.logger.exception")
97+
@mock.patch("etools.applications.audit.purchase_order.tasks.POSynchronizer")
98+
def test_update_purchase_orders(self, synchronizer, mock_logger_exception):
99+
"""Ensure no exceptions if no countries"""
100+
update_purchase_orders()
101+
self.assertEqual(mock_logger_exception.call_count, 0)
102+
self.assertEqual(synchronizer.call_count, 1)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from unittest.mock import patch
2+
3+
from etools.applications.EquiTrack.tests.cases import BaseTenantTestCase
4+
from etools.applications.audit.purchase_order.tasks import update_purchase_orders
5+
6+
7+
class TestUpdatePurchaseOrders(BaseTenantTestCase):
8+
9+
@patch("etools.applications.audit.purchase_order.synchronizers.POSynchronizer.sync")
10+
@patch('etools.applications.audit.purchase_order.tasks.logger', spec=['info', 'error'])
11+
def test_update_purchase_orders(self, logger, mock_send):
12+
update_purchase_orders()
13+
self.assertEqual(mock_send.call_count, 1)
14+
self.assertEqual(logger.info.call_count, 4)

src/etools/applications/audit/serializers/export.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,8 @@ class Meta(EngagementPDFSerializer.Meta):
245245
class EngagementBaseDetailCSVSerializer(serializers.Serializer):
246246
unique_id = serializers.ReadOnlyField()
247247
link = serializers.ReadOnlyField(source='get_object_url')
248-
auditor = serializers.ReadOnlyField(source='agreement.auditor_firm.__str__')
249-
partner = serializers.ReadOnlyField(source='partner.__str__')
248+
auditor = serializers.ReadOnlyField(source='agreement.auditor_firm')
249+
partner = serializers.ReadOnlyField()
250250
status_display = serializers.SerializerMethodField()
251251

252252
def get_status_display(self, obj):
@@ -298,7 +298,7 @@ def get_subject_area(self, obj):
298298
weaknesses = serializer.to_representation(serializer.get_attribute(instance=obj))
299299

300300
return OrderedDict(
301-
(b['id'], ', '.join([risk['value_display'] for risk in b['risks']]) or '-')
301+
(b['id'], ', '.join([str(risk['value_display']) for risk in b['risks']]) or '-')
302302
for b in weaknesses['blueprints']
303303
)
304304

@@ -339,7 +339,7 @@ def get_questionnaire(self, obj):
339339
)
340340

341341

342-
class SpecialAuditDetailPDFSerializer(EngagementBaseDetailCSVSerializer):
342+
class SpecialAuditDetailCSVSerializer(EngagementBaseDetailCSVSerializer):
343343
"""
344344
345345
"""

src/etools/applications/audit/tests/test_views.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,32 @@ def test_hact_view(self):
376376
self.assertEqual(len(response.data), 1)
377377
self.assertNotEqual(response.data[0], {})
378378

379+
def test_csv_view(self):
380+
AuditFactory()
381+
MicroAssessmentFactory()
382+
SpotCheckFactory()
383+
384+
response = self.forced_auth_req(
385+
'get',
386+
'/api/audit/engagements/csv/',
387+
user=self.unicef_user,
388+
)
389+
390+
self.assertEqual(response.status_code, status.HTTP_200_OK)
391+
self.assertIn('text/csv', response['Content-Type'])
392+
393+
def test_staff_spot_checks_csv_view(self):
394+
engagement = StaffSpotCheckFactory()
395+
396+
response = self.forced_auth_req(
397+
'get',
398+
'/api/audit/staff-spot-checks/csv/'.format(engagement.id),
399+
user=self.unicef_user,
400+
)
401+
402+
self.assertEqual(response.status_code, status.HTTP_200_OK)
403+
self.assertIn('text/csv', response['Content-Type'])
404+
379405

380406
class BaseTestEngagementsCreateViewSet(EngagementTransitionsTestCaseMixin):
381407
endpoint = 'engagements'
@@ -1081,9 +1107,8 @@ def setUpTestData(cls):
10811107
def test_csv_view(self):
10821108
response = self.forced_auth_req(
10831109
'get',
1084-
'/api/audit/micro-assessments/{}/'.format(self.engagement.id),
1110+
'/api/audit/micro-assessments/{}/csv/'.format(self.engagement.id),
10851111
user=self.unicef_user,
1086-
data={'format': 'csv'}
10871112
)
10881113

10891114
self.assertEqual(response.status_code, status.HTTP_200_OK)

0 commit comments

Comments
 (0)