diff --git a/src/etools/applications/attachments/tests/test_views.py b/src/etools/applications/attachments/tests/test_views.py index 5e441b9a18..d634756f6d 100644 --- a/src/etools/applications/attachments/tests/test_views.py +++ b/src/etools/applications/attachments/tests/test_views.py @@ -104,6 +104,7 @@ def assert_keys(self, response): "object_link", "filename", "file_type", + "file_type_id", "file_link", "uploaded_by", "created", diff --git a/src/etools/applications/audit/purchase_order/synchronizers.py b/src/etools/applications/audit/purchase_order/synchronizers.py index d22d0c277c..4b93ab2ebb 100644 --- a/src/etools/applications/audit/purchase_order/synchronizers.py +++ b/src/etools/applications/audit/purchase_order/synchronizers.py @@ -1,8 +1,6 @@ - from collections import OrderedDict from etools.applications.audit.purchase_order.models import AuditorFirm, PurchaseOrder, PurchaseOrderItem -from etools.applications.funds.models import Donor, Grant from etools.applications.vision.synchronizers import ManualVisionSynchronizer @@ -34,18 +32,8 @@ class POSynchronizer(ManualVisionSynchronizer): "name": "VENDOR_NAME", "country": "VENDOR_CTRY_NAME", }, - 'grant': { - "name": "GRANT_REF", - "expiry": "EXPIRY_DATE", - "donor": "DONOR_NAME" - }, - 'donor': { - "name": "DONOR_NAME", - } } MODEL_MAPPING = OrderedDict(( - ('donor', Donor), - ('grant', Grant), ('auditor_firm', AuditorFirm), ('purchase_order', PurchaseOrder), ('order_item', PurchaseOrderItem), diff --git a/src/etools/applications/audit/purchase_order/tests/test_synchronizers.py b/src/etools/applications/audit/purchase_order/tests/test_synchronizers.py index ad03a8b1a5..be69ab4f34 100644 --- a/src/etools/applications/audit/purchase_order/tests/test_synchronizers.py +++ b/src/etools/applications/audit/purchase_order/tests/test_synchronizers.py @@ -6,7 +6,6 @@ from etools.applications.audit.purchase_order.models import AuditorFirm, PurchaseOrder, PurchaseOrderItem from etools.applications.audit.purchase_order.tasks import update_purchase_orders from etools.applications.EquiTrack.tests.cases import BaseTenantTestCase -from etools.applications.funds.models import Donor, Grant from etools.applications.users.models import Country @@ -65,21 +64,15 @@ def test_save_records(self): number=self.data["PO_ITEM"] ) auditor_qs = AuditorFirm.objects.filter(name=self.data["VENDOR_NAME"]) - donor_qs = Donor.objects.filter(name=self.data["DONOR_NAME"]) - grant_qs = Grant.objects.filter(name=self.data["GRANT_REF"]) self.assertFalse(purchase_order_qs.exists()) self.assertFalse(purchase_order_item_qs.exists()) self.assertFalse(auditor_qs.exists()) - self.assertFalse(donor_qs.exists()) - self.assertFalse(grant_qs.exists()) self.adapter._save_records([self.data]) self.assertTrue(purchase_order_qs.exists()) self.assertTrue(purchase_order_item_qs.exists()) self.assertTrue(auditor_qs.exists()) - self.assertTrue(donor_qs.exists()) - self.assertTrue(grant_qs.exists()) class TestUpdatePurchaseOrders(BaseTenantTestCase): diff --git a/src/etools/applications/partners/serializers/prp_v1.py b/src/etools/applications/partners/serializers/prp_v1.py index 2800608bf8..5018ed4fcd 100644 --- a/src/etools/applications/partners/serializers/prp_v1.py +++ b/src/etools/applications/partners/serializers/prp_v1.py @@ -233,14 +233,13 @@ class PRPInterventionListSerializer(serializers.ModelSerializer): unicef_budget_cash = serializers.DecimalField(source='total_unicef_cash', read_only=True, max_digits=20, decimal_places=2) unicef_budget_currency = serializers.SerializerMethodField(read_only=True) - # TODO: update this after FR Validation changes, pending new Insight API changes. - expected_results = PRPResultSerializer(many=True, read_only=True, source='all_lower_results') + expected_results = serializers.SerializerMethodField() update_date = serializers.DateTimeField(source='modified') - reporting_requirements = ReportingRequirementsSerializer(many=True, read_only=True) + reporting_requirements = serializers.SerializerMethodField() special_reports = SpecialReportingRequirementsSerializer(source="special_reporting_requirements", many=True, read_only=True) - sections = SectionSerializer(source="combined_sections", many=True, read_only=True) + sections = SectionSerializer(many=True, read_only=True) locations = PRPLocationSerializer(source="flat_locations", many=True, read_only=True) def get_unicef_budget_currency(self, obj): @@ -255,14 +254,23 @@ def get_unicef_budget_currency(self, obj): def get_business_area_code(self, obj): return connection.tenant.business_area_code - def get_funds_received(self, obj): - return obj.total_frs['total_actual_amt'] + def get_reporting_requirements(self, obj): + if obj.status not in [Intervention.ACTIVE, ]: + return [] + return ReportingRequirementsSerializer(obj.reporting_requirements, many=True).data + + def get_expected_results(self, obj): + if obj.status not in [Intervention.ACTIVE, ]: + return [] + return PRPResultSerializer(obj.all_lower_results, many=True).data class Meta: model = Intervention fields = ( - 'id', 'title', 'business_area_code', - 'offices', # todo: convert to names, not ids + 'id', + 'title', + 'business_area_code', + 'offices', 'number', 'status', 'partner_org', diff --git a/src/etools/applications/partners/tests/data/prp-intervention-list.json b/src/etools/applications/partners/tests/data/prp-intervention-list.json index 1c31aebfb4..32cfecc0d3 100644 --- a/src/etools/applications/partners/tests/data/prp-intervention-list.json +++ b/src/etools/applications/partners/tests/data/prp-intervention-list.json @@ -89,103 +89,5 @@ "id": 642, "cso_budget": "0.00", "update_date": "2017-10-12T08:10:43.708677Z" - }, - { - "expected_results": [], - "unicef_focal_points": [], - "end_date": null, - "reporting_requirements": [], - "sections": [], - "agreement": "PCA20172036", - "agreement_auth_officers": [], - "amendments": [], - "business_area_code": "ZZZ", - "title": "Intervention 2", - "unicef_budget": "0.00", - "unicef_budget_cash": "0.00", - "unicef_budget_supplies": "0.00", - "unicef_budget_currency": "", - "number": "/PCA2017393/PD2017641", - "start_date": null, - "focal_points": [], - "locations": [], - "offices": [], - "partner_org": { - "id": 0, - "address": null, - "partner_type": "", - "cso_type": null, - "total_ct_cp": null, - "total_ct_cy": null, - "short_name": "", - "alternate_name": null, - "basis_for_risk_rating": "", - "city": null, - "core_values_assessment_date": null, - "country": null, - "email": null, - "last_assessment_date": null, - "name": "Partner 1", - "phone_number": null, - "postal_code": null, - "rating": null, - "street_address": null, - "type_of_assessment": null - }, - "special_reports": [], - "status": "draft", - "cso_budget_currency": "", - "id": 641, - "cso_budget": "0.00", - "update_date": "2017-10-12T08:10:43.708677Z" - }, - { - "expected_results": [], - "unicef_focal_points": [], - "end_date": null, - "reporting_requirements": [], - "sections": [], - "agreement": "PCA20172036", - "agreement_auth_officers": [], - "amendments": [], - "business_area_code": "ZZZ", - "title": "Intervention 1", - "unicef_budget": "110.00", - "unicef_budget_cash": "100.00", - "unicef_budget_supplies": "10.00", - "unicef_budget_currency": "", - "number": "/PCA2017393/2017640", - "start_date": null, - "focal_points": [], - "locations": [], - "offices": [], - "partner_org": { - "id": 0, - "address": null, - "partner_type": "", - "cso_type": null, - "total_ct_cp": null, - "total_ct_cy": null, - "short_name": "", - "alternate_name": null, - "basis_for_risk_rating": "", - "city": null, - "core_values_assessment_date": null, - "country": null, - "email": null, - "last_assessment_date": null, - "name": "Partner 1", - "phone_number": null, - "postal_code": null, - "rating": null, - "street_address": null, - "type_of_assessment": null - }, - "special_reports": [], - "status": "draft", - "cso_budget_currency": "", - "id": 640, - "cso_budget": "200.00", - "update_date": "2017-10-12T08:10:43.708677Z" } ] diff --git a/src/etools/applications/partners/tests/test_api_prp.py b/src/etools/applications/partners/tests/test_api_prp.py index e7f86f7f06..2e72612d49 100644 --- a/src/etools/applications/partners/tests/test_api_prp.py +++ b/src/etools/applications/partners/tests/test_api_prp.py @@ -107,10 +107,10 @@ def test_prp_api_modified_queries(self): tomorrow = (timezone.now() + datetime.timedelta(days=1)).isoformat() checks = [ ({'updated_before': yesterday}, 0), - ({'updated_before': tomorrow}, 3), - ({'updated_after': yesterday}, 3), + ({'updated_before': tomorrow}, 1), + ({'updated_after': yesterday}, 1), ({'updated_after': tomorrow}, 0), - ({'updated_before': tomorrow, 'updated_after': yesterday}, 3), + ({'updated_before': tomorrow, 'updated_after': yesterday}, 1), ] for params, expected_results in checks: status_code, response = self.run_prp_v1( diff --git a/src/etools/applications/partners/views/prp_v1.py b/src/etools/applications/partners/views/prp_v1.py index cb61093f63..720ad1e23c 100644 --- a/src/etools/applications/partners/views/prp_v1.py +++ b/src/etools/applications/partners/views/prp_v1.py @@ -57,16 +57,18 @@ def get_queryset(self, format=None): 'result_links__ll_results', 'result_links__ll_results__applied_indicators__indicator', 'result_links__ll_results__applied_indicators__disaggregation__disaggregation_values', - 'result_links__ll_results__applied_indicators__locations__gateway', + 'result_links__ll_results__applied_indicators__locations', 'special_reporting_requirements', 'reporting_requirements', 'frs', 'partner_focal_points', 'unicef_focal_points', 'agreement__authorized_officers', + 'agreement__partner', 'amendments', - 'flat_locations' - ) + 'flat_locations', + 'sections' + ).exclude(status=Intervention.DRAFT) query_params = self.request.query_params workspace = query_params.get('workspace', None) diff --git a/src/etools/applications/tpm/serializers/attachments.py b/src/etools/applications/tpm/serializers/attachments.py index 715a6c99ee..f41eb0075f 100644 --- a/src/etools/applications/tpm/serializers/attachments.py +++ b/src/etools/applications/tpm/serializers/attachments.py @@ -1,8 +1,9 @@ from django.utils.translation import ugettext as _ +from rest_framework import serializers from unicef_attachments.fields import FileTypeModelChoiceField -from unicef_attachments.models import FileType -from unicef_attachments.serializers import BaseAttachmentSerializer +from unicef_attachments.models import AttachmentLink, FileType +from unicef_attachments.serializers import AttachmentLinkSerializer, BaseAttachmentSerializer class TPMPartnerAttachmentsSerializer(BaseAttachmentSerializer): @@ -57,3 +58,17 @@ class Meta(BaseAttachmentSerializer.Meta): def create(self, validated_data): validated_data['code'] = 'visit_attachments' return super().create(validated_data) + + +class TPMActivityAttachmentLinkSerializer(serializers.Serializer): + attachments = AttachmentLinkSerializer(many=True, allow_empty=False) + + def create(self, validated_data): + links = [] + for attachment in validated_data["attachments"]: + links.append(AttachmentLink.objects.create( + attachment=attachment["attachment"], + content_type=self.context["content_type"], + object_id=self.context["object_id"], + )) + return {"attachments": links} diff --git a/src/etools/applications/tpm/tests/test_views.py b/src/etools/applications/tpm/tests/test_views.py index 140ec23119..640d84838a 100644 --- a/src/etools/applications/tpm/tests/test_views.py +++ b/src/etools/applications/tpm/tests/test_views.py @@ -1,22 +1,22 @@ - from datetime import datetime from django.core.files.uploadedfile import SimpleUploadedFile from django.urls import reverse from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from factory import fuzzy +from factory import fuzzy from rest_framework import status +from unicef_attachments.models import AttachmentLink from etools.applications.action_points.tests.factories import ActionPointFactory -from etools.applications.attachments.tests.factories import AttachmentFileTypeFactory, AttachmentFactory +from etools.applications.attachments.tests.factories import AttachmentFactory, AttachmentFileTypeFactory from etools.applications.EquiTrack.tests.cases import BaseTenantTestCase from etools.applications.partners.models import PartnerType from etools.applications.reports.tests.factories import SectionFactory -from etools.applications.tpm.models import TPMVisit, ThirdPartyMonitor +from etools.applications.tpm.models import ThirdPartyMonitor, TPMVisit from etools.applications.tpm.tests.base import TPMTestCaseMixin -from etools.applications.tpm.tests.factories import TPMPartnerFactory, TPMVisitFactory, UserFactory, _FUZZY_END_DATE +from etools.applications.tpm.tests.factories import _FUZZY_END_DATE, TPMPartnerFactory, TPMVisitFactory, UserFactory from etools.applications.utils.common.tests.test_utils import TestExportMixin @@ -761,3 +761,38 @@ def test_add(self): ) self.assertEqual(list_response.status_code, status.HTTP_200_OK) self.assertEqual(len(list_response.data['results']), attachments_num + 1) + + +class TestActivityAttachmentLinkView(TPMTestCaseMixin, BaseTenantTestCase): + @classmethod + def setUpTestData(cls): + super().setUpTestData() + + cls.visit = TPMVisitFactory( + status='draft', + tpm_partner=cls.tpm_user.tpmpartners_tpmpartnerstaffmember.tpm_partner, + tpm_partner_focal_points=[cls.tpm_user.tpmpartners_tpmpartnerstaffmember], + tpm_activities__count=1 + ) + cls.activity = cls.visit.tpm_activities.first() + partner = TPMPartnerFactory() + cls.attachment = AttachmentFactory(content_object=partner) + + def test_add(self): + links_qs = AttachmentLink.objects + self.assertEqual(links_qs.count(), 0) + create_response = self.forced_auth_req( + 'post', + reverse('tpm:activity-links', args=[self.activity.pk]), + user=self.pme_user, + data={'attachments': [{'attachment': self.attachment.pk}]} + ) + self.assertEqual(create_response.status_code, status.HTTP_201_CREATED) + + list_response = self.forced_auth_req( + 'get', + reverse('tpm:activity-links', args=[self.activity.pk]), + user=self.pme_user + ) + self.assertEqual(list_response.status_code, status.HTTP_200_OK) + self.assertEqual(links_qs.count(), 1) diff --git a/src/etools/applications/tpm/urls.py b/src/etools/applications/tpm/urls.py index 971f39bcee..601c04703c 100644 --- a/src/etools/applications/tpm/urls.py +++ b/src/etools/applications/tpm/urls.py @@ -4,6 +4,7 @@ from unicef_restlib.routers import NestedComplexRouter from etools.applications.tpm.views import ( + ActivityAttachmentLinksView, ActivityAttachmentsViewSet, ActivityReportAttachmentsViewSet, PartnerAttachmentsViewSet, @@ -36,7 +37,6 @@ base_name='activity-attachments') visit_attachments_api.register('activities/report-attachments', ActivityReportAttachmentsViewSet, base_name='activity-report-attachments') - tpm_action_points_api = NestedComplexRouter(tpm_visits_api, r'visits', lookup='tpm_activity__tpm_visit') tpm_action_points_api.register(r'action-points', TPMActionPointViewSet, base_name='action-points') @@ -49,4 +49,9 @@ url(r'^', include(tpm_action_points_api.urls)), url(r'^', include(visit_attachments_api.urls)), url(r'^', include(tpm_visits_api.urls)), + url( + r'^visits/activities/(?P\d+)/links', + view=ActivityAttachmentLinksView.as_view(), + name='activity-links' + ) ] diff --git a/src/etools/applications/tpm/views.py b/src/etools/applications/tpm/views.py index 93a4a33d49..746e61f46d 100644 --- a/src/etools/applications/tpm/views.py +++ b/src/etools/applications/tpm/views.py @@ -8,10 +8,12 @@ from easy_pdf.rendering import render_to_pdf_response from rest_framework import generics, mixins, viewsets from rest_framework.decorators import action +from rest_framework.exceptions import NotFound from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response -from unicef_attachments.models import Attachment +from unicef_attachments.models import Attachment, AttachmentLink +from unicef_attachments.serializers import AttachmentLinkSerializer from unicef_restlib.pagination import DynamicPageNumberPagination from unicef_restlib.views import MultiSerializerViewSetMixin, NestedViewSetMixin, SafeTenantViewSetMixin @@ -57,6 +59,7 @@ from etools.applications.tpm.serializers.attachments import ( ActivityAttachmentsSerializer, ActivityReportSerializer, + TPMActivityAttachmentLinkSerializer, TPMPartnerAttachmentsSerializer, TPMVisitAttachmentsSerializer, TPMVisitReportAttachmentsSerializer, @@ -623,3 +626,46 @@ def get_parent_filter(self): def perform_create(self, serializer): serializer.save(content_type=ContentType.objects.get_for_model(TPMActivity)) + + +class ActivityAttachmentLinksView(generics.ListCreateAPIView): + metadata_class = PermissionBasedMetadata + serializer_class = TPMActivityAttachmentLinkSerializer + permission_classes = [IsAuthenticated] + + def set_content_object(self): + try: + self.content_type = ContentType.objects.get_by_natural_key( + "tpm", + "tpmactivity", + ) + except ContentType.DoesNotExist: + raise NotFound() + + try: + self.object_id = self.kwargs.get("object_pk") + model_cls = self.content_type.model_class() + self.content_object = model_cls.objects.get( + pk=self.object_id + ) + except model_cls.DoesNotExist: + raise NotFound() + + def get_serializer_context(self): + self.set_content_object() + context = super().get_serializer_context() + context["content_type"] = self.content_type + context["object_id"] = self.object_id + return context + + def get_queryset(self): + self.set_content_object() + return AttachmentLink.objects.filter( + content_type=self.content_type, + object_id=self.object_id, + ) + + def list(self, request, *args, **kwargs): + queryset = self.get_queryset() + serializer = AttachmentLinkSerializer(queryset, many=True) + return Response(serializer.data) diff --git a/src/etools/assets/partner/intervention_permissions.csv b/src/etools/assets/partner/intervention_permissions.csv index f3a904b1d3..561104a048 100644 --- a/src/etools/assets/partner/intervention_permissions.csv +++ b/src/etools/assets/partner/intervention_permissions.csv @@ -51,7 +51,6 @@ Field no,Field Name,Group,Condition,Status,Action,Allowed 3.2.8,offices,Partnership Manager,user_adds_amendment,*,Edit,TRUE 3.2.9,unicef_focal_points,Partnership Manager,user_adds_amendment,*,Edit,TRUE 3.3.1,country_programme,Partnership Manager,user_adds_amendment,*,Edit,TRUE -3.3.7,contingency_pd,Partnership Manager,user_adds_amendment,*,Edit,TRUE 3.3.3,end,Partnership Manager,user_adds_amendment,*,Edit,TRUE 3.3.5,sections,Partnership Manager,user_adds_amendment,*,Edit,TRUE 3.4.1,result_links,Partnership Manager,user_adds_amendment+prp_mode_on,*,Edit,TRUE diff --git a/src/requirements/base.txt b/src/requirements/base.txt index 6ee2b551be..f88793f2fc 100644 --- a/src/requirements/base.txt +++ b/src/requirements/base.txt @@ -48,6 +48,7 @@ djangorestframework-recursive==0.1.2 djangorestframework-xml==1.3 djangorestframework==3.8.2 drf-nested-routers==0.90.2 +drf-querystringfilter==1.0.0 # via unicef-attachments drfpasswordless==1.2 et-xmlfile==1.0.1 # via openpyxl etools-validator==0.3.2 @@ -82,7 +83,7 @@ pysaml2==4.4.0 python-dateutil==2.5.3 # via azure-storage, pyrestcli, pysaml2 pytz==2018.3 # via babel, celery, etools-validator, flower, pysaml2, unicef-attachments, unicef-djangolib, unicef-restlib pyyaml==3.12 -raven==6.2.1 +raven==6.9 redis==2.10.6 # via django-redis-cache reportlab==3.3.0 # via django-easy-pdf, xhtml2pdf repoze.who==2.3 # via pysaml2 @@ -94,7 +95,7 @@ static3==0.7.0 # via dj-static tablib==0.12.1 # via django-import-export tenant-schemas-celery==0.1.7 tornado==4.2.0 # via flower -unicef-attachments==0.3.0 +unicef-attachments==0.4.1 unicef-djangolib==0.5 unicef-locations==1.4.1 unicef_notification==0.2.0 diff --git a/src/requirements/input/base.in b/src/requirements/input/base.in index 9ccbd96af5..0e234f8f06 100644 --- a/src/requirements/input/base.in +++ b/src/requirements/input/base.in @@ -39,9 +39,9 @@ drfpasswordless==1.2 newrelic==2.94.0.79 pyyaml==3.12 psycopg2==2.7.5 -raven==6.2.1 +raven==6.9 tenant-schemas-celery==0.1.7 -unicef-attachments==0.3.0 +unicef-attachments==0.4.1 unicef-djangolib==0.5 unicef-locations==1.4.1 unicef_notification==0.2.0 diff --git a/src/requirements/test.txt b/src/requirements/test.txt index dafaf30cdd..c22374b77e 100644 --- a/src/requirements/test.txt +++ b/src/requirements/test.txt @@ -56,6 +56,7 @@ djangorestframework==3.8.2 docutils==0.14 # via sphinx drf-api-checker==0.3.0 drf-nested-routers==0.90.2 +drf-querystringfilter==1.0.0 drfpasswordless==1.2 et-xmlfile==1.0.1 etools-validator==0.3.2 @@ -119,7 +120,7 @@ pysaml2==4.4.0 python-dateutil==2.5.3 pytz==2018.3 pyyaml==3.12 -raven==6.2.1 +raven==6.9 redis==2.10.6 reportlab==3.3.0 repoze.who==2.3 @@ -139,7 +140,7 @@ text-unidecode==1.2 # via faker tornado==4.2.0 tox==3.0.0 traitlets==4.3.2 # via ipython -unicef-attachments==0.3.0 +unicef-attachments==0.4.1 unicef-djangolib==0.5 unicef-locations==1.4.1 unicef_notification==0.2.0