diff --git a/akvo/api/resources/partnership.py b/akvo/api/resources/partnership.py index 0983d6daa5..40915b7e02 100644 --- a/akvo/api/resources/partnership.py +++ b/akvo/api/resources/partnership.py @@ -31,7 +31,7 @@ FIELD_NAME = 'name' FIELD_LONG_NAME = 'long_name' FIELD_NEW_ORGANISATION_TYPE = 'new_organisation_type' -FIELD_PARTNER_TYPE = 'partner_type' +FIELD_PARTNER_TYPE = 'iati_organisation_role' ORG_FIELDS = [FIELD_IATI_ORG_ID, FIELD_NAME, FIELD_LONG_NAME, FIELD_NEW_ORGANISATION_TYPE] # InternalOrganisationID FIELD_INTERNAL_ORG_ID = 'internal_org_id' @@ -173,7 +173,7 @@ def hydrate(self, bundle): bundle.data[FIELD_ORGANISATION] = organisation if ( organisation.iati_org_id != bundle.data[FIELD_REPORTING_ORG] or - bundle.data[FIELD_PARTNER_TYPE] != Partnership.SUPPORT_PARTNER + bundle.data[FIELD_PARTNER_TYPE] != Partnership.IATI_ACCOUNTABLE_PARTNER ): bundle.data[FIELD_IATI_ACTIVITY_ID] = None bundle.data[FIELD_INTERNAL_ID] = None @@ -197,15 +197,48 @@ def hydrate_organisation(self, bundle): class PartnershipResource(ConditionalFullResource): organisation = ConditionalFullToOneField('akvo.api.resources.OrganisationResource', 'organisation') project = ConditionalFullToOneField('akvo.api.resources.ProjectResource', 'project') + partner_type = fields.CharField(attribute='iati_role_to_partner_type') def __init__(self, api_name=None): """ override to be able to create custom help_text on the partner_type field """ super(PartnershipResource, self).__init__(api_name=None) - self.fields['partner_type'].help_text = "Uses the following key-value pair list: {%s}" % ', '.join( - ['"%s": "%s"' % (k, v) for k, v in Partnership.PARTNER_TYPES] + self.fields[ + 'iati_organisation_role'].help_text = "Uses the following key-value pair list: {%s}" % ', '.join( + ['"%s": "%s"' % (k, v) for k, v in Partnership.IATI_ROLES] ) + def apply_filters(self, request, applicable_filters): + """ Custom filtering for the "fake" field partner_type allowing the filtering of the + resource using the deprecated Akvo partner type values + + Uses Partnership.PARTNER_TYPES_TO_ROLES_MAP map the filter to the + Partnership.iati_organisation_role field + """ + + partner_type_filter = {} + if 'iati_role_to_partner_type' in [k.split('__')[0] for k in applicable_filters.keys()]: + for k in applicable_filters.keys(): + if k.startswith('iati_role_to_partner_type'): + operator = k.split('__')[1] + if operator == 'in': + values = [ + Partnership.PARTNER_TYPES_TO_ROLES_MAP[v] for v in applicable_filters[k] + ] + partner_type_filter['iati_organisation_role__in'] = values + else: + partner_type_filter[ + "iati_organisation_role__{}".format(operator) + ] = Partnership.PARTNER_TYPES_TO_ROLES_MAP[applicable_filters[k]] + applicable_filters.pop(k) + + default_filtering = super(PartnershipResource, self).apply_filters(request, applicable_filters) + + if partner_type_filter: + return default_filtering.filter(**partner_type_filter) + else: + return default_filtering + class Meta: max_limit = 10 allowed_methods = ['get'] @@ -214,10 +247,11 @@ class Meta: filtering = dict(organisation=ALL_WITH_RELATIONS) filtering = dict( # other fields - iati_activity_id = ALL, - internal_id = ALL, - partner_type = ALL, + iati_activity_id = ALL, + internal_id = ALL, + partner_type = ALL, + iati_organisation_role = ALL, # foreign keys - organisation = ALL_WITH_RELATIONS, - project = ALL_WITH_RELATIONS, + organisation = ALL_WITH_RELATIONS, + project = ALL_WITH_RELATIONS, ) diff --git a/akvo/api/resources/project.py b/akvo/api/resources/project.py index a84ca95b76..85f3a72769 100644 --- a/akvo/api/resources/project.py +++ b/akvo/api/resources/project.py @@ -216,7 +216,7 @@ def alter_deserialized_detail_data(self, request, data): reporting_org=temp_org['reporting_org'], name='Incorrect business unit', #this should never be used, if it is the lookup of existing BUs is borked long_name='Incorrect business unit', - partner_type='sponsor', + iati_organisation_role=100, # old partner type sponsor partner new_organisation_type='21', organisation=None, )] diff --git a/akvo/api/xml/iati-xslt.xsl b/akvo/api/xml/iati-xslt.xsl index 32d20a2a0e..5eb016e6d2 100644 --- a/akvo/api/xml/iati-xslt.xsl +++ b/akvo/api/xml/iati-xslt.xsl @@ -524,16 +524,16 @@ - support + 2 - support + 3 - funding + 1 - field + 4 diff --git a/akvo/cordaid_org_importer.py b/akvo/cordaid_org_importer.py index 4a273cf87c..901004ffa6 100644 --- a/akvo/cordaid_org_importer.py +++ b/akvo/cordaid_org_importer.py @@ -16,7 +16,7 @@ from django.core.files.temp import NamedTemporaryFile from akvo.codelists.store.codelists_v201 import ORGANISATION_TYPE as IATI_LIST_ORGANISATION_TYPE -from akvo.rsr.models import InternalOrganisationID, Organisation, PartnerType +from akvo.rsr.models import InternalOrganisationID, Organisation from akvo.utils import model_and_instance_based_filename @@ -95,8 +95,6 @@ def import_orgs(xml_file): identifier=identifier ) internal_org_id.save() - for partner_type in PartnerType.objects.all(): - referenced_org.partner_types.add(partner_type) except Exception, e: action = "failed" internal_org_id.delete() diff --git a/akvo/iati/checks/v201.py b/akvo/iati/checks/v201.py index 45403c761f..07abcb6262 100644 --- a/akvo/iati/checks/v201.py +++ b/akvo/iati/checks/v201.py @@ -109,15 +109,16 @@ def partners(self): if partnership.organisation: org = partnership.organisation org_name = org.long_name or org.name - if partnership.partner_type and (org.iati_org_id or org_name): + if (partnership.iati_organisation_role and + partnership.iati_organisation_role < 100 and (org.iati_org_id or org_name)): valid_partner = True - if not partnership.partner_type: + if not partnership.iati_organisation_role: checks.append((u'error', u'missing role for partner %s' % org_name)) if not org.iati_org_id: checks.append((u'warning', u'partner %s has no IATI identifier' % org_name)) if not org_name: - checks.append((u'warning', u'%s partner has no organisation ' - u'name' % partnership.partner_type)) + checks.append((u'warning', u'%s has no organisation ' + u'name' % partnership.iati_organisation_role)) else: checks.append((u'error', u'partnership has no organisation')) diff --git a/akvo/iati/elements/participating_org.py b/akvo/iati/elements/participating_org.py index 925320726f..a8346acdb7 100644 --- a/akvo/iati/elements/participating_org.py +++ b/akvo/iati/elements/participating_org.py @@ -23,6 +23,8 @@ def participating_org(project): """ partnership_elements = [] + from akvo.rsr.models import Partnership + for partnership in project.partnerships.all(): org = partnership.organisation element = etree.Element("participating-org") @@ -32,9 +34,9 @@ def participating_org(project): if org.new_organisation_type: element.attrib['type'] = str(org.new_organisation_type) - - if partnership.partner_type in TYPE_TO_CODE.keys(): - element.attrib['role'] = TYPE_TO_CODE[partnership.partner_type] + # don't include old akvo sponsor partner value when checking + if partnership.iati_organisation_role in Partnership.IATI_ROLE_LIST[:-1]: + element.attrib['role'] = str(partnership.iati_organisation_role) narrative_element = etree.SubElement(element, "narrative") diff --git a/akvo/rest/rsr_api_upload/json/organisation_1.json b/akvo/rest/rsr_api_upload/json/organisation_1.json index 700f29c6fc..b2010ddd18 100644 --- a/akvo/rest/rsr_api_upload/json/organisation_1.json +++ b/akvo/rest/rsr_api_upload/json/organisation_1.json @@ -18,11 +18,6 @@ "content_owner": null, "allow_edit": true, "primary_location": null, - "partner_types": [ - "field", - "funding", - "sponsor" - ], "internal_org_ids": [] }, "organisation_location": { diff --git a/akvo/rest/rsr_api_upload/json/project_1.json b/akvo/rest/rsr_api_upload/json/project_1.json index 511d59dd7c..358f7c1ccf 100644 --- a/akvo/rest/rsr_api_upload/json/project_1.json +++ b/akvo/rest/rsr_api_upload/json/project_1.json @@ -98,7 +98,7 @@ }, "partnership": { "organisation": "Akvo", - "partner_type": "field", + "iati_organisation_role": "4", "funding_amount": null, "partner_type_extra": null, "iati_activity_id": null, @@ -107,7 +107,7 @@ }, "partnership": { "organisation": "Akvo", - "partner_type": "funding", + "iati_organisation_role": "1", "funding_amount": 4711, "partner_type_extra": null, "iati_activity_id": null, diff --git a/akvo/rest/serializers/__init__.py b/akvo/rest/serializers/__init__.py index 1215db53e4..c4cb7871a2 100644 --- a/akvo/rest/serializers/__init__.py +++ b/akvo/rest/serializers/__init__.py @@ -27,7 +27,6 @@ from .organisation_location import (OrganisationLocationSerializer, MapOrganisationLocationSerializer) from .partner_site import PartnerSiteSerializer -from .partner_type import PartnerTypeSerializer from .partnership import PartnershipSerializer from .planned_disbursement import PlannedDisbursementSerializer from .policy_marker import PolicyMarkerSerializer @@ -82,7 +81,6 @@ 'OrganisationLocationSerializer', 'PartnershipSerializer', 'PartnerSiteSerializer', - 'PartnerTypeSerializer', 'PlannedDisbursementSerializer', 'PolicyMarkerSerializer', 'ProjectCommentSerializer', diff --git a/akvo/rest/serializers/partner_type.py b/akvo/rest/serializers/partner_type.py deleted file mode 100644 index a3d123fc05..0000000000 --- a/akvo/rest/serializers/partner_type.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- - -# Akvo RSR 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 >. - - -from akvo.rsr.models import PartnerType - -from .rsr_serializer import BaseRSRSerializer - - -class PartnerTypeSerializer(BaseRSRSerializer): - - class Meta: - model = PartnerType diff --git a/akvo/rest/serializers/partnership.py b/akvo/rest/serializers/partnership.py index 2ba83769dd..90f38d0c5f 100644 --- a/akvo/rest/serializers/partnership.py +++ b/akvo/rest/serializers/partnership.py @@ -4,6 +4,7 @@ # 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 >. +from rest_framework import serializers from akvo.rsr.models import Partnership @@ -12,5 +13,7 @@ class PartnershipSerializer(BaseRSRSerializer): + partner_type = serializers.Field(source='iati_role_to_partner_type') + class Meta: model = Partnership diff --git a/akvo/rest/serializers/project_update_location.py b/akvo/rest/serializers/project_update_location.py index 0bd1f624f8..dc1766e81a 100644 --- a/akvo/rest/serializers/project_update_location.py +++ b/akvo/rest/serializers/project_update_location.py @@ -18,6 +18,9 @@ class Meta: class ProjectUpdateLocationExtraSerializer(ProjectUpdateLocationSerializer): + # Limit update data to its PK, this is needed because of Meta.depth = 2 + location_target = serializers.Field(source='location_target.pk') + class Meta(ProjectUpdateLocationSerializer.Meta): depth = 2 diff --git a/akvo/rest/urls.py b/akvo/rest/urls.py index 23f7bf10fb..425277c909 100644 --- a/akvo/rest/urls.py +++ b/akvo/rest/urls.py @@ -36,7 +36,6 @@ router.register(r'organisation_location', views.OrganisationLocationViewSet) router.register(r'organisation_map_location', views.MapOrganisationLocationViewSet) router.register(r'partner_site', views.PartnerSiteViewSet) -router.register(r'partner_type', views.PartnerTypeViewSet) router.register(r'partnership', views.PartnershipViewSet) router.register(r'planned_disbursement', views.PlannedDisbursementViewSet) router.register(r'policy_marker', views.PolicyMarkerViewSet) diff --git a/akvo/rest/views/__init__.py b/akvo/rest/views/__init__.py index 38977b6865..e462ea814c 100644 --- a/akvo/rest/views/__init__.py +++ b/akvo/rest/views/__init__.py @@ -26,7 +26,6 @@ from .organisation import OrganisationViewSet from .organisation_location import OrganisationLocationViewSet, MapOrganisationLocationViewSet from .partner_site import PartnerSiteViewSet -from .partner_type import PartnerTypeViewSet from .partnership import PartnershipViewSet from .planned_disbursement import PlannedDisbursementViewSet from .policy_marker import PolicyMarkerViewSet @@ -98,7 +97,6 @@ 'OrganisationCustomFieldViewSet', 'PartnershipViewSet', 'PartnerSiteViewSet', - 'PartnerTypeViewSet', 'PlannedDisbursementViewSet', 'PolicyMarkerViewSet', 'ProjectCommentViewSet', diff --git a/akvo/rest/views/internal_organisation_id.py b/akvo/rest/views/internal_organisation_id.py index 40a9603720..4adf3f5be9 100644 --- a/akvo/rest/views/internal_organisation_id.py +++ b/akvo/rest/views/internal_organisation_id.py @@ -18,19 +18,3 @@ class InternalOrganisationIDViewSet(BaseRSRViewSet): serializer_class = InternalOrganisationIDSerializer queryset = InternalOrganisationID.objects.all() filter_fields = ('recording_org', 'referenced_org', 'identifier', ) - - def get_queryset(self): - """ - filter on recording org and/or identifier - """ - queryset = self.queryset - recording_org = self.request.QUERY_PARAMS.get('recording_org', None) - identifier = self.request.QUERY_PARAMS.get('identifier', None) - filter_params = {} - if recording_org is not None: - filter_params.update(recording_org__id=recording_org) - if identifier is not None: - filter_params.update(identifier=identifier) - if filter: - queryset = queryset.filter(**filter_params) - return queryset \ No newline at end of file diff --git a/akvo/rest/views/organisation.py b/akvo/rest/views/organisation.py index e78b6e835c..f4fc116bcd 100644 --- a/akvo/rest/views/organisation.py +++ b/akvo/rest/views/organisation.py @@ -71,24 +71,3 @@ class OrganisationViewSet(BaseRSRViewSet): serializer_class = OrganisationSerializer parser_classes = (AkvoOrganisationParser, JSONParser,) filter_fields = ('name', 'long_name', 'iati_org_id', 'content_owner') - - def get_queryset(self): - """ Enable filtering of Organisations on iati_org_id or name - """ - queryset = super(OrganisationViewSet, self).get_queryset() - pk = self.request.QUERY_PARAMS.get('id', None) - if pk is not None: - try: - queryset = queryset.filter(pk=pk) - except ValueError: - pass - iati_org_id = self.request.QUERY_PARAMS.get('iati_org_id', None) - if iati_org_id is not None: - queryset = queryset.filter(iati_org_id=iati_org_id) - name = self.request.QUERY_PARAMS.get('name', None) - if name is not None: - queryset = queryset.filter(name=name) - long_name = self.request.QUERY_PARAMS.get('long_name', None) - if long_name is not None: - queryset = queryset.filter(long_name=long_name) - return queryset diff --git a/akvo/rest/views/partner_type.py b/akvo/rest/views/partner_type.py deleted file mode 100644 index 3bcd022fd2..0000000000 --- a/akvo/rest/views/partner_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- - -# Akvo RSR 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 >. - - -from akvo.rsr.models import PartnerType - -from ..serializers import PartnerTypeSerializer -from ..viewsets import BaseRSRViewSet - - -class PartnerTypeViewSet(BaseRSRViewSet): - """ - """ - queryset = PartnerType.objects.all() - serializer_class = PartnerTypeSerializer diff --git a/akvo/rest/views/partnership.py b/akvo/rest/views/partnership.py index 8fdc4c9405..9e151c4828 100644 --- a/akvo/rest/views/partnership.py +++ b/akvo/rest/views/partnership.py @@ -16,4 +16,14 @@ class PartnershipViewSet(BaseRSRViewSet): """ queryset = Partnership.objects.all() serializer_class = PartnershipSerializer - filter_fields = ('project', 'organisation', 'partner_type', ) + filter_fields = ('project', 'organisation', 'iati_organisation_role', ) + + def get_queryset(self): + """Allow filtering on partner_type.""" + queryset = self.queryset + partner_type = self.request.QUERY_PARAMS.get('partner_type', None) + if partner_type and partner_type in Partnership.PARTNER_TYPES_TO_ROLES_MAP.keys(): + queryset = queryset.filter( + iati_organisation_role=Partnership.PARTNER_TYPES_TO_ROLES_MAP[partner_type] + ) + return queryset diff --git a/akvo/rest/views/project_editor.py b/akvo/rest/views/project_editor.py index 634c47c0fe..4ba25e1bd0 100644 --- a/akvo/rest/views/project_editor.py +++ b/akvo/rest/views/project_editor.py @@ -80,7 +80,7 @@ PARTNER_FIELDS = ( ('organisation', 'value-partner-', 'related-object'), - ('partner_type', 'partner-type-', 'text'), + ('iati_organisation_role', 'partner-role-', 'integer'), ('funding_amount', 'funding-amount-', 'decimal'), ) @@ -487,6 +487,7 @@ def log_addition(obj, user): change_message=change_message ) + @api_view(['POST']) @permission_classes((IsAuthenticated, )) def project_editor_delete_document(request, project_pk=None, document_pk=None): diff --git a/akvo/rest/views/project_update.py b/akvo/rest/views/project_update.py index 38019fe90e..94316ee75f 100644 --- a/akvo/rest/views/project_update.py +++ b/akvo/rest/views/project_update.py @@ -19,25 +19,45 @@ class ProjectUpdateViewSet(BaseRSRViewSet): queryset = ProjectUpdate.objects.select_related('project', 'user').prefetch_related('locations') serializer_class = ProjectUpdateSerializer - filter_fields = ('project', 'user', ) + filter_fields = { + 'project': ['exact', ], + 'user': ['exact', ], + 'uuid': ['exact', 'icontains', ], + # These filters only accept a date, not a datetime + # 'created_at': ['exact', 'gt', 'gte', 'lt', 'lte', ], + # 'last_modified_at': ['exact', 'gt', 'gte', 'lt', 'lte', ], + } + + # filter_fields = ('project', 'user', ) paginate_by_param = 'limit' max_paginate_by = 1000 def get_queryset(self): - """Allow simple filtering on selected fields.""" + """ + Allow simple filtering on selected fields. + We don't use the default filter_fields, because Up filters on + datetime for last_modified_at, and they only support a date, not datetime. + """ queryset = self.queryset - project = self.request.QUERY_PARAMS.get('project', None) - if project is not None: - queryset = self.queryset.filter(project=project) - uuid = self.request.QUERY_PARAMS.get('uuid', None) - if uuid is not None: - queryset = self.queryset.filter(uuid=uuid) - created_at = self.request.QUERY_PARAMS.get('created_at__gt', None) - if created_at is not None: - queryset = self.queryset.filter(created_at__gt=created_at) - last_modified_at = self.request.QUERY_PARAMS.get('last_modified_at__gt', None) - if last_modified_at is not None: - queryset = self.queryset.filter(last_modified_at__gt=last_modified_at) + created_at__gt = self.request.QUERY_PARAMS.get('created_at__gt', None) + if created_at__gt is not None: + queryset = queryset.filter(created_at__gt=created_at__gt) + created_at__lt = self.request.QUERY_PARAMS.get('created_at__lt', None) + if created_at__lt is not None: + queryset = queryset.filter(created_at__lt=created_at__lt) + last_modified_at__gt = self.request.QUERY_PARAMS.get('last_modified_at__gt', None) + if last_modified_at__gt is not None: + queryset = queryset.filter(last_modified_at__gt=last_modified_at__gt) + last_modified_at__lt = self.request.QUERY_PARAMS.get('last_modified_at__lt', None) + if last_modified_at__lt is not None: + queryset = queryset.filter(last_modified_at__lt=last_modified_at__lt) + # Get updates per organisation + project__partners = self.request.QUERY_PARAMS.get('project__partners', None) + if project__partners: + queryset = queryset.filter(project__partners=project__partners) + user__organisations = self.request.QUERY_PARAMS.get('user__organisations', None) + if user__organisations: + queryset = queryset.filter(user__organisations=user__organisations) return queryset @@ -61,31 +81,47 @@ class ProjectUpdateExtraViewSet(BaseRSRViewSet): 'user__organisation__primary_location', 'user__organisation__primary_location__country', 'user__organisation__primary_location__location_target', - 'user__organisation__primary_location__location_target__partner_types', 'user__organisation__primary_location__location_target__internal_org_ids', ).prefetch_related( 'user__organisations', 'user__organisations__primary_location', 'user__organisations__primary_location__country', - 'user__organisations__primary_location__location_target', - 'user__organisations__primary_location__location_target__partner_types') + 'user__organisations__primary_location__location_target') serializer_class = ProjectUpdateExtraSerializer - filter_fields = ('project', 'user', ) + filter_fields = { + 'project': ['exact', ], + 'user': ['exact', ], + 'uuid': ['exact', 'icontains', ], + # These filters only accept a date, not a datetime + # 'created_at': ['exact', 'gt', 'gte', 'lt', 'lte', ], + # 'last_modified_at': ['exact', 'gt', 'gte', 'lt', 'lte', ], + } def get_queryset(self): - """Allow simple filtering on selected fields.""" + """ + Allow simple filtering on selected fields. + We don't use the default filter_fields, because Up filters on + datetime for last_modified_at, and they only support a date, not datetime. + """ queryset = self.queryset - project = self.request.QUERY_PARAMS.get('project', None) - if project is not None: - queryset = self.queryset.filter(project=project) - uuid = self.request.QUERY_PARAMS.get('uuid', None) - if uuid is not None: - queryset = self.queryset.filter(uuid=uuid) - created_at = self.request.QUERY_PARAMS.get('created_at__gt', None) - if created_at is not None: - queryset = self.queryset.filter(created_at__gt=created_at) - last_modified_at = self.request.QUERY_PARAMS.get('last_modified_at__gt', None) - if last_modified_at is not None: - queryset = self.queryset.filter(last_modified_at__gt=last_modified_at) + created_at__gt = self.request.QUERY_PARAMS.get('created_at__gt', None) + if created_at__gt is not None: + queryset = queryset.filter(created_at__gt=created_at__gt) + created_at__lt = self.request.QUERY_PARAMS.get('created_at__lt', None) + if created_at__lt is not None: + queryset = queryset.filter(created_at__lt=created_at__lt) + last_modified_at__gt = self.request.QUERY_PARAMS.get('last_modified_at__gt', None) + if last_modified_at__gt is not None: + queryset = queryset.filter(last_modified_at__gt=last_modified_at__gt) + last_modified_at__lt = self.request.QUERY_PARAMS.get('last_modified_at__lt', None) + if last_modified_at__lt is not None: + queryset = queryset.filter(last_modified_at__lt=last_modified_at__lt) + # Get updates per organisation + project__partners = self.request.QUERY_PARAMS.get('project__partners', None) + if project__partners: + queryset = queryset.filter(project__partners=project__partners) + user__organisations = self.request.QUERY_PARAMS.get('user__organisations', None) + if user__organisations: + queryset = queryset.filter(user__organisations=user__organisations) return queryset diff --git a/akvo/rest/views/user.py b/akvo/rest/views/user.py index 8332bba66a..c8173fafae 100644 --- a/akvo/rest/views/user.py +++ b/akvo/rest/views/user.py @@ -30,14 +30,12 @@ class UserViewSet(BaseRSRViewSet): 'organisation__primary_location', 'organisation__primary_location__country', 'organisation__primary_location__location_target', - 'organisation__primary_location__location_target__partner_types', 'organisation__primary_location__location_target__internal_org_ids', ).prefetch_related( 'organisations', 'organisations__primary_location', 'organisations__primary_location__country', - 'organisations__primary_location__location_target', - 'organisations__primary_location__location_target__partner_types') + 'organisations__primary_location__location_target',) serializer_class = UserSerializer filter_fields = ('username', 'email', 'first_name', 'last_name', 'is_active', 'is_staff', 'is_admin') diff --git a/akvo/rsr/admin.py b/akvo/rsr/admin.py index 257881d901..56054e603a 100644 --- a/akvo/rsr/admin.py +++ b/akvo/rsr/admin.py @@ -99,10 +99,9 @@ class OrganisationAdmin(TimestampsAdminDisplayMixin, ObjectPermissionsModelAdmin fieldsets = ( (_(u'General information'), {'fields': ( - 'name', 'long_name', 'partner_types', 'organisation_type', - 'new_organisation_type', 'can_become_reporting', 'logo', 'url', 'facebook', - 'twitter', 'linkedin', 'iati_org_id', 'public_iati_file', 'language', 'content_owner', - 'allow_edit',)}), + 'name', 'long_name', 'organisation_type', 'new_organisation_type', + 'can_become_reporting', 'logo', 'url', 'facebook', 'twitter', 'linkedin', 'iati_org_id', + 'public_iati_file', 'language', 'content_owner', 'allow_edit',)}), (_(u'Contact information'), {'fields': ('phone', 'mobile', 'fax', 'contact_person', 'contact_email', ), }), (_(u'About the organisation'), {'fields': ('description', 'notes',)}), @@ -121,16 +120,6 @@ def __init__(self, model, admin_site): self.formfield_overrides = {ImageField: {'widget': widgets.AdminFileWidget}, } super(OrganisationAdmin, self).__init__(model, admin_site) - def allowed_partner_types(self, obj): - return ', '.join([pt.label for pt in obj.partner_types.all()]) - - def get_list_display(self, request): - # see the notes fields in the change list if you have the right permissions - if request.user.has_perm(self.opts.app_label + '.' + get_permission_codename('change', - self.opts)): - return list(self.list_display) + ['allowed_partner_types'] - return super(OrganisationAdmin, self).get_list_display(request) - def get_readonly_fields(self, request, obj=None): """Make sure only super users can set the ability to become a reporting org""" if request.user.is_superuser: @@ -296,21 +285,21 @@ def duplicates_in_list(seq): if not my_org_found: errors += [_(u'Your organisation should be somewhere here.')] - # now check that the same org isn't assigned the same partner_type more than once - partner_types = {} + # now check that the same org isn't assigned the same iati_organisation_role more than once + iati_organisation_roles = {} for form in self.forms: - # populate a dict with org names as keys and a list of partner_types as values + # populate a dict with org names as keys and a list of iati_organisation_roles as values try: if not form.cleaned_data.get('DELETE', False): - partner_types.setdefault( + iati_organisation_roles.setdefault( form.cleaned_data['organisation'], [] - ).append(form.cleaned_data['partner_type']) + ).append(form.cleaned_data['iati_organisation_role']) except: pass - for org, types in partner_types.items(): - # are there duplicates in the list of partner_types? - if duplicates_in_list(types): - errors += [_(u'{} has duplicate partner types of the same kind.'.format(org))] + for org, roles in iati_organisation_roles.items(): + # are there duplicates in the list of organisation roles? + if duplicates_in_list(roles): + errors += [_(u'{} has duplicate organisation roles of the same kind.'.format(org))] self._non_form_errors = ErrorList(errors) @@ -318,7 +307,7 @@ def duplicates_in_list(seq): class PartnershipInline(NestedTabularInline): model = get_model('rsr', 'Partnership') - fields = ('organisation', 'partner_type', 'funding_amount', 'internal_id') + fields = ('organisation', 'iati_organisation_role', 'funding_amount', 'internal_id') extra = 0 formset = RSR_PartnershipInlineFormFormSet formfield_overrides = { diff --git a/akvo/rsr/migrations/0024_auto_20150817_1120.py b/akvo/rsr/migrations/0024_auto_20150817_1120.py new file mode 100644 index 0000000000..2ee162c3c1 --- /dev/null +++ b/akvo/rsr/migrations/0024_auto_20150817_1120.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('rsr', '0023_auto_20150821_1145'), + ] + + operations = [ + migrations.RemoveField( + model_name='organisation', + name='partner_types', + ), + migrations.DeleteModel( + name='PartnerType', + ), + ] diff --git a/akvo/rsr/migrations/0025_auto_20150819_1029.py b/akvo/rsr/migrations/0025_auto_20150819_1029.py new file mode 100644 index 0000000000..ec5a218a57 --- /dev/null +++ b/akvo/rsr/migrations/0025_auto_20150819_1029.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('rsr', '0024_auto_20150817_1120'), + ] + + operations = [ + migrations.AlterModelOptions( + name='partnership', + options={'ordering': ['iati_organisation_role'], 'verbose_name': 'project partner', 'verbose_name_plural': 'project partners'}, + ), + migrations.AddField( + model_name='partnership', + name='iati_organisation_role', + field=models.PositiveSmallIntegerField(db_index=True, null=True, verbose_name='Organisation role', choices=[(1, 'Funding partner'), (2, 'Accountable partner'), (3, 'Extending partner'), (4, 'Implementing partner'), (100, 'Sponsor partner')]), + preserve_default=True, + ), + ] diff --git a/akvo/rsr/migrations/0026_auto_20150819_1149.py b/akvo/rsr/migrations/0026_auto_20150819_1149.py new file mode 100644 index 0000000000..b5dc24a4b4 --- /dev/null +++ b/akvo/rsr/migrations/0026_auto_20150819_1149.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations + +def merge_keys(apps, schema_editor): + """ This operation changes a number of keyword labels, listed in the keyword_mergers_labels dict + from the key (old label) of keyword_mergers_labels to the value (new label). + """ + # dict with old label: new label + keyword_mergers_labels = { + u'Dutch WASH Alliance': u'WASH Alliance', + u'C4C': u'Connect4Change', + u'SRHR': u'SRHR Alliance', + u'f4winternational': u'Football for Water', + u'IGG-Water': u'IGG-water program', + } + Keyword = apps.get_model('rsr', 'Keyword') + Project = apps.get_model('rsr', 'Project') + print + print "Keyword distribution before migration:" + for keyword in Keyword.objects.all(): + projects = Project.objects.filter(keywords__exact=keyword) + print keyword.label, projects.count() + + # dict holding old labels as keys and the keword objects that replace the old labelled keywords + keyword_merger_objects = {} + for old_label in keyword_mergers_labels.keys(): + try: + new_key = Keyword.objects.get(label=keyword_mergers_labels[old_label]) + keyword_merger_objects[old_label] = new_key + except: + print "Error: %s keyword does not exist" % str(keyword_mergers_labels[old_label]) + + for project in Project.objects.all(): + # create a list of keyword objects for the project + keywords = [keyword for keyword in project.keywords.all()] + #loop over project's keywords + for keyword in keywords: + # when we find an old labelled key + if keyword.label in keyword_merger_objects.keys(): + # is the new label not there? + if not keyword_merger_objects[keyword.label] in keywords: + # then add it + project.keywords.add(keyword_merger_objects[keyword.label]) + # remove old keyword + project.keywords.remove(keyword) + print "Project {}: keyword {} changed to {}".format( + project.id, keyword.label, keyword_merger_objects[keyword.label].label + ) + project.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('rsr', '0025_auto_20150819_1029'), + ] + + operations = [ + migrations.RunPython( + merge_keys + ), + ] diff --git a/akvo/rsr/migrations/0027_auto_20150820_0144.py b/akvo/rsr/migrations/0027_auto_20150820_0144.py new file mode 100644 index 0000000000..33be684e11 --- /dev/null +++ b/akvo/rsr/migrations/0027_auto_20150820_0144.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations + +def remove_unused_keys(apps, schema_editor): + Keyword = apps.get_model('rsr', 'Keyword') + Project = apps.get_model('rsr', 'Project') + print + + for keyword in Keyword.objects.all(): + projects = Project.objects.filter(keywords__exact=keyword) + if not projects: + print "Deleting keyword {}".format(keyword.label) + keyword.delete() + + print "Keyword distribution after migration:" + for keyword in Keyword.objects.all(): + projects = Project.objects.filter(keywords__exact=keyword) + print keyword.label, projects.count() + +class Migration(migrations.Migration): + + dependencies = [ + ('rsr', '0026_auto_20150819_1149'), + ] + + operations = [ + migrations.RunPython( + remove_unused_keys + ), + ] diff --git a/akvo/rsr/migrations/0028_auto_20150820_0251.py b/akvo/rsr/migrations/0028_auto_20150820_0251.py new file mode 100644 index 0000000000..9fd45bba33 --- /dev/null +++ b/akvo/rsr/migrations/0028_auto_20150820_0251.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations + +def keywords_from_sponsor_partners(apps, schema_editor): + Keyword = apps.get_model('rsr', 'Keyword') + Project = apps.get_model('rsr', 'Project') + print + + projects = Project.objects.filter(partnerships__partner_type=u'sponsor') + for project in projects: + for sponsor in project.partnerships.filter(partner_type='sponsor'): + keyword, created = Keyword.objects.get_or_create( + label="{}:{}".format( + sponsor.organisation.id, + sponsor.organisation.name, + ) + ) + project.keywords.add(keyword) + + print "Keyword distribution after migration:" + for keyword in Keyword.objects.all(): + projects = Project.objects.filter(keywords__exact=keyword) + print keyword.label, projects.count() + + +class Migration(migrations.Migration): + + dependencies = [ + ('rsr', '0027_auto_20150820_0144'), + ] + + operations = [ + migrations.RunPython( + keywords_from_sponsor_partners + ), + ] diff --git a/akvo/rsr/migrations/0029_auto_20150824_1452.py b/akvo/rsr/migrations/0029_auto_20150824_1452.py new file mode 100644 index 0000000000..9898318ba5 --- /dev/null +++ b/akvo/rsr/migrations/0029_auto_20150824_1452.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +def types_to_roles(apps, schema_editor): + from akvo.rsr.models import Partnership as ps + Partnership = apps.get_model("rsr", "Partnership") + partnerships = Partnership.objects.all() + for partnership in partnerships: + if partnership.partner_type: + partnership.iati_organisation_role = ps.PARTNER_TYPES_TO_ROLES_MAP[ + partnership.partner_type + ] + partnership.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('rsr', '0028_auto_20150820_0251'), + ] + + operations = [ + migrations.RunPython( + types_to_roles, + ), + ] diff --git a/akvo/rsr/migrations/0030_remove_partnership_partner_type.py b/akvo/rsr/migrations/0030_remove_partnership_partner_type.py new file mode 100644 index 0000000000..250124037d --- /dev/null +++ b/akvo/rsr/migrations/0030_remove_partnership_partner_type.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('rsr', '0029_auto_20150824_1452'), + ] + + operations = [ + migrations.RemoveField( + model_name='partnership', + name='partner_type', + ), + ] diff --git a/akvo/rsr/models/__init__.py b/akvo/rsr/models/__init__.py index bd1de081e9..f1a907e447 100644 --- a/akvo/rsr/models/__init__.py +++ b/akvo/rsr/models/__init__.py @@ -43,7 +43,6 @@ from .organisation import Organisation from .organisation_account import OrganisationAccount from .partner_site import PartnerSite -from .partner_type import PartnerType from .partnership import Partnership from .payment_gateway import PayPalGateway, MollieGateway, PaymentGatewaySelector from .planned_disbursement import PlannedDisbursement @@ -94,7 +93,6 @@ 'OrganisationAccount', 'OrganisationCustomField', 'PartnerSite', - 'PartnerType', 'Partnership', 'PayPalGateway', 'MollieGateway', @@ -154,9 +152,6 @@ rules.add_perm('rsr.add_partnersite', is_rsr_admin) rules.add_perm('rsr.change_partnersite', is_rsr_admin | is_org_admin) -rules.add_perm('rsr.add_partnertype', is_rsr_admin) -rules.add_perm('rsr.change_partnertype', is_rsr_admin) - rules.add_perm('rsr.change_organisationaccount', is_rsr_admin) rules.add_perm('rsr.add_projectupdate', is_rsr_admin | is_org_admin | is_org_user_manager | diff --git a/akvo/rsr/models/organisation.py b/akvo/rsr/models/organisation.py index e54c3668fd..4c0d003f78 100644 --- a/akvo/rsr/models/organisation.py +++ b/akvo/rsr/models/organisation.py @@ -20,7 +20,6 @@ from akvo.codelists.store.codelists_v201 import ORGANISATION_TYPE as IATI_LIST_ORGANISATION_TYPE from .country import Country -from .partner_type import PartnerType from .partner_site import PartnerSite from .partnership import Partnership from .publishing_status import PublishingStatus @@ -88,7 +87,6 @@ def org_type_from_iati_type(cls, iati_type): _(u'language'), max_length=2, choices=settings.LANGUAGES, default='en', help_text=_(u'The main language of the organisation'), ) - partner_types = models.ManyToManyField(PartnerType) organisation_type = ValidXMLCharField( _(u'organisation type'), max_length=1, db_index=True, choices=ORG_TYPES ) @@ -179,31 +177,34 @@ class QuerySet(DjangoQuerySet): def has_location(self): return self.filter(primary_location__isnull=False) - def partners(self, partner_type): - "return the organisations in the queryset that are partners of type partner_type" - return self.filter(partnerships__partner_type__exact=partner_type).distinct() + def partners(self, role): + "return the organisations in the queryset that are partners of type role" + return self.filter(partnerships__iati_organisation_role__exact=role).distinct() def allpartners(self): return self.distinct() def fieldpartners(self): - return self.partners(Partnership.FIELD_PARTNER) + return self.partners(Partnership.IATI_IMPLEMENTING_PARTNER) def fundingpartners(self): - return self.partners(Partnership.FUNDING_PARTNER) + return self.partners(Partnership.IATI_FUNDING_PARTNER) def sponsorpartners(self): - return self.partners(Partnership.SPONSOR_PARTNER) + return self.partners(Partnership.AKVO_SPONSOR_PARTNER) def supportpartners(self): - return self.partners(Partnership.SUPPORT_PARTNER) + return self.partners(Partnership.IATI_ACCOUNTABLE_PARTNER) + + def extendingpartners(self): + return self.partners(Partnership.IATI_EXTENDING_PARTNER) def supportpartners_with_projects(self): """return the organisations in the queryset that are support partners with published projects, not counting archived projects""" from .project import Project return self.filter( - partnerships__partner_type=Partnership.SUPPORT_PARTNER, + partnerships__iati_organisation_role=Partnership.IATI_ACCOUNTABLE_PARTNER, partnerships__project__publishingstatus__status=PublishingStatus.STATUS_PUBLISHED, partnerships__project__status__in=[ Project.STATUS_ACTIVE, @@ -248,27 +249,6 @@ def iati_org_type(self): return dict(IATI_LIST_ORGANISATION_TYPE)[str(self.new_organisation_type)] if \ self.new_organisation_type else "" - def is_partner_type(self, partner_type): - """returns True if the organisation is a partner of type partner_type to - at least one project""" - return self.partnerships.filter(partner_type__exact=partner_type).count() > 0 - - def is_field_partner(self): - "returns True if the organisation is a field partner to at least one project" - return self.is_partner_type(Partnership.FIELD_PARTNER) - - def is_funding_partner(self): - "returns True if the organisation is a funding partner to at least one project" - return self.is_partner_type(Partnership.FUNDING_PARTNER) - - def is_sponsor_partner(self): - "returns True if the organisation is a sponsor partner to at least one project" - return self.is_partner_type(Partnership.SPONSOR_PARTNER) - - def is_support_partner(self): - "returns True if the organisation is a support partner to at least one project" - return self.is_partner_type(Partnership.SUPPORT_PARTNER) - def partnersites(self): "returns the partnersites belonging to the organisation in a PartnerSite queryset" return PartnerSite.objects.filter(organisation=self) @@ -306,11 +286,10 @@ def support_partners(self): def has_partner_types(self, project): """Return a list of partner types of this organisation to the project""" - from .partnership import Partnership partner_types = [] for ps in Partnership.objects.filter(project=project, organisation=self): - if ps.partner_type: - partner_types.append(ps.partner_type) + if ps.iati_organisation_role: + partner_types.append(ps.iati_organisation_role_label()) return partner_types def countries_where_active(self): @@ -338,7 +317,7 @@ def euros_pledged(self): "How much € the organisation has pledged to projects it is a partner to" return self.active_projects().euros().filter( partnerships__organisation__exact=self, - partnerships__partner_type__exact=Partnership.FUNDING_PARTNER + partnerships__iati_organisation_role__exact=Partnership.IATI_FUNDING_PARTNER ).aggregate( euros_pledged=Sum('partnerships__funding_amount') )['euros_pledged'] or 0 @@ -347,7 +326,7 @@ def dollars_pledged(self): "How much $ the organisation has pledged to projects" return self.active_projects().dollars().filter( partnerships__organisation__exact=self, - partnerships__partner_type__exact=Partnership.FUNDING_PARTNER + partnerships__iati_organisation_role__exact=Partnership.IATI_FUNDING_PARTNER ).aggregate( dollars_pledged=Sum('partnerships__funding_amount') )['dollars_pledged'] or 0 diff --git a/akvo/rsr/models/partner_type.py b/akvo/rsr/models/partner_type.py deleted file mode 100644 index 4f5698c656..0000000000 --- a/akvo/rsr/models/partner_type.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- - -# Akvo RSR 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 >. - - -from django.db import models - -from ..fields import ValidXMLCharField - - -class PartnerType(models.Model): - id = ValidXMLCharField(max_length=8, primary_key=True, unique=True) - label = ValidXMLCharField(max_length=30, unique=True) - - def __unicode__(self): - return self.label - - class Meta: - app_label = 'rsr' - ordering = ('label',) diff --git a/akvo/rsr/models/partnership.py b/akvo/rsr/models/partnership.py index 5a493d752f..606f1d6da3 100644 --- a/akvo/rsr/models/partnership.py +++ b/akvo/rsr/models/partnership.py @@ -12,6 +12,7 @@ class Partnership(models.Model): + # the old way FIELD_PARTNER = u'field' FUNDING_PARTNER = u'funding' SPONSOR_PARTNER = u'sponsor' @@ -28,9 +29,46 @@ class Partnership(models.Model): _(u'Accountable partner'), _(u'Extending partner'), ] - PARTNER_TYPES = zip(PARTNER_TYPE_LIST, PARTNER_LABELS) + # the new way + IATI_FUNDING_PARTNER = 1 + IATI_ACCOUNTABLE_PARTNER = 2 + IATI_EXTENDING_PARTNER = 3 + IATI_IMPLEMENTING_PARTNER = 4 + AKVO_SPONSOR_PARTNER = 100 # not part of the IATI OrganisationRole codelist! + + # make sure the AKVO_SPONSOR_PARTNER is last in the list + IATI_ROLE_LIST = [ + IATI_FUNDING_PARTNER, IATI_ACCOUNTABLE_PARTNER, IATI_EXTENDING_PARTNER, + IATI_IMPLEMENTING_PARTNER, AKVO_SPONSOR_PARTNER, + ] + IATI_ROLE_LABELS = [ + _(u'Funding partner'), + _(u'Accountable partner'), + _(u'Extending partner'), + _(u'Implementing partner'), + _(u'Sponsor partner'), + ] + IATI_ROLES = zip(IATI_ROLE_LIST, IATI_ROLE_LABELS) + + # used when migrating + PARTNER_TYPES_TO_ROLES_MAP = { + FUNDING_PARTNER: IATI_FUNDING_PARTNER, + SUPPORT_PARTNER: IATI_ACCOUNTABLE_PARTNER, + FIELD_PARTNER: IATI_IMPLEMENTING_PARTNER, + SPONSOR_PARTNER: AKVO_SPONSOR_PARTNER, + } + + # backwards compatibility + ROLES_TO_PARTNER_TYPES_MAP = { + IATI_FUNDING_PARTNER: FUNDING_PARTNER, + IATI_ACCOUNTABLE_PARTNER: SUPPORT_PARTNER, + IATI_EXTENDING_PARTNER: EXTENDING_PARTNER, + IATI_IMPLEMENTING_PARTNER: FIELD_PARTNER, + AKVO_SPONSOR_PARTNER: SPONSOR_PARTNER, + } + ALLIANCE_PARTNER = u'alliance' KNOWLEDGE_PARTNER = u'knowledge' NETWORK_PARTNER = u'network' @@ -48,9 +86,8 @@ class Partnership(models.Model): 'Organisation', verbose_name=_(u'organisation'), related_name='partnerships', null=True, help_text=_(u'Select an organisation that is taking an active role in the project.')) project = models.ForeignKey('Project', verbose_name=_(u'project'), related_name='partnerships') - partner_type = ValidXMLCharField( - _(u'partner type'), max_length=9, db_index=True, choices=PARTNER_TYPES, blank=True, - help_text=_(u'Select the role that the organisation is taking within the project.')) + iati_organisation_role = models.PositiveSmallIntegerField( + u'Organisation role', choices=IATI_ROLES, db_index=True, null=True) funding_amount = models.DecimalField( _(u'funding amount'), max_digits=10, decimal_places=2, blank=True, null=True, db_index=True, help_text=_(u'The funding amount of the partner.
' @@ -81,11 +118,20 @@ class Partnership(models.Model): _(u'related IATI activity ID'), max_length=50, blank=True ) + def iati_organisation_role_label(self): + return dict(self.IATI_ROLES)[self.iati_organisation_role] + + def iati_role_to_partner_type(self): + if self.iati_organisation_role: + return self.ROLES_TO_PARTNER_TYPES_MAP[int(self.iati_organisation_role)] + else: + return None + class Meta: app_label = 'rsr' verbose_name = _(u'project partner') verbose_name_plural = _(u'project partners') - ordering = ['partner_type'] + ordering = ['iati_organisation_role'] def __unicode__(self): if self.organisation: @@ -98,8 +144,8 @@ def __unicode__(self): else: organisation_unicode = u'%s' % _(u'Organisation not specified') - if self.partner_type: - organisation_unicode += u' (' + unicode(dict(self.PARTNER_TYPES)[self.partner_type]) \ - + u')' - + if self.iati_organisation_role: + organisation_unicode += u' ({})'.format( + unicode(dict(self.IATI_ROLES)[self.iati_organisation_role]) + ) return organisation_unicode diff --git a/akvo/rsr/models/project.py b/akvo/rsr/models/project.py index c89d3b2382..70a7a9aaa8 100644 --- a/akvo/rsr/models/project.py +++ b/akvo/rsr/models/project.py @@ -426,7 +426,7 @@ def get_pending_donations(self): def get_pledged(self): """ How much is pledges by funding organisations""" return Partnership.objects.filter(project__exact=self).filter( - partner_type__exact=Partnership.FUNDING_PARTNER + iati_organisation_role__exact=Partnership.IATI_FUNDING_PARTNER ).aggregate(Sum('funding_amount'))['funding_amount__sum'] or 0 def get_funds(self): @@ -607,23 +607,26 @@ def all_updates(self): return qs #the following 6 methods return organisation querysets! - def _partners(self, partner_type=None): + def _partners(self, role=None): orgs = Organisation.objects.filter(partnerships__project__in=self) - if partner_type: - orgs = orgs.filter(partnerships__partner_type=partner_type) + if role: + orgs = orgs.filter(partnerships__iati_organisation_role=role) return orgs.distinct() def field_partners(self): - return self._partners(Partnership.FIELD_PARTNER) + return self._partners(Partnership.IATI_IMPLEMENTING_PARTNER) def funding_partners(self): - return self._partners(Partnership.FUNDING_PARTNER) + return self._partners(Partnership.IATI_FUNDING_PARTNER) def sponsor_partners(self): - return self._partners(Partnership.SPONSOR_PARTNER) + return self._partners(Partnership.AKVO_SPONSOR_PARTNER) def support_partners(self): - return self._partners(Partnership.SUPPORT_PARTNER) + return self._partners(Partnership.IATI_ACCOUNTABLE_PARTNER) + + def extending_partners(self): + return self._partners(Partnership.IATI_EXTENDING_PARTNER) def all_partners(self): return self._partners() @@ -773,14 +776,14 @@ def areas_and_categories(self): return areas #shortcuts to linked orgs for a single project - def _partners(self, partner_type=None): + def _partners(self, role=None): """ Return the partner organisations to the project. - If partner_type is specified only organisations having that role are returned + If role is specified only organisations having that role are returned """ orgs = self.partners.all() - if partner_type: - return orgs.filter(partnerships__partner_type=partner_type).distinct() + if role: + return orgs.filter(partnerships__iati_organisation_role=role).distinct() else: return orgs.distinct() @@ -795,16 +798,19 @@ def reporting_org(self): return None def field_partners(self): - return self._partners(Partnership.FIELD_PARTNER) + return self._partners(Partnership.IATI_IMPLEMENTING_PARTNER) def funding_partners(self): - return self._partners(Partnership.FUNDING_PARTNER) + return self._partners(Partnership.IATI_FUNDING_PARTNER) def sponsor_partners(self): - return self._partners(Partnership.SPONSOR_PARTNER) + return self._partners(Partnership.AKVO_SPONSOR_PARTNER) def support_partners(self): - return self._partners(Partnership.SUPPORT_PARTNER) + return self._partners(Partnership.IATI_ACCOUNTABLE_PARTNER) + + def extending_partners(self): + return self._partners(Partnership.IATI_EXTENDING_PARTNER) def all_partners(self): return self._partners() @@ -832,7 +838,7 @@ def partners_info(self): def funding_partnerships(self): "Return the Partnership objects associated with the project that have funding information" - return self.partnerships.filter(partner_type=Partnership.FUNDING_PARTNER) + return self.partnerships.filter(iati_organisation_role=Partnership.IATI_FUNDING_PARTNER) def show_status_large(self): "Show the current project status with background" diff --git a/akvo/rsr/models/publishing_status.py b/akvo/rsr/models/publishing_status.py index 6b31f615e9..af62654aec 100644 --- a/akvo/rsr/models/publishing_status.py +++ b/akvo/rsr/models/publishing_status.py @@ -11,6 +11,7 @@ from django.db.models.signals import post_save from django.dispatch import receiver from django.utils.translation import ugettext_lazy as _ +from .partnership import Partnership from ..fields import ValidXMLCharField @@ -72,16 +73,20 @@ def clean(self): ) if not self.project.partnerships.filter( - partner_type__in=['field', 'funding', 'support'] + iati_organisation_role__in=[Partnership.IATI_FUNDING_PARTNER, + Partnership.IATI_IMPLEMENTING_PARTNER, + Partnership.IATI_ACCOUNTABLE_PARTNER] ).exists(): validation_errors.append( ValidationError( - _('Project needs to have at least one field, funding or support partner.'), + _('Project needs to have at least one funding, implementing or accountable ' + 'partner.'), code='partners' ) ) else: - for funding_partner in self.project.partnerships.filter(partner_type='funding'): + for funding_partner in self.project.partnerships.filter( + iati_organisation_role=Partnership.IATI_FUNDING_PARTNER): if not funding_partner.funding_amount: validation_errors.append( ValidationError(_('All funding partners should have a funding amount.'), diff --git a/akvo/rsr/static/scripts-src/admin/budget_item.js b/akvo/rsr/static/scripts-src/admin/budget_item.js index 1102dd57d8..fa6e1d9dc0 100755 --- a/akvo/rsr/static/scripts-src/admin/budget_item.js +++ b/akvo/rsr/static/scripts-src/admin/budget_item.js @@ -23,7 +23,7 @@ $(function() { // show or hide the associated partnerships-*-funding_amount field depending // on the selected partner type var selected = select.options[select.selectedIndex].value; - if ( selected == 'funding' ) + if ( selected == '1' ) $("input[name='partnerships-"+ select.name.split('-')[1] +"-funding_amount']").show(); else $("input[name='partnerships-"+ select.name.split('-')[1] +"-funding_amount']").hide(); @@ -43,11 +43,11 @@ $(function() { // find all inputs named partnerships-*-funding_amount except // partnerships-__prefix__-funding_amount $("input[name^='partnerships-'][name$='-funding_amount'][name!='partnerships-__prefix__-funding_amount']").each(function(i) { - var select = $("select[name='partnerships-" + i + "-partner_type']"); + var select = $("select[name='partnerships-" + i + "-iati_organisation_role']"); checkPartnerType(select[0]); }); - $("select[name^='partnerships-'][name$='-partner_type']").change(function() { + $("select[name^='partnerships-'][name$='-iati_organisation_role']").change(function() { checkPartnerType(this); }); diff --git a/akvo/rsr/views/project.py b/akvo/rsr/views/project.py index 1932177463..d70b95a434 100644 --- a/akvo/rsr/views/project.py +++ b/akvo/rsr/views/project.py @@ -461,17 +461,15 @@ def search(request): def partners(request, project_id): """.""" project = get_object_or_404(Project, pk=project_id) - partner_vals = project.all_partners().values() - for partner in partner_vals: + partners = project.all_partners().values() + for partner in partners: id_key = "id".decode('unicode-escape') p = Organisation.objects.get(pk=partner[id_key]) partner['partner_types'] = p.has_partner_types(project) partner['organisation_obj'] = p - partner_types = _get_project_partners(project) context = { 'project': project, - 'partner_vals': partner_vals, - 'partner_types': partner_types + 'partners': partners, } return render(request, 'project_partners.html', context) diff --git a/akvo/scripts/cordaid/post_import.py b/akvo/scripts/cordaid/post_import.py index 1edb73192f..cb9081250c 100644 --- a/akvo/scripts/cordaid/post_import.py +++ b/akvo/scripts/cordaid/post_import.py @@ -78,7 +78,7 @@ def assign_funding_partner(project, organisation, amount): funding_partnership, created = Partnership.objects.get_or_create( organisation=organisation, project=project, - partner_type=Partnership.FUNDING_PARTNER, + iati_organisation_role=Partnership.IATI_FUNDING_PARTNER, defaults={'funding_amount': amount} ) if created: diff --git a/akvo/scripts/set_partner_types.py b/akvo/scripts/set_partner_types.py deleted file mode 100644 index 5435be0af7..0000000000 --- a/akvo/scripts/set_partner_types.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -#!/usr/bin/env python - -""" -Script for setting all organisations to at least 'field' and 'funding' partner type. -""" - -import os -os.environ['DJANGO_SETTINGS_MODULE'] = 'akvo.settings' - -from akvo.rsr.models import Organisation, PartnerType - - -def set_partner_types(): - # Collect all organisations - organisations = Organisation.objects.all() - - # Set counters - total_count_org = len(organisations) - field_count = 0 - funding_count = 0 - - # Get field and funding partner type - field_partnertype, created = PartnerType.objects.get_or_create(id="field", label="Field partner") - funding_partnertype, created = PartnerType.objects.get_or_create(id="funding", label="Funding partner") - - # For each organisation, check whether they are field and/or funding partners - for count, organisation in enumerate(organisations): - print "Checking organisation " + str(count + 1) + " of " + str(total_count_org) + ": " + organisation.long_name - - if field_partnertype not in organisation.partner_types.all(): - organisation.partner_types.add(field_partnertype) - print organisation.long_name + " added as field partner" - field_count += 1 - - if funding_partnertype not in organisation.partner_types.all(): - organisation.partner_types.add(funding_partnertype) - print organisation.long_name + " added as funding partner" - funding_count += 1 - - print "\nDone:\n" - print "- " + str(field_count) + " organisations added as field partner." - print "- " + str(funding_count) + " organisations added as funding partner.\n" - - -if __name__ == '__main__': - set_partner_types() \ No newline at end of file diff --git a/akvo/templates/myrsr/project_editor.html b/akvo/templates/myrsr/project_editor.html index 899eaf3178..00059c36fe 100644 --- a/akvo/templates/myrsr/project_editor.html +++ b/akvo/templates/myrsr/project_editor.html @@ -1248,7 +1248,7 @@
{% trans 'Keywords' %}
fundingInputNode = parentNode.getElementsByTagName("input")[1]; fundingNode = fundingInputNode.parentNode.parentNode; - if (node.options[node.selectedIndex].value === 'funding') { + if (node.options[node.selectedIndex].value === '1') { fundingInputNode.removeAttribute('disabled'); fundingInputNode.className += ' priority1'; diff --git a/akvo/templates/myrsr/project_editor_partials/partner_input.html b/akvo/templates/myrsr/project_editor_partials/partner_input.html index 7fe670932b..a59c0fbfc9 100644 --- a/akvo/templates/myrsr/project_editor_partials/partner_input.html +++ b/akvo/templates/myrsr/project_editor_partials/partner_input.html @@ -21,16 +21,16 @@
- + + + + + + {% if partnership.iati_organisation_role == 100 %}{% endif %} - {% if partnership.partner_type == 'sponsor' %}

{% trans "The 'sponsor' role will soon be removed, please select a different role for this organisation." %}

{% endif %} + {% if partnership.iati_organisation_role == 100 %}

{% trans "The 'sponsor' role will soon be removed, please select a different role for this organisation." %}

{% endif %}
-
+
{{ project.get_currency_display }} + {% if partnership.iati_organisation_role != 1 %} disabled {% endif %}>
diff --git a/akvo/templates/project_partners.html b/akvo/templates/project_partners.html index 3dcc49469c..75b43e489b 100644 --- a/akvo/templates/project_partners.html +++ b/akvo/templates/project_partners.html @@ -10,7 +10,7 @@

{% trans "Project partners" %}

- {% for partner in partner_vals|dictsort:"name" %} + {% for partner in partners|dictsort:"name" %}
{% img partner.organisation_obj 120 120 partner.name %} @@ -25,12 +25,9 @@

{{ partner.name }}

{% trans "Roles" %}

    {% for type in partner.partner_types %} -
  • {{ type|title }} {% trans "partner" %}
  • - {% if forloop.first %} -
  • {{type|title}} {% trans "partner" %}{% if forloop.last %}.{% else %},{% endif %}
  • - {% else %} -
  • {{type|title}} {% trans "partner" %}
  • - {% endif %} +
  • + {{ type }}{% if forloop.last %}.{% else %},{% endif %} +
  • {% endfor %}
diff --git a/akvo/templates/project_report.html b/akvo/templates/project_report.html index a0d51ca3c2..41736082c9 100644 --- a/akvo/templates/project_report.html +++ b/akvo/templates/project_report.html @@ -535,7 +535,7 @@

{% trans "Participating Organisations" %}

{% trans "Role" %} - {% for ps in info.0 %}{% if not forloop.first %}, {% endif %}{{ps.partner_type|title}}{% endfor %} {% trans "Partner" %} + {% for ps in info.0 %}{% if not forloop.first %}, {% endif %}{{ps.iati_organisation_role_label}}{% endfor %} {% trans "RSR ID" %}