From 4981e1150a3350abe99bc75c09649a1c024840f6 Mon Sep 17 00:00:00 2001 From: rup Date: Wed, 13 Jul 2022 11:25:25 +0545 Subject: [PATCH 01/13] Crate api for eap create, update, get - Modified models - Added serializers - Added views - Added test cases, factories --- eap/admin.py | 6 + eap/factories.py | 76 ++++++++ eap/migrations/0001_initial.py | 4 +- eap/migrations/0003_auto_20220713_1229.py | 44 +++++ eap/models.py | 28 ++- eap/serializers.py | 129 +++++++++++++ eap/test_views.py | 220 ++++++++++++++++++++++ eap/views.py | 65 ++++++- main/urls.py | 6 + 9 files changed, 568 insertions(+), 10 deletions(-) create mode 100644 eap/factories.py create mode 100644 eap/migrations/0003_auto_20220713_1229.py create mode 100644 eap/serializers.py create mode 100644 eap/test_views.py diff --git a/eap/admin.py b/eap/admin.py index 8c38f3f3d..c74bac829 100644 --- a/eap/admin.py +++ b/eap/admin.py @@ -1,3 +1,9 @@ from django.contrib import admin # Register your models here. +from .models import EAP + + +@admin.register(EAP) +class EAPAdmin(admin.ModelAdmin): + pass diff --git a/eap/factories.py b/eap/factories.py new file mode 100644 index 000000000..59ab863a3 --- /dev/null +++ b/eap/factories.py @@ -0,0 +1,76 @@ +import datetime +import factory +from factory import fuzzy +from datetime import timezone + +from django.core.files.base import ContentFile + +from api.factories import ( + disaster_type, + country, + district, +) + +from .models import ( + EAP, + EAPDocument, +) + + +class EAPDocumentFactory(factory.django.DjangoModelFactory): + class Meta: + model = EAPDocument + + file = factory.LazyAttribute( + lambda _: ContentFile( + factory.django.ImageField()._make_data( + {'width': 1024, 'height': 768} + ), 'flash_update.jpg' + ) + ) + + +class EAPFactory(factory.django.DjangoModelFactory): + class Meta: + model = EAP + + district = factory.SubFactory(district.DistrictFactory) + country = factory.SubFactory(country.CountryFactory) + disaster_type = factory.SubFactory(disaster_type.DisasterTypeFactory) + document = factory.SubFactory(EAPDocumentFactory) + eap_number = fuzzy.FuzzyText(length=20) + approval_date = fuzzy.FuzzyDateTime(datetime.datetime(2008, 1, 1, tzinfo=timezone.utc)) + status = fuzzy.FuzzyChoice(EAP.Status) + operational_timeframe = fuzzy.FuzzyInteger(0, 2) + lead_time = fuzzy.FuzzyInteger(0, 2) + eap_timeframe = fuzzy.FuzzyInteger(0, 2) + num_of_people = fuzzy.FuzzyInteger(0, 5) + total_budget = fuzzy.FuzzyInteger(0, 5) + readiness_budget = fuzzy.FuzzyInteger(0, 5) + pre_positioning_budget = fuzzy.FuzzyInteger(0, 5) + early_action_budget = fuzzy.FuzzyInteger(0, 5) + trigger_statement = fuzzy.FuzzyText(length=20) + overview = fuzzy.FuzzyText(length=50) + originator_name = fuzzy.FuzzyText(length=50) + originator_title = fuzzy.FuzzyText(length=50) + originator_email = fuzzy.FuzzyText(length=50) + originator_phone = fuzzy.FuzzyInteger(0, 9) + + nsc_name = fuzzy.FuzzyText(length=50) + nsc_title = fuzzy.FuzzyText(length=50) + nsc_email = fuzzy.FuzzyText(length=50) + nsc_phone = fuzzy.FuzzyInteger(0, 9) + + ifrc_focal_name = fuzzy.FuzzyText(length=50) + ifrc_focal_title = fuzzy.FuzzyText(length=50) + ifrc_focal_email = fuzzy.FuzzyText(length=50) + ifrc_focal_phone = fuzzy.FuzzyInteger(0, 9) + + @factory.post_generation + def early_actions(self, create, extracted, **kwargs): + if not create: + return + + if extracted: + for early_action in extracted: + self.early_actions.add(early_action) diff --git a/eap/migrations/0001_initial.py b/eap/migrations/0001_initial.py index 20c806194..2412659bd 100644 --- a/eap/migrations/0001_initial.py +++ b/eap/migrations/0001_initial.py @@ -26,7 +26,7 @@ class Migration(migrations.Migration): ('modified_at', models.DateTimeField(auto_now=True, verbose_name='updated at')), ('eap_number', models.CharField(max_length=50, verbose_name='EAP Number')), ('approval_date', models.DateField(verbose_name='Date of EAP Approval')), - ('status', models.CharField(choices=[('approved', 'Approved'), ('in_process', 'In Process')], default=eap.models.EAP.Status('in_process'), max_length=255, verbose_name='EAP Status')), + ('status', models.CharField(choices=[('approved', 'Approved'), ('activated', 'Activated')], default=eap.models.EAP.Status('activated'), max_length=255, verbose_name='EAP Status')), ('operational_timeframe', models.IntegerField(verbose_name='Operational Timeframe (Months)')), ('lead_time', models.IntegerField(verbose_name='Lead Time')), ('eap_timeframe', models.IntegerField(verbose_name='EAP Timeframe (Years)')), @@ -137,4 +137,4 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Actions', }, ), - ] + ] \ No newline at end of file diff --git a/eap/migrations/0003_auto_20220713_1229.py b/eap/migrations/0003_auto_20220713_1229.py new file mode 100644 index 000000000..14d1931dd --- /dev/null +++ b/eap/migrations/0003_auto_20220713_1229.py @@ -0,0 +1,44 @@ +# Generated by Django 2.2.28 on 2022-07-13 12:29 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import eap.models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('eap', '0002_auto_20220708_0747'), + ] + + operations = [ + migrations.CreateModel( + name='EAPDocument', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('file', models.FileField(blank=True, null=True, upload_to='')), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='document_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created by')), + ], + options={ + 'verbose_name': 'Document', + 'verbose_name_plural': 'Documents', + }, + ), + migrations.AlterField( + model_name='eap', + name='document', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='eap_document', to='eap.EAPDocument', verbose_name='EAP Documents'), + ), + migrations.AlterField( + model_name='eap', + name='eap_number', + field=models.CharField(editable=False, max_length=50, verbose_name='EAP Number'), + ), + migrations.AlterField( + model_name='eap', + name='status', + field=models.CharField(choices=[('approved', 'Approved'), ('activated', 'Activated')], default=eap.models.EAP.Status('approved'), max_length=255, verbose_name='EAP Status'), + ), + ] diff --git a/eap/models.py b/eap/models.py index 743753021..d1610b543 100644 --- a/eap/models.py +++ b/eap/models.py @@ -66,10 +66,25 @@ def __str__(self): return f'{self.sector}' +class EAPDocument(models.Model): + file = models.FileField(null=True, blank=True) + created_by = models.ForeignKey( + settings.AUTH_USER_MODEL, verbose_name=_('Created by'), related_name='document_created_by', + null=True, blank=True, on_delete=models.SET_NULL, + ) + + class Meta: + verbose_name = _('Document') + verbose_name_plural = _('Documents') + + def __str__(self): + return str(self.id) + + class EAP(models.Model): - class Status(TextChoices): # TODO some more status choices are to be expected by client. + class Status(TextChoices): APPROVED = 'approved', _('Approved') - IN_PROCESS = 'in_process', _('In Process') + ACTIVATED = 'activated', _('Activated') created_by = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_('Created by'), related_name='eap_created_by', @@ -94,10 +109,10 @@ class Status(TextChoices): # TODO some more status choices are to be expected b DisasterType, on_delete=models.SET_NULL, verbose_name=_('Disaster Type'), related_name='eap_disaster_type', null=True ) - eap_number = models.CharField(max_length=50, verbose_name=_('EAP Number')) + eap_number = models.CharField(max_length=50, editable=False, verbose_name=_('EAP Number')) approval_date = models.DateField(verbose_name=_('Date of EAP Approval')) status = models.CharField( - max_length=255, choices=Status.choices, default=Status.IN_PROCESS, + max_length=255, choices=Status.choices, default=Status.APPROVED, verbose_name=_('EAP Status') ) operational_timeframe = models.IntegerField(verbose_name=_('Operational Timeframe (Months)')) @@ -110,8 +125,9 @@ class Status(TextChoices): # TODO some more status choices are to be expected b early_action_budget = models.IntegerField(verbose_name=_('Early Actions Budget (CHF)'), null=True, blank=True) trigger_statement = models.TextField(verbose_name=_('Trigger Statement (Threshold for Activation)')) overview = models.TextField(verbose_name=_('EAP Overview')) - document = models.FileField( - verbose_name=_('EAP Documents'), upload_to='eap/documents/', + document = models.ForeignKey( + EAPDocument, on_delete=models.SET_NULL, + verbose_name=_('EAP Documents'), related_name='eap_document', null=True, blank=True ) early_actions = models.ManyToManyField( diff --git a/eap/serializers.py b/eap/serializers.py new file mode 100644 index 000000000..178fd7f5b --- /dev/null +++ b/eap/serializers.py @@ -0,0 +1,129 @@ +from django.utils.translation import ugettext + +from rest_framework import serializers + +from enumfields.drf.serializers import EnumSupportSerializerMixin + +from api.serializers import ( + UserNameSerializer, + DisasterTypeSerializer, + CountrySerializer, + MiniDistrictSerializer, +) + +from eap.models import ( + EAP, + Action, + EAPPartner, + EAPReference, + EarlyAction, + EarlyActionIndicator, + EAPDocument, +) + +from main.writable_nested_serializers import ( + NestedCreateMixin, + NestedUpdateMixin, +) + + +class EAPReferenceSerializer(serializers.ModelSerializer): + class Meta: + model = EAPReference + fields = '__all__' + read_only_fields = ('eap',) + + +class EAPPartnerSerializer(serializers.ModelSerializer): + class Meta: + model = EAPPartner + fields = '__all__' + read_only_fields = ('eap',) + + +class EarlyActionIndicatorSerializer(serializers.ModelSerializer): + class Meta: + model = EarlyActionIndicator + fields = ('__all__') + + +class ActionSerializer(serializers.ModelSerializer): + class Meta: + model = Action + fields = ('__all__') + read_only_fields = ('early_action',) + + +class EarlyActionSerializer( + NestedUpdateMixin, + NestedCreateMixin, + serializers.ModelSerializer +): + indicators = EarlyActionIndicatorSerializer(many=True, required=False) + actions = ActionSerializer(source='action', many=True, required=False) + sector_display = serializers.CharField(source='get_sector_display', read_only=True) + + class Meta: + model = EarlyAction + fields = ('__all__') + + +class EAPActionSerializer(serializers.ModelSerializer): + class Meta: + model = Action + fields = ('__all__') + read_only_fields = ('early_action',) + + +class EAPDocumentSerializer(serializers.ModelSerializer): + created_by_details = UserNameSerializer(source='created_by', read_only=True) + file = serializers.FileField(required=False) + + class Meta: + model = EAPDocument + fields = '__all__' + read_only_fields = ('created_by',) + + def create(self, validated_data): + validated_data['created_by'] = self.context['request'].user + return super().create(validated_data) + + +class EAPSerializer( + EnumSupportSerializerMixin, + NestedUpdateMixin, + NestedCreateMixin, + serializers.ModelSerializer +): + country_details = CountrySerializer(source='country', read_only=True) + district_details = MiniDistrictSerializer(source='district', read_only=True) + references = EAPReferenceSerializer(source='eap_reference', many=True, required=False) + partners = EAPPartnerSerializer(source='eap_partner', many=True, required=False) + early_actions = EarlyActionSerializer(many=True) + created_by_details = UserNameSerializer(source='created_by', read_only=True) + hazard_type_details = DisasterTypeSerializer(source='disaster_type', read_only=True) + document_details = EAPDocumentSerializer(source='document', read_only=True, required=False) + + class Meta: + model = EAP + fields = '__all__' + + def validate(self, validated_data): + district = validated_data['district'] + if district: + if district.country != validated_data['country']: + raise serializers.ValidationError({ + 'district': ugettext('Different districts found for given country') + }) + return validated_data + + def create(self, validated_data): + validated_data['created_by'] = self.context['request'].user + eap = super().create(validated_data) + return eap + + def update(self, instance, validated_data): + validated_data['modified_by'] = self.context['request'].user + eap = super().update(instance, validated_data) + return eap + diff --git a/eap/test_views.py b/eap/test_views.py new file mode 100644 index 000000000..156da04ba --- /dev/null +++ b/eap/test_views.py @@ -0,0 +1,220 @@ +import os + +from django.conf import settings +from django.contrib.auth.models import User + +from main.test_case import APITestCase +from eap.models import EAP + +from api.factories.country import CountryFactory +from api.factories.district import DistrictFactory +from api.factories.disaster_type import DisasterTypeFactory +from deployments.factories.user import UserFactory + +from .factories import EAPDocumentFactory, EAPFactory + + +class EAPTest(APITestCase): + + def setUp(self): + self.user = UserFactory.create(username='jo') + self.country1 = CountryFactory.create(name='abc') + self.country2 = CountryFactory.create(name='xyz') + self.district1 = DistrictFactory.create(name='test district1', country=self.country1) + self.district2 = DistrictFactory.create(name='test district2', country=self.country2) + self.document1 = EAPDocumentFactory.create(created_by=self.user) + self.disaster_type = DisasterTypeFactory.create(name="test earthquake") + self.disaster_type_updated = DisasterTypeFactory.create(name="test flood") + + path = os.path.join(settings.TEST_DIR, 'documents') + self.file = os.path.join(path, 'go.png') + + self.body = { + "references": [ + { + "source": "test", + "url": "https://jsonformatter.org/" + }, + { + "source": "test 2", + "url": "https://jsonformatter.org/" + } + ], + "partners": [ + { + "name": "test name", + "url": "https://jsonformatter2.org/" + }, + { + "name": "test name 2", + "url": "https://jsonformatter2.org/" + } + ], + "eap_number": "1", + "approval_date": "2022-11-11", + "status": "approved", + "operational_timeframe": 5, + "lead_time": 5, + "eap_timeframe": 5, + "num_of_people": 1000, + "total_budget": 10000, + "readiness_budget": 5000, + "pre_positioning_budget": 3000, + "early_action_budget": 2000, + "trigger_statement": "test", + "overview": "test", + "document": self.document1.id, + "originator_name": "eap name", + "originator_title": "eap title", + "originator_email": "eap@gmail.com", + "originator_phone": "1245145241", + "nsc_name": "eap ns name", + "nsc_title": "eap ns title", + "nsc_email": "eap_ns@gmail.com", + "nsc_phone": "8547458745", + "ifrc_focal_name": "ifrc name", + "ifrc_focal_title": "ifrc title", + "ifrc_focal_email": "eap_ifrc@gmail.com", + "ifrc_focal_phone": "5685471584", + "country": self.country1.id, + "district": self.district1.id, + "disaster_type": self.disaster_type.id, + "early_actions": [ + { + "sector": "Health", + "budget_per_sector": 1000, + "prioritized_risk": "test", + "targeted_people": 100, + "readiness_activities": "test", + "prepositioning_activities": "test", + "indicators": [ + { + "indicator": "indicator_1", + "indicator_value": 1 + }, + { + "indicator": "indicator_2", + "indicator_value": 2 + } + ], + "actions": [ + { + "early_act": "test" + }, + { + "early_act": "test 2" + } + ] + + }, + { + "sector": "Health", + "budget_per_sector": 1000, + "prioritized_risk": "prioritized_risk", + "targeted_people": 200, + "readiness_activities": "test", + "prepositioning_activities": "test", + "indicators": [ + { + "indicator": "indicator_1", + "indicator_value": 1 + }, + { + "indicator": "indicator_2", + "indicator_value": 2 + } + ], + "actions": [ + { + "early_act": " early_acttest" + }, + { + "early_act": "early_act test 2" + } + ] + + } + ] + } + super().setUp() + + def test_create_and_update_eap(self): + self.client.force_authenticate(user=self.user) + # create eap + with self.capture_on_commit_callbacks(execute=True): + response = self.client.post('/api/v2/eap/', self.body, format='json').json() + created = EAP.objects.get(id=response['id']) + self.assertEqual(created.created_by.id, self.user.id) + self.assertEqual(created.country.id, self.country1.id) + self.assertEqual(created.district.id, self.district1.id) + self.assertEqual(created.disaster_type, self.disaster_type) + self.assertEqual(created.document.id, self.document1.id) + self.assertEqual(response['country'], self.country1.id) + self.assertEqual(created.status, EAP.Status.APPROVED) + self.assertEqual(created.early_actions.count(), 2) + self.assertEqual(len(response['references']), 2) + self.assertEqual(len(response['partners']), 2) + + # update eap + data = self.body + data['country'] = self.country2.id + data['district'] = self.district2.id + data['references'] = [ + { + "source": "test updated", + "url": "https://jsonformatter.org/" + } + ] + data['partners'] = [ + { + "name": "test name updated", + "url": "https://jsonformatter2.org/" + } + ] + + data['disaster_type'] = str(self.disaster_type_updated.id) + data['status'] = EAP.Status.ACTIVATED + data['reference'] = self.document1.id + + response = self.client.put(f'/api/v2/eap/{created.id}/', data, format='json').json() + updated = EAP.objects.get(id=response['id']) + self.assertEqual(updated.id, created.id) + self.assertEqual(updated.modified_by, self.user) + self.assertEqual(updated.status, EAP.Status.ACTIVATED) + self.assertEqual(updated.disaster_type, self.disaster_type_updated) + self.assertEqual(updated.early_actions.count(), 2) + self.assertEqual(len(response['references']), 1) + self.assertEqual(len(response['partners']), 1) + + def test_get_eap(self): + user1 = UserFactory.create(username='abc') + eap1, eap2, eap3 = EAPFactory.create_batch(3, created_by=user1) + self.client.force_authenticate(user=user1) + response1 = self.client.get('/api/v2/eap/').json() + self.assertEqual(response1['count'], 3) + self.assertEqual(response1['results'][0]['created_by'], user1.id) + self.assertEqual( + sorted([eap1.id, eap2.id, eap3.id]), + sorted([data['id'] for data in response1['results']]) + ) + + # query single eap + response = self.client.get(f'/api/v2/eap/{eap1.id}/').json() + self.assertEqual(response['created_by'], user1.id) + self.assertEqual(response['id'], eap1.id) + + # try with another user + user2 = User.objects.create(username='xyz') + self.client.force_authenticate(user=user2) + eap4, eap5 = EAPFactory.create_batch(2, created_by=user2) + response2 = self.client.get('/api/v2/eap/').json() + self.assertEqual(response2['count'], 5) + self.assertEqual(response2['results'][0]['created_by'], user2.id) + self.assertIn(eap4.id, [data['id'] for data in response2['results']]) + self.assertNotIn([data['id'] for data in response2['results']], [data['id'] for data in response1['results']]) + + # try with users who has no any eap created + user3 = User.objects.create(username='ram') + self.client.force_authenticate(user=user3) + response3 = self.client.get('/api/v2/eap/').json() + self.assertEqual(response3['count'], 5) diff --git a/eap/views.py b/eap/views.py index 91ea44a21..fdc72bd51 100644 --- a/eap/views.py +++ b/eap/views.py @@ -1,3 +1,64 @@ -from django.shortcuts import render - # Create your views here. + +from rest_framework.response import Response +from rest_framework import ( + views, + viewsets, + permissions, + mixins, +) +from .models import ( + EarlyActionIndicator, + EAP, + EAPDocument, +) +from .serializers import ( + EAPSerializer, + EAPDocumentSerializer, +) + + +class EAPDocumentViewSet( + mixins.ListModelMixin, + mixins.CreateModelMixin, + viewsets.GenericViewSet +): + permission_class = [permissions.IsAuthenticated] + serializer_class = EAPDocumentSerializer + + def get_queryset(self): + return EAPDocument.objects.all() + + +class EAPViewSet(viewsets.ModelViewSet): + serializer_class = EAPSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return EAP.objects.all().order_by('-created_at') + + +class EAPStatusOptions(views.APIView): + def get(self, request, format=None): + options = { + 'eap_status': [ + { + 'value': value, + 'label': label, + } for value, label in EAP.Status.choices + ] + } + return Response(options) + + +class EarlyActionIndicatorOptions(views.APIView): + def get(self, request, format=None): + options = { + 'eap_status': [ + { + 'value': value, + 'label': label, + } for value, label in EarlyActionIndicator.IndicatorChoices.choices + ] + } + return Response(options) diff --git a/main/urls.py b/main/urls.py index de6a821c3..2e03a4c46 100644 --- a/main/urls.py +++ b/main/urls.py @@ -68,6 +68,7 @@ from rest_framework.documentation import include_docs_urls from api import drf_views as api_views from flash_update import views as flash_views +from eap import views as eap_views from per import drf_views as per_views from deployments import drf_views as deployment_views from notifications import drf_views as notification_views @@ -148,6 +149,9 @@ router.register(r'donor', flash_views.DonorsViewSet, basename='donor') router.register(r'share-flash-update', flash_views.ShareFlashUpdateViewSet, basename='share_flash_update') +router.register(r'eap', eap_views.EAPViewSet, basename='eap') +router.register(r'eap-file', eap_views.EAPDocumentViewSet, basename='eap_file') + # Dref apis router.register(r'dref', dref_views.DrefViewSet, basename='dref') router.register(r'dref-files', dref_views.DrefFileViewSet, basename='dref_files') @@ -177,6 +181,8 @@ url(r'^api/v2/add_cronjob_log/', AddCronJobLog.as_view()), url(r'^api/v2/flash-update-options/', flash_views.FlashUpdateOptions.as_view()), url(r'^api/v2/export-flash-update/(?P\d+)/', flash_views.ExportFlashUpdateView.as_view()), + url(r'^api/v2/eap-status/', eap_views.EAPStatusOptions.as_view()), + url(r'^api/v2/eap-indicators/', eap_views.EarlyActionIndicatorOptions.as_view()), url(r'^register', NewRegistration.as_view()), # url(r'^createperform', CreatePerForm.as_view()), url(r'^updateperform', UpdatePerForm.as_view()), From a5598bfd868098f088ce0561c0d377bceadcdb23 Mon Sep 17 00:00:00 2001 From: rup Date: Fri, 15 Jul 2022 13:39:45 +0545 Subject: [PATCH 02/13] Add api for eap options - removed api for indicator, status - created a model for prioritized_risk for early actions - modified test cases to accept many prioritized risks --- eap/admin.py | 3 +- eap/migrations/0004_auto_20220715_0628.py | 40 +++++++++++++++++++ eap/models.py | 25 +++++++++--- eap/serializers.py | 9 +++++ eap/test_views.py | 24 +++++++++--- eap/views.py | 47 +++++++++++++---------- main/urls.py | 3 +- 7 files changed, 116 insertions(+), 35 deletions(-) create mode 100644 eap/migrations/0004_auto_20220715_0628.py diff --git a/eap/admin.py b/eap/admin.py index c74bac829..2f5179ab5 100644 --- a/eap/admin.py +++ b/eap/admin.py @@ -6,4 +6,5 @@ @admin.register(EAP) class EAPAdmin(admin.ModelAdmin): - pass + list_display = ('eap_number', 'country', 'status', 'operational_timeframe', 'total_budget') + diff --git a/eap/migrations/0004_auto_20220715_0628.py b/eap/migrations/0004_auto_20220715_0628.py new file mode 100644 index 000000000..60be4f4ee --- /dev/null +++ b/eap/migrations/0004_auto_20220715_0628.py @@ -0,0 +1,40 @@ +# Generated by Django 2.2.28 on 2022-07-15 06:28 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('eap', '0003_auto_20220713_1229'), + ] + + operations = [ + migrations.RemoveField( + model_name='earlyaction', + name='prioritized_risk', + ), + migrations.AlterField( + model_name='eap', + name='eap_number', + field=models.CharField(max_length=50, verbose_name='EAP Number'), + ), + migrations.AlterField( + model_name='earlyaction', + name='sector', + field=models.CharField(choices=[('shelter_housing_and_settlements', 'Shelter, Housing And Settlements'), ('livelihoods', 'Livelihoods'), ('multi-purpose_cash', 'Multi-purpose Cash'), ('health_and_care', 'Health And Care'), ('water_sanitation_and_hygiene', 'Water, Sanitation And Hygiene'), ('protection_gender_and_inclusion', 'Protection, Gender And Inclusion'), ('education', 'Education'), ('migration', 'Migration'), ('risk_reduction_climate_adaptation_and_recovery', 'Risk Reduction, Climate Adaptation And Recovery'), ('community_engagement_and _accountability', 'Community Engagement And Accountability'), ('environment_sustainability ', 'Environment Sustainability'), ('shelter_cluster_coordination', 'Shelter Cluster Coordination')], max_length=255, verbose_name='sector'), + ), + migrations.CreateModel( + name='PrioritizedRisk', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('risks', models.TextField(blank=True, null=True)), + ('early_action', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='early_actions_prioritized_risk', to='eap.EarlyAction', verbose_name='early action')), + ], + options={ + 'verbose_name': 'Prioritized risk', + 'verbose_name_plural': 'Prioritized risks', + }, + ), + ] diff --git a/eap/models.py b/eap/models.py index d1610b543..241ee50ce 100644 --- a/eap/models.py +++ b/eap/models.py @@ -2,9 +2,8 @@ from django.conf import settings from django.utils.translation import ugettext_lazy as _ -from main.enums import TextChoices +from main.enums import TextChoices, IntegerChoices from deployments.models import Sectors -from main.enums import IntegerChoices from api.models import ( Country, District, @@ -51,10 +50,7 @@ class Sector(IntegerChoices): sector = models.IntegerField(choices=Sector.choices, verbose_name=_('sector')) budget_per_sector = models.IntegerField(verbose_name=_('Budget per sector (CHF)'), null=True, blank=True) indicators = models.ManyToManyField(EarlyActionIndicator, verbose_name=_('Indicators'), blank=True) - - prioritized_risk = models.TextField(verbose_name=_('Prioritized risk'), null=True, blank=True) targeted_people = models.IntegerField(verbose_name=_('Targeted people'), null=True, blank=True,) - readiness_activities = models.TextField(verbose_name=_('Readiness Activities'), null=True, blank=True) prepositioning_activities = models.TextField(verbose_name=_('Pre-positioning Activities'), null=True, blank=True) @@ -109,7 +105,7 @@ class Status(TextChoices): DisasterType, on_delete=models.SET_NULL, verbose_name=_('Disaster Type'), related_name='eap_disaster_type', null=True ) - eap_number = models.CharField(max_length=50, editable=False, verbose_name=_('EAP Number')) + eap_number = models.CharField(max_length=50, verbose_name=_('EAP Number')) approval_date = models.DateField(verbose_name=_('Date of EAP Approval')) status = models.CharField( max_length=255, choices=Status.choices, default=Status.APPROVED, @@ -197,3 +193,20 @@ class Meta: def __str__(self): return f'{self.id}' + + +class PrioritizedRisk(models.Model): + early_action = models.ForeignKey( + EarlyAction, + on_delete=models.CASCADE, + related_name='early_actions_prioritized_risk', + verbose_name=_('early action') + ) + risks = models.TextField(null=True, blank=True) + + class Meta: + verbose_name = _('Prioritized risk') + verbose_name_plural = _('Prioritized risks') + + def __str__(self): + return f'{self.id}' diff --git a/eap/serializers.py b/eap/serializers.py index 178fd7f5b..481fdd064 100644 --- a/eap/serializers.py +++ b/eap/serializers.py @@ -19,6 +19,7 @@ EarlyAction, EarlyActionIndicator, EAPDocument, + PrioritizedRisk, ) from main.writable_nested_serializers import ( @@ -54,6 +55,13 @@ class Meta: read_only_fields = ('early_action',) +class PrioritizedRiskSerializer(serializers.ModelSerializer): + class Meta: + model = PrioritizedRisk + fields = ('__all__') + read_only_fields = ('early_action',) + + class EarlyActionSerializer( NestedUpdateMixin, NestedCreateMixin, @@ -61,6 +69,7 @@ class EarlyActionSerializer( ): indicators = EarlyActionIndicatorSerializer(many=True, required=False) actions = ActionSerializer(source='action', many=True, required=False) + prioritized_risks = PrioritizedRiskSerializer(source='early_actions_prioritized_risk', many=True, required=False) sector_display = serializers.CharField(source='get_sector_display', read_only=True) class Meta: diff --git a/eap/test_views.py b/eap/test_views.py index 156da04ba..7b0e79503 100644 --- a/eap/test_views.py +++ b/eap/test_views.py @@ -4,7 +4,7 @@ from django.contrib.auth.models import User from main.test_case import APITestCase -from eap.models import EAP +from eap.models import EAP, EarlyAction from api.factories.country import CountryFactory from api.factories.district import DistrictFactory @@ -81,9 +81,16 @@ def setUp(self): "disaster_type": self.disaster_type.id, "early_actions": [ { - "sector": "Health", + "sector": EarlyAction.Sector.LIVELIHOODS, "budget_per_sector": 1000, - "prioritized_risk": "test", + "prioritized_risks": [ + { + "risks": "test1" + }, + { + "risks": "test2" + } + ], "targeted_people": 100, "readiness_activities": "test", "prepositioning_activities": "test", @@ -108,9 +115,16 @@ def setUp(self): }, { - "sector": "Health", + "sector": EarlyAction.Sector.MIGRATION, "budget_per_sector": 1000, - "prioritized_risk": "prioritized_risk", + "prioritized_risks": [ + { + "risks": "test1" + }, + { + "risks": "test2" + } + ], "targeted_people": 200, "readiness_activities": "test", "prepositioning_activities": "test", diff --git a/eap/views.py b/eap/views.py index fdc72bd51..7478f577a 100644 --- a/eap/views.py +++ b/eap/views.py @@ -1,9 +1,9 @@ # Create your views here. -from rest_framework.response import Response from rest_framework import ( views, viewsets, + response, permissions, mixins, ) @@ -11,6 +11,7 @@ EarlyActionIndicator, EAP, EAPDocument, + EarlyAction, ) from .serializers import ( EAPSerializer, @@ -38,27 +39,31 @@ def get_queryset(self): return EAP.objects.all().order_by('-created_at') -class EAPStatusOptions(views.APIView): - def get(self, request, format=None): - options = { - 'eap_status': [ - { - 'value': value, - 'label': label, - } for value, label in EAP.Status.choices - ] - } - return Response(options) - +class EAPOptionsView(views.APIView): + """ + Options for various attribute related to eap + """ + permission_classes = [permissions.IsAuthenticated] -class EarlyActionIndicatorOptions(views.APIView): - def get(self, request, format=None): + def get(self, request, version=None): options = { - 'eap_status': [ + "status": [ + { + "key": status.value, + "value": status.label + } for status in EAP.Status + ], + "early_actions_indicators": [ + { + "key": indicator.value, + "value": indicator.label + } for indicator in EarlyActionIndicator.IndicatorChoices + ], + "sectors": [ { - 'value': value, - 'label': label, - } for value, label in EarlyActionIndicator.IndicatorChoices.choices - ] + "key": sector.value, + "value": sector.label + } for sector in EarlyAction.Sector + ], } - return Response(options) + return response.Response(options) diff --git a/main/urls.py b/main/urls.py index 2e03a4c46..cdf557d9a 100644 --- a/main/urls.py +++ b/main/urls.py @@ -181,8 +181,7 @@ url(r'^api/v2/add_cronjob_log/', AddCronJobLog.as_view()), url(r'^api/v2/flash-update-options/', flash_views.FlashUpdateOptions.as_view()), url(r'^api/v2/export-flash-update/(?P\d+)/', flash_views.ExportFlashUpdateView.as_view()), - url(r'^api/v2/eap-status/', eap_views.EAPStatusOptions.as_view()), - url(r'^api/v2/eap-indicators/', eap_views.EarlyActionIndicatorOptions.as_view()), + url(r'^api/v2/eap-options/', eap_views.EAPOptionsView.as_view()), url(r'^register', NewRegistration.as_view()), # url(r'^createperform', CreatePerForm.as_view()), url(r'^updateperform', UpdatePerForm.as_view()), From fe19092181dd85949840b9ba3d6ab921e7a099bb Mon Sep 17 00:00:00 2001 From: rup Date: Wed, 20 Jul 2022 17:32:13 +0545 Subject: [PATCH 03/13] Modified create, update, get of field report api to accept eap activation: --- api/drf_views.py | 25 +++++++++++- api/models.py | 2 +- api/serializers.py | 11 ++++- api/test_views.py | 23 +++++++++++ eap/admin.py | 29 ++++++++++++-- ...715_0628.py => 0004_auto_20220715_1301.py} | 2 +- eap/migrations/0005_eapactivation.py | 39 ++++++++++++++++++ eap/models.py | 40 +++++++++++++++++++ eap/serializers.py | 12 ++++++ 9 files changed, 176 insertions(+), 7 deletions(-) rename eap/migrations/{0004_auto_20220715_0628.py => 0004_auto_20220715_1301.py} (97%) create mode 100644 eap/migrations/0005_eapactivation.py diff --git a/api/drf_views.py b/api/drf_views.py index 4efe2c543..bac82450d 100644 --- a/api/drf_views.py +++ b/api/drf_views.py @@ -18,6 +18,8 @@ from deployments.models import Personnel from databank.serializers import CountryOverviewSerializer +from eap.models import EAPActivation, EAP, EAPDocument + from .event_sources import SOURCES from .exceptions import BadRequest from .view_filters import ListFilter @@ -914,6 +916,14 @@ def create_event(self, report): report.save() return event + def create_eap_activation(self, eap_activation_data, fieldreport): + eap_id = eap_activation_data.pop('eap') + document_id = eap_activation_data.pop('document') + eap = EAP.objects.get(id=eap_id) + document = EAPDocument.objects.get(id=document_id) + eap_activation = EAPActivation.objects.create(eap=eap, field_report=fieldreport, document=document, **eap_activation_data) + return eap_activation + def create(self, request, *args, **kwargs): serializer = self.serialize(request.data) if not serializer.is_valid(): @@ -928,7 +938,9 @@ def create(self, request, *args, **kwargs): try: # TODO: Use serializer to create fieldreport + eap_activation_data = data.pop('eap_activation') fieldreport = FieldReport.objects.create(**data) + self.create_eap_activation(eap_activation_data, fieldreport) CreateFieldReportSerializer.trigger_field_translation(fieldreport) except Exception as e: try: @@ -970,12 +982,22 @@ def create(self, request, *args, **kwargs): return Response({'id': fieldreport.id}, status=HTTP_201_CREATED) +from eap.serializers import EAPActivationSerializer class UpdateFieldReport(UpdateAPIView, GenericFieldReportView): authentication_classes = (TokenAuthentication,) permission_classes = (IsAuthenticated,) queryset = FieldReport.objects.all() serializer_class = CreateFieldReportSerializer + def update_eap_activation(self, eap_activation_data, fieldreport): + eap_activation_data.pop('eap') + document_id = eap_activation_data.pop('document') + eap_activation_obj = EAPActivation.objects.get(field_report=fieldreport) + eap_activation = EAPActivationSerializer().update(eap_activation_obj, eap_activation_data) + eap_activation_obj.document = EAPDocument.objects.get(id=document_id) + eap_activation_obj.save(update_fields=['document']) + return eap_activation + def partial_update(self, request, *args, **kwargs): self.update(request, *args, **kwargs) @@ -989,7 +1011,8 @@ def update(self, request, *args, **kwargs): data, locations, meta, partners = self.map_many_to_many_relations(data) try: - serializer.save() + field_report = serializer.save() + self.update_eap_activation(request.data['eap_activation'], field_report) except Exception: logger.error('Faild to update field report', exc_info=True) raise BadRequest('Could not update field report') diff --git a/api/models.py b/api/models.py index e0c494e3b..5d6638c9b 100644 --- a/api/models.py +++ b/api/models.py @@ -1932,8 +1932,8 @@ class EmergencyOperationsFR(EmergencyOperationsBase): raw_shelter_people_targeted = None raw_water_sanitation_and_hygiene_people_targeted = None - # Fields for the cleaned data date_of_disaster = models.DateField(verbose_name=_('date of disaster'), null=True, blank=True) + # Fields for the cleaned data num_of_other_partner_involved = models.TextField(verbose_name=_('number of other partner involved'), null=True, blank=True) num_of_partner_ns_involved = models.TextField(verbose_name=_('number of NS partner involved'), null=True, blank=True) operation_end_date = models.DateField(verbose_name=_('operation end date'), null=True, blank=True) diff --git a/api/serializers.py b/api/serializers.py index 4e3577e73..d36aae7b7 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -54,6 +54,7 @@ ) from notifications.models import Subscription from deployments.models import EmergencyProject +from eap.models import EAPActivation class GeoSerializerMixin: @@ -1059,7 +1060,7 @@ class Meta: model = FieldReport fields = '__all__' - +from eap.serializers import EAPActivationSerializer class DetailFieldReportSerializer(FieldReportEnumDisplayMixin, ModelSerializer): user = UserSerializer() dtype = DisasterTypeSerializer() @@ -1071,6 +1072,13 @@ class DetailFieldReportSerializer(FieldReportEnumDisplayMixin, ModelSerializer): districts = MiniDistrictSerializer(many=True) external_partners = ExternalPartnerSerializer(many=True) supported_activities = SupportedActivitySerializer(many=True) + eap_activation = serializers.SerializerMethodField('get_eap_activation') + + @staticmethod + def get_eap_activation(obj): + eap_activation = EAPActivation.objects.get(field_report=obj) + eap_activation_data = EAPActivationSerializer(eap_activation).data + return eap_activation_data class Meta: model = FieldReport @@ -1078,6 +1086,7 @@ class Meta: class CreateFieldReportSerializer(FieldReportEnumDisplayMixin, ModelSerializer): + class Meta: model = FieldReport fields = '__all__' diff --git a/api/test_views.py b/api/test_views.py index f20bca429..9b6c9d1f8 100644 --- a/api/test_views.py +++ b/api/test_views.py @@ -12,6 +12,7 @@ EventLinkFactory, AppealFactory ) +from eap.factories import EAPFactory, EAPDocumentFactory class AuthTokenTest(APITestCase): @@ -82,6 +83,10 @@ class FieldReportTest(APITestCase): def test_create_and_update(self): user = User.objects.create(username='jo') region = models.Region.objects.create(name=1) + eap1 = EAPFactory.create(created_by=user) + eap2 = EAPFactory.create(created_by=user) + document1 = EAPDocumentFactory.create(created_by=self.user) + document2 = EAPDocumentFactory.create(created_by=self.user) country1 = models.Country.objects.create(name='abc', region=region) country2 = models.Country.objects.create(name='xyz') body = { @@ -105,6 +110,13 @@ def test_create_and_update(self): {'ctype': 'Originator', 'name': 'jo', 'title': 'head', 'email': '123'} ], 'user': user.id, + 'eap_activation': { + 'title': 'eap activation title', + 'eap': eap1.id, + 'description': 'test eap description', + 'trigger_met_date': '2022-11-11 00:00', + 'document': document1.id, + } } self.client.force_authenticate(user=user) with self.capture_on_commit_callbacks(execute=True): @@ -149,6 +161,17 @@ def test_create_and_update(self): ] body['actions_taken'] = [] body['visibility'] = models.VisibilityChoices.PUBLIC + body['eap_activation'] = { + 'title': 'eap activation title updated', + 'eap': eap2.id, + 'description': 'test eap description updated', + 'trigger_met_date': '2022-11-11 01:00', + 'document': document2.id, + 'originator_name': 'test name', + 'nsc_name_operational': 'test name operational', + 'ifrc_focal_email': 'testemail@gmail.com' + } + response = self.client.put(f'/api/v2/update_field_report/{created.id}/', body, format='json').json() updated = models.FieldReport.objects.get(pk=response['id']) diff --git a/eap/admin.py b/eap/admin.py index 2f5179ab5..5363faff2 100644 --- a/eap/admin.py +++ b/eap/admin.py @@ -1,10 +1,33 @@ from django.contrib import admin # Register your models here. -from .models import EAP + +from .models import ( + EAP, + EAPPartner, + EAPReference, + EAPDocument, +) + + +class ReferenceAdminInline(admin.TabularInline): + model = EAPReference + extra = 0 + + +class PartnerAdminInline(admin.TabularInline): + model = EAPPartner + extra = 0 + + +@admin.register(EAPDocument) +class EAPDocumentAdmin(admin.ModelAdmin): + model = EAPDocument + extra = 0 @admin.register(EAP) class EAPAdmin(admin.ModelAdmin): - list_display = ('eap_number', 'country', 'status', 'operational_timeframe', 'total_budget') - + list_display = ('eap_number', 'country', 'status', 'operational_timeframe',) + inlines = [ReferenceAdminInline, PartnerAdminInline] + autocomplete_fields = ('country', 'district', 'disaster_type', 'created_by', 'modified_by') diff --git a/eap/migrations/0004_auto_20220715_0628.py b/eap/migrations/0004_auto_20220715_1301.py similarity index 97% rename from eap/migrations/0004_auto_20220715_0628.py rename to eap/migrations/0004_auto_20220715_1301.py index 60be4f4ee..ebd6dcdc4 100644 --- a/eap/migrations/0004_auto_20220715_0628.py +++ b/eap/migrations/0004_auto_20220715_1301.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.28 on 2022-07-15 06:28 +# Generated by Django 2.2.28 on 2022-07-15 13:01 from django.db import migrations, models import django.db.models.deletion diff --git a/eap/migrations/0005_eapactivation.py b/eap/migrations/0005_eapactivation.py new file mode 100644 index 000000000..e142cf076 --- /dev/null +++ b/eap/migrations/0005_eapactivation.py @@ -0,0 +1,39 @@ +# Generated by Django 2.2.28 on 2022-07-19 11:06 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0156_appealfilter_comment'), + ('eap', '0004_auto_20220715_1301'), + ] + + operations = [ + migrations.CreateModel( + name='EAPActivation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(blank=True, max_length=250, null=True)), + ('trigger_met_date', models.DateTimeField(verbose_name='Date the trigger was met')), + ('description', models.TextField(verbose_name='Description of EAP Activation')), + ('originator_name', models.CharField(blank=True, max_length=250, null=True, verbose_name='Originator name')), + ('originator_title', models.CharField(blank=True, max_length=250, null=True, verbose_name='Originator title')), + ('originator_email', models.CharField(blank=True, max_length=250, null=True, verbose_name='Originator email')), + ('nsc_name_operational', models.CharField(blank=True, max_length=250, null=True, verbose_name='National Society Contact (Operational)')), + ('nsc_title_operational', models.CharField(blank=True, max_length=250, null=True, verbose_name='National Society Contact (Operational)')), + ('nsc_email_operational', models.CharField(blank=True, max_length=250, null=True, verbose_name='National Society Contact (Operational)')), + ('nsc_name_secretary', models.CharField(blank=True, max_length=250, null=True, verbose_name='National Society Contact (Secretary)')), + ('nsc_title_secretary', models.CharField(blank=True, max_length=250, null=True, verbose_name='National Society Contact (Secretary)')), + ('nsc_email_secretary', models.CharField(blank=True, max_length=250, null=True, verbose_name='National Society Contact (Secretary)')), + ('ifrc_focal_name', models.CharField(blank=True, max_length=250, null=True, verbose_name='IFRC Focal Point Name')), + ('ifrc_focal_title', models.CharField(blank=True, max_length=250, null=True, verbose_name='IFRC Focal Point Title')), + ('ifrc_focal_email', models.CharField(blank=True, max_length=250, null=True, verbose_name='IFRC Focal Point Email')), + ('document', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='eap_activation_document', to='eap.EAPDocument', verbose_name='EAP Activation Documents')), + ('eap', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='eap_activation', to='eap.EAP', verbose_name='eap')), + ('field_report', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='field_report_eap_activation', to='api.FieldReport', verbose_name='field report')), + ], + ), + ] diff --git a/eap/models.py b/eap/models.py index 241ee50ce..5cae91457 100644 --- a/eap/models.py +++ b/eap/models.py @@ -8,6 +8,7 @@ Country, District, DisasterType, + FieldReport, ) @@ -210,3 +211,42 @@ class Meta: def __str__(self): return f'{self.id}' + + +class EAPActivation(models.Model): + title = models.CharField(max_length=250, null=True, blank=True) + field_report = models.ForeignKey( + FieldReport, + on_delete=models.SET_NULL, + related_name='field_report_eap_activation', + verbose_name=_('field report'), + null=True, blank=True + ) + eap = models.ForeignKey( + EAP, + on_delete=models.SET_NULL, + related_name='eap_activation', + verbose_name=_('eap'), + null=True, blank=True + ) + trigger_met_date = models.DateTimeField(verbose_name=_('Date the trigger was met')) + description = models.TextField(verbose_name=_('Description of EAP Activation')) + document = models.ForeignKey( + EAPDocument, + on_delete=models.SET_NULL, + verbose_name=_('EAP Activation Documents'), + related_name='eap_activation_document', + null=True, blank=True + ) + originator_name = models.CharField(max_length=250, verbose_name=_('Originator name'), null=True, blank=True) + originator_title = models.CharField(max_length=250, verbose_name=_('Originator title'), null=True, blank=True) + originator_email = models.CharField(max_length=250, verbose_name=_('Originator email'), null=True, blank=True) + nsc_name_operational = models.CharField(max_length=250, verbose_name=_('National Society Contact (Operational)'), null=True, blank=True) + nsc_title_operational = models.CharField(max_length=250, verbose_name=_('National Society Contact (Operational)'), null=True, blank=True) + nsc_email_operational = models.CharField(max_length=250, verbose_name=_('National Society Contact (Operational)'), null=True, blank=True) + nsc_name_secretary = models.CharField(max_length=250, verbose_name=_('National Society Contact (Secretary)'), null=True, blank=True) + nsc_title_secretary = models.CharField(max_length=250, verbose_name=_('National Society Contact (Secretary)'), null=True, blank=True) + nsc_email_secretary = models.CharField(max_length=250, verbose_name=_('National Society Contact (Secretary)'), null=True, blank=True) + ifrc_focal_name = models.CharField(max_length=250, verbose_name=_('IFRC Focal Point Name'), null=True, blank=True) + ifrc_focal_title = models.CharField(max_length=250, verbose_name=_('IFRC Focal Point Title'), null=True, blank=True) + ifrc_focal_email = models.CharField(max_length=250, verbose_name=_('IFRC Focal Point Email'), null=True, blank=True) diff --git a/eap/serializers.py b/eap/serializers.py index 481fdd064..cfaa56ec7 100644 --- a/eap/serializers.py +++ b/eap/serializers.py @@ -20,6 +20,7 @@ EarlyActionIndicator, EAPDocument, PrioritizedRisk, + EAPActivation, ) from main.writable_nested_serializers import ( @@ -136,3 +137,14 @@ def update(self, instance, validated_data): eap = super().update(instance, validated_data) return eap + +class EAPActivationSerializer(serializers.ModelSerializer): + document_detail = EAPDocumentSerializer(source='document', read_only=True) + + class Meta: + model = EAPActivation + exclude = ('eap', 'field_report') + + def update(self, instance, validated_data): + eap_activation = super().update(instance, validated_data) + return eap_activation From 4b3f01354687d2521a297e7778f98cc7d435fa9b Mon Sep 17 00:00:00 2001 From: rup Date: Thu, 21 Jul 2022 12:22:40 +0545 Subject: [PATCH 04/13] Modified test case for field report to check EAP Activation Data - Solve issue for empty document for eap activation create and update --- api/drf_views.py | 36 +++++++++++++++++++++--------------- api/serializers.py | 3 ++- api/test_views.py | 31 ++++++++++++++++++++++++++++++- eap/serializers.py | 3 --- 4 files changed, 53 insertions(+), 20 deletions(-) diff --git a/api/drf_views.py b/api/drf_views.py index bac82450d..1f3966997 100644 --- a/api/drf_views.py +++ b/api/drf_views.py @@ -916,12 +916,15 @@ def create_event(self, report): report.save() return event - def create_eap_activation(self, eap_activation_data, fieldreport): - eap_id = eap_activation_data.pop('eap') - document_id = eap_activation_data.pop('document') - eap = EAP.objects.get(id=eap_id) - document = EAPDocument.objects.get(id=document_id) - eap_activation = EAPActivation.objects.create(eap=eap, field_report=fieldreport, document=document, **eap_activation_data) + def create_eap_activation(self, data, fieldreport): + eap = EAP.objects.filter(id=data.pop('eap', None)).first() + document = EAPDocument.objects.filter(id=data.pop('document', None)).first() + eap_activation = EAPActivation.objects.create( + eap=eap, + field_report=fieldreport, + document=document, + **data + ) return eap_activation def create(self, request, *args, **kwargs): @@ -940,7 +943,7 @@ def create(self, request, *args, **kwargs): # TODO: Use serializer to create fieldreport eap_activation_data = data.pop('eap_activation') fieldreport = FieldReport.objects.create(**data) - self.create_eap_activation(eap_activation_data, fieldreport) + self.create_eap_activation(eap_activation_data, fieldreport) # activate respected eap for field report CreateFieldReportSerializer.trigger_field_translation(fieldreport) except Exception as e: try: @@ -982,20 +985,22 @@ def create(self, request, *args, **kwargs): return Response({'id': fieldreport.id}, status=HTTP_201_CREATED) -from eap.serializers import EAPActivationSerializer +from eap.serializers import EAPActivationSerializer # It is imported here to avoid circular import issue class UpdateFieldReport(UpdateAPIView, GenericFieldReportView): authentication_classes = (TokenAuthentication,) permission_classes = (IsAuthenticated,) queryset = FieldReport.objects.all() serializer_class = CreateFieldReportSerializer - def update_eap_activation(self, eap_activation_data, fieldreport): - eap_activation_data.pop('eap') - document_id = eap_activation_data.pop('document') - eap_activation_obj = EAPActivation.objects.get(field_report=fieldreport) - eap_activation = EAPActivationSerializer().update(eap_activation_obj, eap_activation_data) - eap_activation_obj.document = EAPDocument.objects.get(id=document_id) - eap_activation_obj.save(update_fields=['document']) + # function for updating eap-activate in field report + def update_eap_activation(self, data, fieldreport): + eap_id = data.pop('eap', None) + document_id = data.pop('document', None) + instance = EAPActivation.objects.get(field_report=fieldreport) + eap_activation = EAPActivationSerializer().update(instance, data) + instance.document = EAPDocument.objects.filter(id=document_id).first() + instance.eap = EAP.objects.filter(id=eap_id).first() + instance.save(update_fields=['document', 'eap']) return eap_activation def partial_update(self, request, *args, **kwargs): @@ -1012,6 +1017,7 @@ def update(self, request, *args, **kwargs): try: field_report = serializer.save() + # update respected eap-activation self.update_eap_activation(request.data['eap_activation'], field_report) except Exception: logger.error('Faild to update field report', exc_info=True) diff --git a/api/serializers.py b/api/serializers.py index d36aae7b7..25fed5b45 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -1060,7 +1060,8 @@ class Meta: model = FieldReport fields = '__all__' -from eap.serializers import EAPActivationSerializer + +from eap.serializers import EAPActivationSerializer # It is imported here to avoid circular import issue class DetailFieldReportSerializer(FieldReportEnumDisplayMixin, ModelSerializer): user = UserSerializer() dtype = DisasterTypeSerializer() diff --git a/api/test_views.py b/api/test_views.py index 9b6c9d1f8..862d4e159 100644 --- a/api/test_views.py +++ b/api/test_views.py @@ -13,6 +13,7 @@ AppealFactory ) from eap.factories import EAPFactory, EAPDocumentFactory +from eap.models import EAPActivation class AuthTokenTest(APITestCase): @@ -64,7 +65,6 @@ def test_sit_rep_types(self): # Filter by event response = self.client.get('/api/v2/situation_report/?limit=100&event=%s' % event1.id) - print(response) self.assertEqual(response.status_code, 200) count = response.json()['count'] self.assertEqual(count, 3) @@ -116,6 +116,15 @@ def test_create_and_update(self): 'description': 'test eap description', 'trigger_met_date': '2022-11-11 00:00', 'document': document1.id, + 'originator_name': 'test name', + 'originator_title': 'test originator title', + 'originator_email': 'test@email.com', + 'nsc_name_operational': 'test name operational', + 'nsc_title_operational': 'test nsc operational', + 'nsc_email_operational': 'test nsc operational', + 'ifrc_focal_name': 'test focal name', + 'ifrc_focal_title': 'test focal title', + 'ifrc_focal_email': 'testemail@gmail.com' } } self.client.force_authenticate(user=user) @@ -149,6 +158,13 @@ def test_create_and_update(self): self.aws_translator._fake_translation('test', 'es', 'en') ) + # check eap-activation data + eap_activation_obj = EAPActivation.objects.get(field_report=created) + self.assertEqual(eap_activation_obj.title, 'eap activation title') + self.assertEqual(eap_activation_obj.eap.id, eap1.id) + self.assertEqual(eap_activation_obj.document.id, document1.id) + self.assertEqual(eap_activation_obj.field_report.id, created.id) + # created an emergency automatically self.assertEqual(created.event.name, 'test') event_pk = created.event.id @@ -168,7 +184,13 @@ def test_create_and_update(self): 'trigger_met_date': '2022-11-11 01:00', 'document': document2.id, 'originator_name': 'test name', + 'originator_title': 'test originator title', + 'originator_email': 'test@email.com', 'nsc_name_operational': 'test name operational', + 'nsc_title_operational': 'test nsc operational', + 'nsc_email_operational': 'test nsc operational', + 'ifrc_focal_name': 'test focal name', + 'ifrc_focal_title': 'test focal title', 'ifrc_focal_email': 'testemail@gmail.com' } @@ -200,6 +222,13 @@ def test_create_and_update(self): self.aws_translator._fake_translation('this is a test description', 'es', 'en'), ) # This has not been reset + # check eap-activation data + eap_activation_obj = EAPActivation.objects.get(field_report=updated) + self.assertEqual(eap_activation_obj.title, 'eap activation title updated') + self.assertEqual(eap_activation_obj.eap.id, eap2.id) + self.assertEqual(eap_activation_obj.document.id, document2.id) + self.assertEqual(eap_activation_obj.field_report.id, updated.id) + body['summary'] = 'test [updated again]' with self.capture_on_commit_callbacks(execute=True): response = self.client.put(f'/api/v2/update_field_report/{created.id}/', body, format='json').json() diff --git a/eap/serializers.py b/eap/serializers.py index cfaa56ec7..c49b995ce 100644 --- a/eap/serializers.py +++ b/eap/serializers.py @@ -145,6 +145,3 @@ class Meta: model = EAPActivation exclude = ('eap', 'field_report') - def update(self, instance, validated_data): - eap_activation = super().update(instance, validated_data) - return eap_activation From ca4ab7a851480d440e7937bc4859d072618cc3c2 Mon Sep 17 00:00:00 2001 From: rup Date: Thu, 21 Jul 2022 13:45:47 +0545 Subject: [PATCH 05/13] Add new choice field EAP Activation in field report model --- api/migrations/0157_auto_20220721_0754.py | 18 ++++++++++++++++++ api/models.py | 1 + api/views.py | 1 - eap/serializers.py | 4 +++- 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 api/migrations/0157_auto_20220721_0754.py diff --git a/api/migrations/0157_auto_20220721_0754.py b/api/migrations/0157_auto_20220721_0754.py new file mode 100644 index 000000000..9a632a7eb --- /dev/null +++ b/api/migrations/0157_auto_20220721_0754.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.28 on 2022-07-21 07:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0156_appealfilter_comment'), + ] + + operations = [ + migrations.AlterField( + model_name='fieldreport', + name='status', + field=models.IntegerField(choices=[(0, 'Unknown'), (2, 'Two'), (3, 'Three'), (8, 'Early Warning'), (9, 'Event-related'), (10, 'Ten'), (11, 'EAP Activation')], default=0, verbose_name='type'), + ), + ] diff --git a/api/models.py b/api/models.py index 5d6638c9b..b4f0f0f22 100644 --- a/api/models.py +++ b/api/models.py @@ -1226,6 +1226,7 @@ class Status(IntegerChoices): EW = 8, _('Early Warning') EVT = 9, _('Event-related') TEN = 10, _('Ten') # legacy usage. Covid? + EAP_ACTV = 11, _('EAP Activation') # EAP Activation user = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_('user'), related_name='user', diff --git a/api/views.py b/api/views.py index 764e4787e..4d5c83b1e 100644 --- a/api/views.py +++ b/api/views.py @@ -127,7 +127,6 @@ def get(self, request): } } } - results = ES_CLIENT.search( index=index, doc_type='page', diff --git a/eap/serializers.py b/eap/serializers.py index c49b995ce..0c417db21 100644 --- a/eap/serializers.py +++ b/eap/serializers.py @@ -44,6 +44,8 @@ class Meta: class EarlyActionIndicatorSerializer(serializers.ModelSerializer): + indicator_display = serializers.CharField(source='get_indicator_display', read_only=True) + class Meta: model = EarlyActionIndicator fields = ('__all__') @@ -100,7 +102,6 @@ def create(self, validated_data): class EAPSerializer( - EnumSupportSerializerMixin, NestedUpdateMixin, NestedCreateMixin, serializers.ModelSerializer @@ -113,6 +114,7 @@ class EAPSerializer( created_by_details = UserNameSerializer(source='created_by', read_only=True) hazard_type_details = DisasterTypeSerializer(source='disaster_type', read_only=True) document_details = EAPDocumentSerializer(source='document', read_only=True, required=False) + status_display = serializers.CharField(source='get_status_display', read_only=True) class Meta: model = EAP From 21855a88439fd0842efbe69f3571a4c6b6677468 Mon Sep 17 00:00:00 2001 From: rup Date: Thu, 28 Jul 2022 17:03:39 +0545 Subject: [PATCH 06/13] Add models for EAP Activation Report --- eap/models.py | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/eap/models.py b/eap/models.py index 5cae91457..2a8ef9105 100644 --- a/eap/models.py +++ b/eap/models.py @@ -250,3 +250,73 @@ class EAPActivation(models.Model): ifrc_focal_name = models.CharField(max_length=250, verbose_name=_('IFRC Focal Point Name'), null=True, blank=True) ifrc_focal_title = models.CharField(max_length=250, verbose_name=_('IFRC Focal Point Title'), null=True, blank=True) ifrc_focal_email = models.CharField(max_length=250, verbose_name=_('IFRC Focal Point Email'), null=True, blank=True) + + +class EAPOperationalPlan(models.Model): + early_action = models.OneToOneField( + EarlyAction, + on_delete=models.SET_NULL, + null=True, + blank=True + ) + budget = models.IntegerField(verbose_name=_('Budget per sector (CHF)'), null=True, blank=True) + indicator_value = models.IntegerField(verbose_name=_('Indicator'), null=True, blank=True) + no_of_people_reached = models.IntegerField(verbose_name=_('People Reached'), null=True, blank=True) + readiness_activities_achievements = models.TextField(verbose_name=_('Readiness Activities Achievements'), null=True, blank=True) + prepo_activities_achievements = models.TextField(verbose_name=_('Pre-positioning Activities Achievements'), null=True, blank=True) + + class Meta: + verbose_name = _('EAP Operational Plan') + verbose_name_plural = _('EAP Operational Plans') + + def __str__(self): + return f'{self.id}' + + +class ActionAchievements(models.Model): + operational_plan = models.ForeignKey( + EAPOperationalPlan, on_delete=models.SET_NULL, + related_name="action_achievement", verbose_name=_('Action Achievement'), + null=True, blank=True + ) + action = models.OneToOneField(Action, on_delete=models.SET_NULL, null=True, blank=True) + early_act_achievement = models.TextField(verbose_name=_('Early Actions Achievements'), null=True, blank=True) + + class Meta: + verbose_name = _('Action Achievement') + verbose_name_plural = _('Action Achievements') + + def __str__(self): + return f'{self.id}' + + +class EAPActivationReport(models.Model): + created_at = models.DateTimeField(verbose_name=_('created at'), auto_now_add=True) + modified_at = models.DateTimeField(verbose_name=_('updated at'), auto_now=True) + eap_activation = models.ForeignKey( + EAPActivation, + on_delete=models.SET_NULL, + verbose_name=_('EAP Activation Report'), + related_name='eap_activation_report', + null=True, blank=True + ) + number_of_people_reached = models.IntegerField(verbose_name=_('Number Of People Reached')) + description = models.TextField(verbose_name=_('Description of Event & Overview of Implementation')) + overall_objectives = models.TextField(verbose_name=_('Overall Objective of the Intervention')) + document = models.ForeignKey( + EAPDocument, + on_delete=models.SET_NULL, + verbose_name=_('EAP Activation Report Document'), + related_name='eap_activation_report_document', + null=True, blank=True + ) + challenges_and_lesson = models.TextField(verbose_name=_('Challenges & Lesson Learned per Sector')) + general_lesson_and_recomendations = models.TextField(verbose_name=_('General Lessons Learned and Recomendations')) + ifrc_financial_report = models.ForeignKey( + EAPDocument, + on_delete=models.SET_NULL, + verbose_name=_('IFRC Financial Report'), + related_name='eap_activation_ifrc_report', + null=True, blank=True + ) + From 7122f275cf014e2a4d61e2b74a57bcb734f6d34f Mon Sep 17 00:00:00 2001 From: rup Date: Tue, 2 Aug 2022 14:52:19 +0545 Subject: [PATCH 07/13] Added create, update, get api for EAP final report --- api/drf_views.py | 20 ++- api/test_views.py | 6 +- eap/admin.py | 2 +- eap/factories.py | 48 ++++++- eap/migrations/0006_auto_20220802_0835.py | 146 ++++++++++++++++++++++ eap/models.py | 82 +++++++++--- eap/serializers.py | 62 ++++++++- eap/test_views.py | 79 +++++++++--- eap/views.py | 10 ++ main/urls.py | 1 + 10 files changed, 406 insertions(+), 50 deletions(-) create mode 100644 eap/migrations/0006_auto_20220802_0835.py diff --git a/api/drf_views.py b/api/drf_views.py index 1f3966997..db9944003 100644 --- a/api/drf_views.py +++ b/api/drf_views.py @@ -918,13 +918,15 @@ def create_event(self, report): def create_eap_activation(self, data, fieldreport): eap = EAP.objects.filter(id=data.pop('eap', None)).first() - document = EAPDocument.objects.filter(id=data.pop('document', None)).first() + documents = EAPDocument.objects.filter(id__in=data.pop('documents', None)) eap_activation = EAPActivation.objects.create( eap=eap, field_report=fieldreport, - document=document, **data ) + if documents: + for document in documents: + eap_activation.documents.add(document) return eap_activation def create(self, request, *args, **kwargs): @@ -985,7 +987,6 @@ def create(self, request, *args, **kwargs): return Response({'id': fieldreport.id}, status=HTTP_201_CREATED) -from eap.serializers import EAPActivationSerializer # It is imported here to avoid circular import issue class UpdateFieldReport(UpdateAPIView, GenericFieldReportView): authentication_classes = (TokenAuthentication,) permission_classes = (IsAuthenticated,) @@ -994,13 +995,19 @@ class UpdateFieldReport(UpdateAPIView, GenericFieldReportView): # function for updating eap-activate in field report def update_eap_activation(self, data, fieldreport): + from eap.serializers import EAPActivationSerializer + eap_id = data.pop('eap', None) - document_id = data.pop('document', None) + document_ids = data.pop('documents', None) instance = EAPActivation.objects.get(field_report=fieldreport) eap_activation = EAPActivationSerializer().update(instance, data) - instance.document = EAPDocument.objects.filter(id=document_id).first() instance.eap = EAP.objects.filter(id=eap_id).first() - instance.save(update_fields=['document', 'eap']) + if document_ids: + documents = EAPDocument.objects.filter(id__in=document_ids) + for document in documents: + instance.documents.add(document) + instance.save(update_fields=['eap']) + return eap_activation def partial_update(self, request, *args, **kwargs): @@ -1074,6 +1081,7 @@ class GoHistoricalViewSet(viewsets.ReadOnlyModelViewSet): def get_queryset(self): return Event.objects.filter(appeals__isnull=False) + class CountryOfFieldReportToReviewViewset(viewsets.ReadOnlyModelViewSet): queryset = CountryOfFieldReportToReview.objects.order_by('country') serializer_class = CountryOfFieldReportToReviewSerializer diff --git a/api/test_views.py b/api/test_views.py index 862d4e159..ee2dabe04 100644 --- a/api/test_views.py +++ b/api/test_views.py @@ -115,7 +115,7 @@ def test_create_and_update(self): 'eap': eap1.id, 'description': 'test eap description', 'trigger_met_date': '2022-11-11 00:00', - 'document': document1.id, + 'documents': [document1.id], 'originator_name': 'test name', 'originator_title': 'test originator title', 'originator_email': 'test@email.com', @@ -162,7 +162,6 @@ def test_create_and_update(self): eap_activation_obj = EAPActivation.objects.get(field_report=created) self.assertEqual(eap_activation_obj.title, 'eap activation title') self.assertEqual(eap_activation_obj.eap.id, eap1.id) - self.assertEqual(eap_activation_obj.document.id, document1.id) self.assertEqual(eap_activation_obj.field_report.id, created.id) # created an emergency automatically @@ -182,7 +181,7 @@ def test_create_and_update(self): 'eap': eap2.id, 'description': 'test eap description updated', 'trigger_met_date': '2022-11-11 01:00', - 'document': document2.id, + 'documents': [document2.id], 'originator_name': 'test name', 'originator_title': 'test originator title', 'originator_email': 'test@email.com', @@ -226,7 +225,6 @@ def test_create_and_update(self): eap_activation_obj = EAPActivation.objects.get(field_report=updated) self.assertEqual(eap_activation_obj.title, 'eap activation title updated') self.assertEqual(eap_activation_obj.eap.id, eap2.id) - self.assertEqual(eap_activation_obj.document.id, document2.id) self.assertEqual(eap_activation_obj.field_report.id, updated.id) body['summary'] = 'test [updated again]' diff --git a/eap/admin.py b/eap/admin.py index 5363faff2..41bd34ac2 100644 --- a/eap/admin.py +++ b/eap/admin.py @@ -30,4 +30,4 @@ class EAPDocumentAdmin(admin.ModelAdmin): class EAPAdmin(admin.ModelAdmin): list_display = ('eap_number', 'country', 'status', 'operational_timeframe',) inlines = [ReferenceAdminInline, PartnerAdminInline] - autocomplete_fields = ('country', 'district', 'disaster_type', 'created_by', 'modified_by') + autocomplete_fields = ('country', 'districts', 'disaster_type', 'created_by', 'modified_by') diff --git a/eap/factories.py b/eap/factories.py index 59ab863a3..fcd2d88ce 100644 --- a/eap/factories.py +++ b/eap/factories.py @@ -9,11 +9,13 @@ disaster_type, country, district, + field_report, ) from .models import ( EAP, EAPDocument, + EAPActivation, ) @@ -34,10 +36,9 @@ class EAPFactory(factory.django.DjangoModelFactory): class Meta: model = EAP - district = factory.SubFactory(district.DistrictFactory) + # districts = factory.SubFactory(district.DistrictFactory) country = factory.SubFactory(country.CountryFactory) disaster_type = factory.SubFactory(disaster_type.DisasterTypeFactory) - document = factory.SubFactory(EAPDocumentFactory) eap_number = fuzzy.FuzzyText(length=20) approval_date = fuzzy.FuzzyDateTime(datetime.datetime(2008, 1, 1, tzinfo=timezone.utc)) status = fuzzy.FuzzyChoice(EAP.Status) @@ -74,3 +75,46 @@ def early_actions(self, create, extracted, **kwargs): if extracted: for early_action in extracted: self.early_actions.add(early_action) + + @factory.post_generation + def documents(self, create, extracted, **kwargs): + if not create: + return + + if extracted: + for document in extracted: + self.documents.add(document) + + +class EAPActivationFactory(factory.django.DjangoModelFactory): + class Meta: + model = EAPActivation + + title = fuzzy.FuzzyText(length=20) + field_report = factory.SubFactory(field_report.FieldReportFactory) + eap = factory.SubFactory(EAPFactory) + trigger_met_date = fuzzy.FuzzyDateTime(datetime.datetime(2008, 1, 1, tzinfo=timezone.utc)) + description = fuzzy.FuzzyText(length=50) + + originator_name = fuzzy.FuzzyText(length=50) + description = fuzzy.FuzzyText(length=50) + originator_email = fuzzy.FuzzyText(length=50) + + nsc_name_operational = fuzzy.FuzzyText(length=50) + nsc_title_operational = fuzzy.FuzzyText(length=50) + nsc_email_operational = fuzzy.FuzzyText(length=50) + + nsc_name_secretary = fuzzy.FuzzyText(length=50) + nsc_title_secretary = fuzzy.FuzzyText(length=50) + nsc_email_secretary = fuzzy.FuzzyText(length=50) + + @factory.post_generation + def documents(self, create, extracted, **kwargs): + if not create: + return + + if extracted: + for document in extracted: + self.documents.add(document) + + diff --git a/eap/migrations/0006_auto_20220802_0835.py b/eap/migrations/0006_auto_20220802_0835.py new file mode 100644 index 000000000..29fd51503 --- /dev/null +++ b/eap/migrations/0006_auto_20220802_0835.py @@ -0,0 +1,146 @@ +# Generated by Django 2.2.28 on 2022-08-02 08:35 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0157_auto_20220721_0754'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('eap', '0005_eapactivation'), + ] + + operations = [ + migrations.CreateModel( + name='EAPOperationalPlan', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('budget', models.IntegerField(blank=True, null=True, verbose_name='Budget per sector (CHF)')), + ('value', models.IntegerField(blank=True, null=True, verbose_name='value')), + ('no_of_people_reached', models.IntegerField(blank=True, null=True, verbose_name='People Reached')), + ('readiness_activities_achievements', models.TextField(blank=True, null=True, verbose_name='Readiness Activities Achievements')), + ('prepo_activities_achievements', models.TextField(blank=True, null=True, verbose_name='Pre-positioning Activities Achievements')), + ('client_id', models.CharField(blank=True, max_length=50, null=True)), + ], + options={ + 'verbose_name': 'EAP Operational Plan', + 'verbose_name_plural': 'EAP Operational Plans', + }, + ), + migrations.RemoveField( + model_name='eap', + name='district', + ), + migrations.RemoveField( + model_name='eap', + name='document', + ), + migrations.RemoveField( + model_name='eapactivation', + name='document', + ), + migrations.AddField( + model_name='action', + name='client_id', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AddField( + model_name='eap', + name='districts', + field=models.ManyToManyField(related_name='eap_district', to='api.District', verbose_name='districts'), + ), + migrations.AddField( + model_name='eap', + name='documents', + field=models.ManyToManyField(blank=True, related_name='eap_document', to='eap.EAPDocument', verbose_name='EAP Documents'), + ), + migrations.AddField( + model_name='eapactivation', + name='documents', + field=models.ManyToManyField(blank=True, related_name='eap_activation_document', to='eap.EAPDocument', verbose_name='EAP Activation Documents'), + ), + migrations.AddField( + model_name='eapdocument', + name='client_id', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AddField( + model_name='eappartner', + name='client_id', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AddField( + model_name='eapreference', + name='client_id', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AddField( + model_name='earlyaction', + name='client_id', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AddField( + model_name='earlyactionindicator', + name='client_id', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AddField( + model_name='prioritizedrisk', + name='client_id', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.CreateModel( + name='OperationalPlanIndicator', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('indicator_value', models.IntegerField(blank=True, null=True)), + ('indicator', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='eap.EarlyActionIndicator')), + ('operational_plan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='operational_plan_indicator', to='eap.EAPOperationalPlan', verbose_name='Operational Plan')), + ], + options={ + 'verbose_name': 'Operational Indicator', + 'verbose_name_plural': 'Operational Indicators', + }, + ), + migrations.AddField( + model_name='eapoperationalplan', + name='early_action', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='eap.EarlyAction'), + ), + migrations.CreateModel( + name='EAPActivationReport', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('number_of_people_reached', models.IntegerField(verbose_name='Number Of People Reached')), + ('description', models.TextField(verbose_name='Description of Event & Overview of Implementation')), + ('overall_objectives', models.TextField(verbose_name='Overall Objective of the Intervention')), + ('challenges_and_lesson', models.TextField(verbose_name='Challenges & Lesson Learned per Sector')), + ('general_lesson_and_recomendations', models.TextField(verbose_name='General Lessons Learned and Recomendations')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='eap_act_report_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created by')), + ('documents', models.ManyToManyField(blank=True, related_name='eap_act_reports', to='eap.EAPDocument', verbose_name='EAP Activation Report Document')), + ('eap_activation', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='eap_activation_report', to='eap.EAPActivation', verbose_name='EAP Activation Report')), + ('ifrc_financial_report', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='eap_activation_ifrc_report', to='eap.EAPDocument', verbose_name='IFRC Financial Report')), + ('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='eap_act_modified_by', to=settings.AUTH_USER_MODEL, verbose_name='Modified by')), + ('operational_plans', models.ManyToManyField(blank=True, to='eap.EAPOperationalPlan', verbose_name='Operational Plans')), + ], + ), + migrations.CreateModel( + name='ActionAchievements', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('early_act_achievement', models.TextField(blank=True, null=True, verbose_name='Early Actions Achievements')), + ('client_id', models.CharField(blank=True, max_length=50, null=True)), + ('action', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='eap.Action')), + ('operational_plan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='action_achievement', to='eap.EAPOperationalPlan', verbose_name='Action Achievement')), + ], + options={ + 'verbose_name': 'Action Achievement', + 'verbose_name_plural': 'Action Achievements', + }, + ), + ] diff --git a/eap/models.py b/eap/models.py index 2a8ef9105..985a9942b 100644 --- a/eap/models.py +++ b/eap/models.py @@ -22,6 +22,7 @@ class IndicatorChoices(TextChoices): # TODO these indicator are yet to be provi default=IndicatorChoices.INDICATOR_1, null=True, blank=True ) indicator_value = models.IntegerField(null=True, blank=True) + client_id = models.CharField(max_length=50, null=True, blank=True) class Meta: verbose_name = _('Early Action Indicator') @@ -54,6 +55,7 @@ class Sector(IntegerChoices): targeted_people = models.IntegerField(verbose_name=_('Targeted people'), null=True, blank=True,) readiness_activities = models.TextField(verbose_name=_('Readiness Activities'), null=True, blank=True) prepositioning_activities = models.TextField(verbose_name=_('Pre-positioning Activities'), null=True, blank=True) + client_id = models.CharField(max_length=50, null=True, blank=True) class Meta: verbose_name = _('Early Action') @@ -69,6 +71,7 @@ class EAPDocument(models.Model): settings.AUTH_USER_MODEL, verbose_name=_('Created by'), related_name='document_created_by', null=True, blank=True, on_delete=models.SET_NULL, ) + client_id = models.CharField(max_length=50, null=True, blank=True) class Meta: verbose_name = _('Document') @@ -98,9 +101,9 @@ class Status(TextChoices): Country, on_delete=models.SET_NULL, verbose_name=_('Country'), related_name='eap_country', null=True ) - district = models.ForeignKey( - District, on_delete=models.SET_NULL, verbose_name=_('Provience/Region'), - related_name='eap_district', null=True, blank=True + districts = models.ManyToManyField( + District, verbose_name=_('districts'), + related_name='eap_district' ) disaster_type = models.ForeignKey( DisasterType, on_delete=models.SET_NULL, verbose_name=_('Disaster Type'), @@ -122,10 +125,11 @@ class Status(TextChoices): early_action_budget = models.IntegerField(verbose_name=_('Early Actions Budget (CHF)'), null=True, blank=True) trigger_statement = models.TextField(verbose_name=_('Trigger Statement (Threshold for Activation)')) overview = models.TextField(verbose_name=_('EAP Overview')) - document = models.ForeignKey( - EAPDocument, on_delete=models.SET_NULL, - verbose_name=_('EAP Documents'), related_name='eap_document', - null=True, blank=True + documents = models.ManyToManyField( + EAPDocument, + verbose_name=_('EAP Documents'), + related_name='eap_document', + blank=True ) early_actions = models.ManyToManyField( EarlyAction, @@ -159,6 +163,7 @@ class EAPPartner(models.Model): eap = models.ForeignKey(EAP, on_delete=models.CASCADE, related_name='eap_partner', verbose_name=_('EAP')) name = models.CharField(max_length=255, verbose_name=_('Name'), null=True, blank=True) url = models.URLField(verbose_name=_('URL'), null=True, blank=True) + client_id = models.CharField(max_length=50, null=True, blank=True) class Meta: verbose_name = _('EAP Partner') @@ -172,6 +177,7 @@ class EAPReference(models.Model): eap = models.ForeignKey(EAP, on_delete=models.CASCADE, related_name='eap_reference', verbose_name=_('EAP')) source = models.CharField(max_length=255, verbose_name=_('Name'), null=True, blank=True) url = models.URLField(verbose_name=_('URL'), null=True, blank=True) + client_id = models.CharField(max_length=50, null=True, blank=True) class Meta: verbose_name = _('EAP Reference') @@ -187,6 +193,7 @@ class Action(models.Model): related_name="action", verbose_name=_('Early Actions') ) early_act = models.TextField(verbose_name=_('Early Actions'), null=True, blank=True) + client_id = models.CharField(max_length=50, null=True, blank=True) class Meta: verbose_name = _('Action') @@ -204,6 +211,7 @@ class PrioritizedRisk(models.Model): verbose_name=_('early action') ) risks = models.TextField(null=True, blank=True) + client_id = models.CharField(max_length=50, null=True, blank=True) class Meta: verbose_name = _('Prioritized risk') @@ -231,12 +239,11 @@ class EAPActivation(models.Model): ) trigger_met_date = models.DateTimeField(verbose_name=_('Date the trigger was met')) description = models.TextField(verbose_name=_('Description of EAP Activation')) - document = models.ForeignKey( + documents = models.ManyToManyField( EAPDocument, - on_delete=models.SET_NULL, verbose_name=_('EAP Activation Documents'), related_name='eap_activation_document', - null=True, blank=True + blank=True ) originator_name = models.CharField(max_length=250, verbose_name=_('Originator name'), null=True, blank=True) originator_title = models.CharField(max_length=250, verbose_name=_('Originator title'), null=True, blank=True) @@ -251,6 +258,9 @@ class EAPActivation(models.Model): ifrc_focal_title = models.CharField(max_length=250, verbose_name=_('IFRC Focal Point Title'), null=True, blank=True) ifrc_focal_email = models.CharField(max_length=250, verbose_name=_('IFRC Focal Point Email'), null=True, blank=True) + def __str__(self): + return f'{self.eap.eap_number}' + class EAPOperationalPlan(models.Model): early_action = models.OneToOneField( @@ -260,10 +270,11 @@ class EAPOperationalPlan(models.Model): blank=True ) budget = models.IntegerField(verbose_name=_('Budget per sector (CHF)'), null=True, blank=True) - indicator_value = models.IntegerField(verbose_name=_('Indicator'), null=True, blank=True) + value = models.IntegerField(verbose_name=_('value'), null=True, blank=True) no_of_people_reached = models.IntegerField(verbose_name=_('People Reached'), null=True, blank=True) readiness_activities_achievements = models.TextField(verbose_name=_('Readiness Activities Achievements'), null=True, blank=True) prepo_activities_achievements = models.TextField(verbose_name=_('Pre-positioning Activities Achievements'), null=True, blank=True) + client_id = models.CharField(max_length=50, null=True, blank=True) class Meta: verbose_name = _('EAP Operational Plan') @@ -273,6 +284,28 @@ def __str__(self): return f'{self.id}' +class OperationalPlanIndicator(models.Model): + operational_plan = models.ForeignKey( + EAPOperationalPlan, on_delete=models.SET_NULL, + related_name="operational_plan_indicator", verbose_name=_('Operational Plan'), + null=True, blank=True + ) + indicator = models.OneToOneField( + EarlyActionIndicator, + on_delete=models.SET_NULL, + null=True, + blank=True + ) + indicator_value = models.IntegerField(null=True, blank=True) + + class Meta: + verbose_name = _('Operational Indicator') + verbose_name_plural = _('Operational Indicators') + + def __str__(self): + return f'{self.indicator.id}' + + class ActionAchievements(models.Model): operational_plan = models.ForeignKey( EAPOperationalPlan, on_delete=models.SET_NULL, @@ -281,6 +314,7 @@ class ActionAchievements(models.Model): ) action = models.OneToOneField(Action, on_delete=models.SET_NULL, null=True, blank=True) early_act_achievement = models.TextField(verbose_name=_('Early Actions Achievements'), null=True, blank=True) + client_id = models.CharField(max_length=50, null=True, blank=True) class Meta: verbose_name = _('Action Achievement') @@ -291,8 +325,6 @@ def __str__(self): class EAPActivationReport(models.Model): - created_at = models.DateTimeField(verbose_name=_('created at'), auto_now_add=True) - modified_at = models.DateTimeField(verbose_name=_('updated at'), auto_now=True) eap_activation = models.ForeignKey( EAPActivation, on_delete=models.SET_NULL, @@ -303,12 +335,11 @@ class EAPActivationReport(models.Model): number_of_people_reached = models.IntegerField(verbose_name=_('Number Of People Reached')) description = models.TextField(verbose_name=_('Description of Event & Overview of Implementation')) overall_objectives = models.TextField(verbose_name=_('Overall Objective of the Intervention')) - document = models.ForeignKey( + documents = models.ManyToManyField( EAPDocument, - on_delete=models.SET_NULL, verbose_name=_('EAP Activation Report Document'), - related_name='eap_activation_report_document', - null=True, blank=True + related_name='eap_act_reports', + blank=True ) challenges_and_lesson = models.TextField(verbose_name=_('Challenges & Lesson Learned per Sector')) general_lesson_and_recomendations = models.TextField(verbose_name=_('General Lessons Learned and Recomendations')) @@ -319,4 +350,21 @@ class EAPActivationReport(models.Model): related_name='eap_activation_ifrc_report', null=True, blank=True ) + operational_plans = models.ManyToManyField( + EAPOperationalPlan, + verbose_name=_('Operational Plans'), + blank=True + ) + created_by = models.ForeignKey( + settings.AUTH_USER_MODEL, verbose_name=_('Created by'), related_name='eap_act_report_created_by', + null=True, blank=True, on_delete=models.SET_NULL, + ) + modified_by = models.ForeignKey( + settings.AUTH_USER_MODEL, verbose_name=_('Modified by'), related_name='eap_act_modified_by', + null=True, blank=True, on_delete=models.SET_NULL, + ) + created_at = models.DateTimeField(verbose_name=_('Created at'), auto_now_add=True) + modified_at = models.DateTimeField(verbose_name=_('Updated at'), auto_now=True) + def __str__(self): + return f'{self.eap_activation.title}' diff --git a/eap/serializers.py b/eap/serializers.py index 0c417db21..598942c35 100644 --- a/eap/serializers.py +++ b/eap/serializers.py @@ -21,6 +21,9 @@ EAPDocument, PrioritizedRisk, EAPActivation, + EAPOperationalPlan, + ActionAchievements, + EAPActivationReport, ) from main.writable_nested_serializers import ( @@ -112,6 +115,7 @@ class EAPSerializer( partners = EAPPartnerSerializer(source='eap_partner', many=True, required=False) early_actions = EarlyActionSerializer(many=True) created_by_details = UserNameSerializer(source='created_by', read_only=True) + modified_by_details = UserNameSerializer(source='modified_by', read_only=True) hazard_type_details = DisasterTypeSerializer(source='disaster_type', read_only=True) document_details = EAPDocumentSerializer(source='document', read_only=True, required=False) status_display = serializers.CharField(source='get_status_display', read_only=True) @@ -121,12 +125,13 @@ class Meta: fields = '__all__' def validate(self, validated_data): - district = validated_data['district'] - if district: - if district.country != validated_data['country']: - raise serializers.ValidationError({ - 'district': ugettext('Different districts found for given country') - }) + districts = validated_data['districts'] + if districts: + for district in districts: + if district.country != validated_data['country']: + raise serializers.ValidationError({ + 'district': ugettext('Different districts found for given country') + }) return validated_data def create(self, validated_data): @@ -147,3 +152,48 @@ class Meta: model = EAPActivation exclude = ('eap', 'field_report') + +class ActionAchievementsSerializer(serializers.ModelSerializer): + + class Meta: + model = ActionAchievements + exclude = ('operational_plan',) + + +class OperationalPlanSerializer( + NestedUpdateMixin, + NestedCreateMixin, + serializers.ModelSerializer +): + # indicators = EarlyActionIndicatorSerializer(many=True, required=False) + early_actions_achievements = ActionAchievementsSerializer(source='action_achievement', many=True, required=False) + + class Meta: + model = EAPOperationalPlan + fields = ('__all__') + + +class EAPActivationReportSerializer( + NestedUpdateMixin, + NestedCreateMixin, + serializers.ModelSerializer +): + operational_plans = OperationalPlanSerializer(many=True) + created_by_details = UserNameSerializer(source='created_by', read_only=True) + modified_by_details = UserNameSerializer(source='modified_by', read_only=True) + document_details = EAPDocumentSerializer(source='document', read_only=True, many=True, required=False) + ifrc_financial_report_details = EAPDocumentSerializer(source='ifrc_financial_report', read_only=True) + + class Meta: + model = EAPActivationReport + fields = '__all__' + + def create(self, validated_data): + validated_data['created_by'] = self.context['request'].user + final_report = super().create(validated_data) + return final_report + + def update(self, instance, validated_data): + validated_data['modified_by'] = self.context['request'].user + final_report = super().update(instance, validated_data) + return final_report diff --git a/eap/test_views.py b/eap/test_views.py index 7b0e79503..874d87063 100644 --- a/eap/test_views.py +++ b/eap/test_views.py @@ -4,14 +4,18 @@ from django.contrib.auth.models import User from main.test_case import APITestCase -from eap.models import EAP, EarlyAction +from eap.models import EAP, EarlyAction, EAPActivationReport, Action from api.factories.country import CountryFactory from api.factories.district import DistrictFactory from api.factories.disaster_type import DisasterTypeFactory from deployments.factories.user import UserFactory -from .factories import EAPDocumentFactory, EAPFactory +from .factories import ( + EAPDocumentFactory, + EAPFactory, + EAPActivationFactory, +) class EAPTest(APITestCase): @@ -23,8 +27,10 @@ def setUp(self): self.district1 = DistrictFactory.create(name='test district1', country=self.country1) self.district2 = DistrictFactory.create(name='test district2', country=self.country2) self.document1 = EAPDocumentFactory.create(created_by=self.user) + self.document2 = EAPDocumentFactory.create(created_by=self.user) self.disaster_type = DisasterTypeFactory.create(name="test earthquake") self.disaster_type_updated = DisasterTypeFactory.create(name="test flood") + self.eap_activation = EAPActivationFactory.create() path = os.path.join(settings.TEST_DIR, 'documents') self.file = os.path.join(path, 'go.png') @@ -63,7 +69,7 @@ def setUp(self): "early_action_budget": 2000, "trigger_statement": "test", "overview": "test", - "document": self.document1.id, + "documents": [self.document1.id], "originator_name": "eap name", "originator_title": "eap title", "originator_email": "eap@gmail.com", @@ -77,7 +83,7 @@ def setUp(self): "ifrc_focal_email": "eap_ifrc@gmail.com", "ifrc_focal_phone": "5685471584", "country": self.country1.id, - "district": self.district1.id, + "districts": [self.district1.id], "disaster_type": self.disaster_type.id, "early_actions": [ { @@ -150,6 +156,33 @@ def setUp(self): } ] } + self.eap_act_report_body = { + "eap_activation": self.eap_activation.id, + "number_of_people_reached": 1000, + "description": "test eap activation report", + "overall_objectives": "test eap activation report", + "documents": [self.document1.id, self.document2.id], + "challenges_and_lesson": "test eap activation report", + "general_lesson_and_recomendations": "test eap activation report", + "ifrc_financial_report": self.document1.id, + "operational_plans": [ + { + "budget": 200000, + "value": 100, + "no_of_people_reached": 100, + "readiness_activities_achievements": "test", + "prepo_activities_achievements": "test", + "early_actions_achievements": [ + { + "early_act_achievement": "test" + }, + { + "early_act_achievement": "test 2" + } + ] + }, + ] + } super().setUp() def test_create_and_update_eap(self): @@ -160,9 +193,7 @@ def test_create_and_update_eap(self): created = EAP.objects.get(id=response['id']) self.assertEqual(created.created_by.id, self.user.id) self.assertEqual(created.country.id, self.country1.id) - self.assertEqual(created.district.id, self.district1.id) self.assertEqual(created.disaster_type, self.disaster_type) - self.assertEqual(created.document.id, self.document1.id) self.assertEqual(response['country'], self.country1.id) self.assertEqual(created.status, EAP.Status.APPROVED) self.assertEqual(created.early_actions.count(), 2) @@ -172,7 +203,7 @@ def test_create_and_update_eap(self): # update eap data = self.body data['country'] = self.country2.id - data['district'] = self.district2.id + data['districts'] = [self.district2.id] data['references'] = [ { "source": "test updated", @@ -205,12 +236,9 @@ def test_get_eap(self): eap1, eap2, eap3 = EAPFactory.create_batch(3, created_by=user1) self.client.force_authenticate(user=user1) response1 = self.client.get('/api/v2/eap/').json() - self.assertEqual(response1['count'], 3) self.assertEqual(response1['results'][0]['created_by'], user1.id) - self.assertEqual( - sorted([eap1.id, eap2.id, eap3.id]), - sorted([data['id'] for data in response1['results']]) - ) + + assert all(item in [data['id'] for data in response1['results']] for item in [eap1.id, eap2.id, eap3.id]) # query single eap response = self.client.get(f'/api/v2/eap/{eap1.id}/').json() @@ -222,7 +250,6 @@ def test_get_eap(self): self.client.force_authenticate(user=user2) eap4, eap5 = EAPFactory.create_batch(2, created_by=user2) response2 = self.client.get('/api/v2/eap/').json() - self.assertEqual(response2['count'], 5) self.assertEqual(response2['results'][0]['created_by'], user2.id) self.assertIn(eap4.id, [data['id'] for data in response2['results']]) self.assertNotIn([data['id'] for data in response2['results']], [data['id'] for data in response1['results']]) @@ -231,4 +258,28 @@ def test_get_eap(self): user3 = User.objects.create(username='ram') self.client.force_authenticate(user=user3) response3 = self.client.get('/api/v2/eap/').json() - self.assertEqual(response3['count'], 5) + self.assertEqual(response3['count'], 6) + + def test_create_and_update_eap_activation_report(self): + self.client.force_authenticate(user=self.user) + # create eap + with self.capture_on_commit_callbacks(execute=True): + self.client.post('/api/v2/eap/', self.body, format='json').json() + actions = Action.objects.all().values_list('id', flat=True) + early_actions = EarlyAction.objects.all().values_list('id', flat=True) + self.eap_act_report_body['operational_plans'][0]['early_action'] = early_actions[0] + self.eap_act_report_body['operational_plans'][0]['early_actions_achievements'][0]['action'] = actions[0] + self.eap_act_report_body['operational_plans'][0]['early_actions_achievements'][1]['action'] = actions[1] + # create eap_report + with self.capture_on_commit_callbacks(execute=True): + final_report_resp = self.client.post( + '/api/v2/eap_activation_report/', + self.eap_act_report_body, + format='json' + ).json() + created = EAPActivationReport.objects.get(id=final_report_resp['id']) + self.assertEqual(created.created_by.id, self.user.id) + self.assertEqual(final_report_resp['eap_activation'], self.eap_activation.id) + self.assertEqual(final_report_resp['ifrc_financial_report'], self.document1.id) + self.assertEqual(len(final_report_resp['documents']), 2) + self.assertEqual(len(final_report_resp['operational_plans']), 1) diff --git a/eap/views.py b/eap/views.py index 7478f577a..ac31f4b66 100644 --- a/eap/views.py +++ b/eap/views.py @@ -12,10 +12,12 @@ EAP, EAPDocument, EarlyAction, + EAPActivationReport, ) from .serializers import ( EAPSerializer, EAPDocumentSerializer, + EAPActivationReportSerializer, ) @@ -39,6 +41,14 @@ def get_queryset(self): return EAP.objects.all().order_by('-created_at') +class EAPActivationReportViewSet(viewsets.ModelViewSet): + serializer_class = EAPActivationReportSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return EAPActivationReport.objects.all().order_by('-created_at') + + class EAPOptionsView(views.APIView): """ Options for various attribute related to eap diff --git a/main/urls.py b/main/urls.py index cdf557d9a..8cdcabdcd 100644 --- a/main/urls.py +++ b/main/urls.py @@ -150,6 +150,7 @@ router.register(r'share-flash-update', flash_views.ShareFlashUpdateViewSet, basename='share_flash_update') router.register(r'eap', eap_views.EAPViewSet, basename='eap') +router.register(r'eap_activation_report', eap_views.EAPActivationReportViewSet, basename='eap-activation-report') router.register(r'eap-file', eap_views.EAPDocumentViewSet, basename='eap_file') # Dref apis From 58591ee556e836f18226193f6267c41fb9a93ef8 Mon Sep 17 00:00:00 2001 From: rup Date: Wed, 3 Aug 2022 11:25:39 +0545 Subject: [PATCH 08/13] Add test case for EAP final report --- eap/migrations/0007_auto_20220803_0936.py | 36 ++++++++++ eap/models.py | 12 +++- eap/serializers.py | 20 ++++-- eap/test_views.py | 82 +++++++++++++++++++++-- 4 files changed, 134 insertions(+), 16 deletions(-) create mode 100644 eap/migrations/0007_auto_20220803_0936.py diff --git a/eap/migrations/0007_auto_20220803_0936.py b/eap/migrations/0007_auto_20220803_0936.py new file mode 100644 index 000000000..63663d481 --- /dev/null +++ b/eap/migrations/0007_auto_20220803_0936.py @@ -0,0 +1,36 @@ +# Generated by Django 2.2.28 on 2022-08-03 09:36 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('eap', '0006_auto_20220802_0835'), + ] + + operations = [ + migrations.CreateModel( + name='ActionAchievement', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('early_act_achievement', models.TextField(blank=True, null=True, verbose_name='Early Actions Achievements')), + ('client_id', models.CharField(blank=True, max_length=50, null=True)), + ('action', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='action_achievement', to='eap.Action')), + ('operational_plan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='action_achievement', to='eap.EAPOperationalPlan', verbose_name='Action Achievement')), + ], + options={ + 'verbose_name': 'Action Achievement', + 'verbose_name_plural': 'Action Achievements', + }, + ), + migrations.AlterField( + model_name='operationalplanindicator', + name='indicator', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='operational_plan_indicator', to='eap.EarlyActionIndicator'), + ), + migrations.DeleteModel( + name='ActionAchievements', + ), + ] diff --git a/eap/models.py b/eap/models.py index 985a9942b..30a101b8f 100644 --- a/eap/models.py +++ b/eap/models.py @@ -290,9 +290,10 @@ class OperationalPlanIndicator(models.Model): related_name="operational_plan_indicator", verbose_name=_('Operational Plan'), null=True, blank=True ) - indicator = models.OneToOneField( + indicator = models.ForeignKey( EarlyActionIndicator, on_delete=models.SET_NULL, + related_name='operational_plan_indicator', null=True, blank=True ) @@ -306,13 +307,18 @@ def __str__(self): return f'{self.indicator.id}' -class ActionAchievements(models.Model): +class ActionAchievement(models.Model): operational_plan = models.ForeignKey( EAPOperationalPlan, on_delete=models.SET_NULL, related_name="action_achievement", verbose_name=_('Action Achievement'), null=True, blank=True ) - action = models.OneToOneField(Action, on_delete=models.SET_NULL, null=True, blank=True) + action = models.ForeignKey( + Action, + on_delete=models.SET_NULL, + related_name='action_achievement', + null=True, blank=True + ) early_act_achievement = models.TextField(verbose_name=_('Early Actions Achievements'), null=True, blank=True) client_id = models.CharField(max_length=50, null=True, blank=True) diff --git a/eap/serializers.py b/eap/serializers.py index 598942c35..5c57732fd 100644 --- a/eap/serializers.py +++ b/eap/serializers.py @@ -2,8 +2,6 @@ from rest_framework import serializers -from enumfields.drf.serializers import EnumSupportSerializerMixin - from api.serializers import ( UserNameSerializer, DisasterTypeSerializer, @@ -22,8 +20,9 @@ PrioritizedRisk, EAPActivation, EAPOperationalPlan, - ActionAchievements, + ActionAchievement, EAPActivationReport, + OperationalPlanIndicator, ) from main.writable_nested_serializers import ( @@ -153,10 +152,17 @@ class Meta: exclude = ('eap', 'field_report') -class ActionAchievementsSerializer(serializers.ModelSerializer): +class ActionAchievementSerializer(serializers.ModelSerializer): + + class Meta: + model = ActionAchievement + exclude = ('operational_plan',) + + +class OperationalPlanIndicatorSerializer(serializers.ModelSerializer): class Meta: - model = ActionAchievements + model = OperationalPlanIndicator exclude = ('operational_plan',) @@ -165,8 +171,8 @@ class OperationalPlanSerializer( NestedCreateMixin, serializers.ModelSerializer ): - # indicators = EarlyActionIndicatorSerializer(many=True, required=False) - early_actions_achievements = ActionAchievementsSerializer(source='action_achievement', many=True, required=False) + indicators = OperationalPlanIndicatorSerializer(source='operational_plan_indicator', many=True, required=False) + early_actions_achievements = ActionAchievementSerializer(source='action_achievement', many=True, required=False) class Meta: model = EAPOperationalPlan diff --git a/eap/test_views.py b/eap/test_views.py index 874d87063..84ec22bb3 100644 --- a/eap/test_views.py +++ b/eap/test_views.py @@ -4,7 +4,13 @@ from django.contrib.auth.models import User from main.test_case import APITestCase -from eap.models import EAP, EarlyAction, EAPActivationReport, Action +from eap.models import ( + EAP, + EarlyAction, + EAPActivationReport, + Action, + EarlyActionIndicator, +) from api.factories.country import CountryFactory from api.factories.district import DistrictFactory @@ -174,11 +180,19 @@ def setUp(self): "prepo_activities_achievements": "test", "early_actions_achievements": [ { - "early_act_achievement": "test" + "early_act_achievement": "test 1" }, { "early_act_achievement": "test 2" } + ], + "indicators": [ + { + "indicator_value": 200 + }, + { + "indicator_value": 300 + } ] }, ] @@ -264,12 +278,17 @@ def test_create_and_update_eap_activation_report(self): self.client.force_authenticate(user=self.user) # create eap with self.capture_on_commit_callbacks(execute=True): - self.client.post('/api/v2/eap/', self.body, format='json').json() - actions = Action.objects.all().values_list('id', flat=True) - early_actions = EarlyAction.objects.all().values_list('id', flat=True) - self.eap_act_report_body['operational_plans'][0]['early_action'] = early_actions[0] + eap_resp = self.client.post('/api/v2/eap/', self.body, format='json').json() + early_action = EarlyAction.objects.get(id=eap_resp['early_actions'][0]['id']) + + actions = list(Action.objects.filter(early_action=early_action).values_list('id', flat=True)) + actions_indicators = early_action.indicators.all().values_list('id', flat=True) + self.eap_act_report_body['operational_plans'][0]['early_action'] = early_action.id self.eap_act_report_body['operational_plans'][0]['early_actions_achievements'][0]['action'] = actions[0] self.eap_act_report_body['operational_plans'][0]['early_actions_achievements'][1]['action'] = actions[1] + + self.eap_act_report_body['operational_plans'][0]['indicators'][0]['indicator'] = actions_indicators[0] + self.eap_act_report_body['operational_plans'][0]['indicators'][1]['indicator'] = actions_indicators[1] # create eap_report with self.capture_on_commit_callbacks(execute=True): final_report_resp = self.client.post( @@ -283,3 +302,54 @@ def test_create_and_update_eap_activation_report(self): self.assertEqual(final_report_resp['ifrc_financial_report'], self.document1.id) self.assertEqual(len(final_report_resp['documents']), 2) self.assertEqual(len(final_report_resp['operational_plans']), 1) + self.assertEqual(len(final_report_resp['operational_plans'][0]['early_actions_achievements']), 2) + self.assertEqual(len(final_report_resp['operational_plans'][0]['indicators']), 2) + + # update eap_report + data = self.eap_act_report_body + data['description'] = 'updated description' + data['documents'] = [self.document1.id] + data['ifrc_financial_report'] = self.document2.id + data['operational_plans'] = [ + { + "budget": 500000, + "value": 500, + "no_of_people_reached": 500, + "readiness_activities_achievements": "test updated", + "prepo_activities_achievements": "test updated", + "early_actions_achievements": [ + { + 'action': actions[0], + "early_act_achievement": "test updated" + }, + { + 'action': actions[1], + "early_act_achievement": "test 2 updated" + } + ], + "indicators": [ + { + "indicator": actions_indicators[0], + "indicator_value": 500 + }, + { + 'indicator': actions_indicators[1], + "indicator_value": 500 + } + ] + }, + ] + with self.capture_on_commit_callbacks(execute=True): + final_report_updated_resp = self.client.put( + f'/api/v2/eap_activation_report/{created.id}/', + data, + format='json' + ).json() + updated = EAPActivationReport.objects.get(id=final_report_updated_resp['id']) + self.assertEqual(updated.created_by.id, self.user.id) + self.assertEqual(final_report_updated_resp['eap_activation'], self.eap_activation.id) + self.assertEqual(final_report_updated_resp['ifrc_financial_report'], self.document2.id) + self.assertEqual(len(final_report_updated_resp['documents']), 1) + self.assertEqual(len(final_report_updated_resp['operational_plans']), 1) + self.assertEqual(len(final_report_updated_resp['operational_plans'][0]['early_actions_achievements']), 2) + self.assertEqual(len(final_report_updated_resp['operational_plans'][0]['indicators']), 2) From 135cfdd311abaf84e12392d4c60907114536a7a2 Mon Sep 17 00:00:00 2001 From: rup Date: Thu, 4 Aug 2022 11:02:11 +0545 Subject: [PATCH 09/13] Fix error for districs_details, indicator_display, document_details for EAP --- eap/admin.py | 12 ++++++++++++ eap/factories.py | 4 ++-- eap/migrations/0008_auto_20220804_0505.py | 19 +++++++++++++++++++ eap/models.py | 2 +- eap/serializers.py | 8 ++++---- eap/test_views.py | 4 ++-- main/urls.py | 2 +- 7 files changed, 41 insertions(+), 10 deletions(-) create mode 100644 eap/migrations/0008_auto_20220804_0505.py diff --git a/eap/admin.py b/eap/admin.py index 41bd34ac2..08a7ecabd 100644 --- a/eap/admin.py +++ b/eap/admin.py @@ -7,6 +7,8 @@ EAPPartner, EAPReference, EAPDocument, + EAPActivation, + EAPActivationReport, ) @@ -31,3 +33,13 @@ class EAPAdmin(admin.ModelAdmin): list_display = ('eap_number', 'country', 'status', 'operational_timeframe',) inlines = [ReferenceAdminInline, PartnerAdminInline] autocomplete_fields = ('country', 'districts', 'disaster_type', 'created_by', 'modified_by') + + +@admin.register(EAPActivation) +class EAPActivation(admin.ModelAdmin): + model = EAPActivation + + +@admin.register(EAPActivationReport) +class EAPActivationReport(admin.ModelAdmin): + model = EAPActivationReport diff --git a/eap/factories.py b/eap/factories.py index fcd2d88ce..b7c6508bd 100644 --- a/eap/factories.py +++ b/eap/factories.py @@ -8,7 +8,6 @@ from api.factories import ( disaster_type, country, - district, field_report, ) @@ -36,7 +35,6 @@ class EAPFactory(factory.django.DjangoModelFactory): class Meta: model = EAP - # districts = factory.SubFactory(district.DistrictFactory) country = factory.SubFactory(country.CountryFactory) disaster_type = factory.SubFactory(disaster_type.DisasterTypeFactory) eap_number = fuzzy.FuzzyText(length=20) @@ -118,3 +116,5 @@ def documents(self, create, extracted, **kwargs): self.documents.add(document) + + diff --git a/eap/migrations/0008_auto_20220804_0505.py b/eap/migrations/0008_auto_20220804_0505.py new file mode 100644 index 000000000..49ccc26b6 --- /dev/null +++ b/eap/migrations/0008_auto_20220804_0505.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.28 on 2022-08-04 05:05 + +from django.db import migrations, models +import eap.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eap', '0007_auto_20220803_0936'), + ] + + operations = [ + migrations.AlterField( + model_name='earlyactionindicator', + name='indicator', + field=models.CharField(blank=True, choices=[('indicator_1', 'Indicator 1'), ('indicator_2', 'Indicator 2')], default=eap.models.EarlyActionIndicator.IndicatorChoices('indicator_1'), max_length=255, null=True), + ), + ] diff --git a/eap/models.py b/eap/models.py index 30a101b8f..a7d86eacc 100644 --- a/eap/models.py +++ b/eap/models.py @@ -18,7 +18,7 @@ class IndicatorChoices(TextChoices): # TODO these indicator are yet to be provi INDICATOR_2 = 'indicator_2', _('Indicator 2') indicator = models.CharField( - IndicatorChoices.choices, max_length=255, + max_length=255, choices=IndicatorChoices.choices, default=IndicatorChoices.INDICATOR_1, null=True, blank=True ) indicator_value = models.IntegerField(null=True, blank=True) diff --git a/eap/serializers.py b/eap/serializers.py index 5c57732fd..94f072b98 100644 --- a/eap/serializers.py +++ b/eap/serializers.py @@ -50,7 +50,7 @@ class EarlyActionIndicatorSerializer(serializers.ModelSerializer): class Meta: model = EarlyActionIndicator - fields = ('__all__') + fields = '__all__' class ActionSerializer(serializers.ModelSerializer): @@ -109,14 +109,14 @@ class EAPSerializer( serializers.ModelSerializer ): country_details = CountrySerializer(source='country', read_only=True) - district_details = MiniDistrictSerializer(source='district', read_only=True) + districts_details = MiniDistrictSerializer(source='districts', many=True, read_only=True) references = EAPReferenceSerializer(source='eap_reference', many=True, required=False) partners = EAPPartnerSerializer(source='eap_partner', many=True, required=False) early_actions = EarlyActionSerializer(many=True) created_by_details = UserNameSerializer(source='created_by', read_only=True) modified_by_details = UserNameSerializer(source='modified_by', read_only=True) hazard_type_details = DisasterTypeSerializer(source='disaster_type', read_only=True) - document_details = EAPDocumentSerializer(source='document', read_only=True, required=False) + documents_details = EAPDocumentSerializer(source='documents', many=True, read_only=True, required=False) status_display = serializers.CharField(source='get_status_display', read_only=True) class Meta: @@ -187,7 +187,7 @@ class EAPActivationReportSerializer( operational_plans = OperationalPlanSerializer(many=True) created_by_details = UserNameSerializer(source='created_by', read_only=True) modified_by_details = UserNameSerializer(source='modified_by', read_only=True) - document_details = EAPDocumentSerializer(source='document', read_only=True, many=True, required=False) + document_details = EAPDocumentSerializer(source='documents', read_only=True, many=True, required=False) ifrc_financial_report_details = EAPDocumentSerializer(source='ifrc_financial_report', read_only=True) class Meta: diff --git a/eap/test_views.py b/eap/test_views.py index 84ec22bb3..7148062a2 100644 --- a/eap/test_views.py +++ b/eap/test_views.py @@ -292,7 +292,7 @@ def test_create_and_update_eap_activation_report(self): # create eap_report with self.capture_on_commit_callbacks(execute=True): final_report_resp = self.client.post( - '/api/v2/eap_activation_report/', + '/api/v2/eap-activation-report/', self.eap_act_report_body, format='json' ).json() @@ -341,7 +341,7 @@ def test_create_and_update_eap_activation_report(self): ] with self.capture_on_commit_callbacks(execute=True): final_report_updated_resp = self.client.put( - f'/api/v2/eap_activation_report/{created.id}/', + f'/api/v2/eap-activation-report/{created.id}/', data, format='json' ).json() diff --git a/main/urls.py b/main/urls.py index 8cdcabdcd..ef86c8625 100644 --- a/main/urls.py +++ b/main/urls.py @@ -150,7 +150,7 @@ router.register(r'share-flash-update', flash_views.ShareFlashUpdateViewSet, basename='share_flash_update') router.register(r'eap', eap_views.EAPViewSet, basename='eap') -router.register(r'eap_activation_report', eap_views.EAPActivationReportViewSet, basename='eap-activation-report') +router.register(r'eap-activation-report', eap_views.EAPActivationReportViewSet, basename='eap-activation-report') router.register(r'eap-file', eap_views.EAPDocumentViewSet, basename='eap_file') # Dref apis From e156db67aad026cf6419d8a1661571c1c190f587 Mon Sep 17 00:00:00 2001 From: rup Date: Fri, 5 Aug 2022 12:06:30 +0545 Subject: [PATCH 10/13] Fix issue for delay time for eap list --- api/drf_views.py | 5 ++-- api/serializers.py | 4 ++- eap/factories.py | 31 ++++++++++++++++++++++ eap/serializers.py | 55 ++++++++++++++++++++++++++++++++++----- eap/test_views.py | 64 ++++++++++++++++++++++++++++++++++++++++++++-- eap/views.py | 29 ++++++++++++++++++++- test | 32 +++++++++++++++++++++++ 7 files changed, 207 insertions(+), 13 deletions(-) create mode 100644 test diff --git a/api/drf_views.py b/api/drf_views.py index db9944003..99cf93763 100644 --- a/api/drf_views.py +++ b/api/drf_views.py @@ -232,11 +232,12 @@ def get_databank(self, request, pk): class CountryFilterRMD(filters.FilterSet): region = filters.NumberFilter(field_name='region', lookup_expr='exact') - + class Meta: model = Country fields = ('region', 'record_type',) + class CountryRMDViewset(viewsets.ReadOnlyModelViewSet): queryset = Country.objects.filter(is_deprecated=False).filter(iso3__isnull=False).exclude(iso3="") filterset_class = CountryFilterRMD @@ -247,7 +248,7 @@ class CountryRMDViewset(viewsets.ReadOnlyModelViewSet): class DistrictRMDFilter(filters.FilterSet): class Meta: model = District - fields = ('country','country__name') + fields = ('country', 'country__name') class DistrictRMDViewset(viewsets.ReadOnlyModelViewSet): diff --git a/api/serializers.py b/api/serializers.py index 25fed5b45..6f2f8ceaa 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -1061,8 +1061,9 @@ class Meta: fields = '__all__' -from eap.serializers import EAPActivationSerializer # It is imported here to avoid circular import issue class DetailFieldReportSerializer(FieldReportEnumDisplayMixin, ModelSerializer): + from eap.serializers import EAPActivationSerializer + user = UserSerializer() dtype = DisasterTypeSerializer() contacts = FieldReportContactSerializer(many=True) @@ -1117,6 +1118,7 @@ class Meta: 'disaster_start_date', 'created_at', 'appeals', ) + class CountryOfFieldReportToReviewSerializer(ModelSerializer): class Meta: model = CountryOfFieldReportToReview diff --git a/eap/factories.py b/eap/factories.py index b7c6508bd..b0366ad73 100644 --- a/eap/factories.py +++ b/eap/factories.py @@ -15,6 +15,7 @@ EAP, EAPDocument, EAPActivation, + EAPActivationReport, ) @@ -116,5 +117,35 @@ def documents(self, create, extracted, **kwargs): self.documents.add(document) +class EAPActivationReportFactory(factory.django.DjangoModelFactory): + class Meta: + model = EAPActivationReport + + eap_activation = factory.SubFactory(EAPActivationFactory) + number_of_people_reached = fuzzy.FuzzyInteger(0, 9) + description = fuzzy.FuzzyText(length=20) + overall_objectives = fuzzy.FuzzyText(length=50) + challenges_and_lesson = fuzzy.FuzzyText(length=50) + general_lesson_and_recomendations = fuzzy.FuzzyText(length=50) + + @factory.post_generation + def documents(self, create, extracted, **kwargs): + if not create: + return + + if extracted: + for document in extracted: + self.documents.add(document) + + @factory.post_generation + def operational_plans(self, create, extracted, **kwargs): + if not create: + return + + if extracted: + for plan in extracted: + self.operational_plans.add(plan) + + diff --git a/eap/serializers.py b/eap/serializers.py index 94f072b98..d10daec7c 100644 --- a/eap/serializers.py +++ b/eap/serializers.py @@ -112,19 +112,60 @@ class EAPSerializer( districts_details = MiniDistrictSerializer(source='districts', many=True, read_only=True) references = EAPReferenceSerializer(source='eap_reference', many=True, required=False) partners = EAPPartnerSerializer(source='eap_partner', many=True, required=False) - early_actions = EarlyActionSerializer(many=True) - created_by_details = UserNameSerializer(source='created_by', read_only=True) - modified_by_details = UserNameSerializer(source='modified_by', read_only=True) - hazard_type_details = DisasterTypeSerializer(source='disaster_type', read_only=True) - documents_details = EAPDocumentSerializer(source='documents', many=True, read_only=True, required=False) + # early_actions = EarlyActionSerializer(many=True) + # created_by_details = UserNameSerializer(source='created_by', read_only=True) + # modified_by_details = UserNameSerializer(source='modified_by', read_only=True) + # hazard_type_details = DisasterTypeSerializer(source='disaster_type', read_only=True) + # documents_details = EAPDocumentSerializer(source='documents', many=True, read_only=True, required=False) status_display = serializers.CharField(source='get_status_display', read_only=True) class Meta: model = EAP - fields = '__all__' + fields = [ + "id", + "created_at", + "modified_at", + "eap_number", + "approval_date", + "status", + "operational_timeframe", + "lead_time", + "eap_timeframe", + "num_of_people", + "total_budget", + "readiness_budget", + "pre_positioning_budget", + "early_action_budget", + "trigger_statement", + "overview", + "originator_name", + "originator_title", + "originator_email", + "originator_phone", + "nsc_name", + "nsc_title", + "nsc_email", + "nsc_phone", + "ifrc_focal_name", + "ifrc_focal_title", + "ifrc_focal_email", + "ifrc_focal_phone", + "created_by", + "modified_by", + "country", + "disaster_type", + "status_display", + "country_details", + "districts_details", + "references", + "partners", + + ] def validate(self, validated_data): - districts = validated_data['districts'] + if self.partial: + return validated_data + districts = validated_data.get('districts', None) if districts: for district in districts: if district.country != validated_data['country']: diff --git a/eap/test_views.py b/eap/test_views.py index 7148062a2..14f384126 100644 --- a/eap/test_views.py +++ b/eap/test_views.py @@ -6,10 +6,10 @@ from main.test_case import APITestCase from eap.models import ( EAP, + Action, EarlyAction, + EAPDocument, EAPActivationReport, - Action, - EarlyActionIndicator, ) from api.factories.country import CountryFactory @@ -21,6 +21,7 @@ EAPDocumentFactory, EAPFactory, EAPActivationFactory, + EAPActivationReportFactory, ) @@ -245,6 +246,12 @@ def test_create_and_update_eap(self): self.assertEqual(len(response['references']), 1) self.assertEqual(len(response['partners']), 1) + # test patch + data = {'status': EAP.Status.APPROVED} + response = self.client.patch(f'/api/v2/eap/{created.id}/', data=data, format='json').json() + pached = EAP.objects.get(id=response['id']) + self.assertEqual(pached.status, EAP.Status.APPROVED) + def test_get_eap(self): user1 = UserFactory.create(username='abc') eap1, eap2, eap3 = EAPFactory.create_batch(3, created_by=user1) @@ -353,3 +360,56 @@ def test_create_and_update_eap_activation_report(self): self.assertEqual(len(final_report_updated_resp['operational_plans']), 1) self.assertEqual(len(final_report_updated_resp['operational_plans'][0]['early_actions_achievements']), 2) self.assertEqual(len(final_report_updated_resp['operational_plans'][0]['indicators']), 2) + + # test patch + data = {'ifrc_financial_report': self.document1.id} + with self.capture_on_commit_callbacks(execute=True): + final_report_patched_resp = self.client.patch( + f'/api/v2/eap-activation-report/{created.id}/', + data, + format='json' + ).json() + updated = EAPActivationReport.objects.get(id=final_report_patched_resp['id']) + self.assertEqual(final_report_patched_resp['ifrc_financial_report'], self.document1.id) + + def test_get_eap_activation_report(self): + user1 = UserFactory.create(username='abc') + report1, report2, report3 = EAPActivationReportFactory.create_batch(3, created_by=user1) + self.client.force_authenticate(user=user1) + response1 = self.client.get('/api/v2/eap-activation-report/').json() + self.assertEqual(response1['results'][0]['created_by'], user1.id) + self.assertEqual(len(response1['results']), 3) + assert all(item in [data['id'] for data in response1['results']] for item in [report1.id, report2.id, report3.id]) + + # query single eap + response = self.client.get(f'/api/v2/eap-activation-report/{report1.id}/').json() + self.assertEqual(response['created_by'], user1.id) + self.assertEqual(response['id'], report1.id) + + # try with another user + user2 = User.objects.create(username='xyz') + self.client.force_authenticate(user=user2) + report4, report5 = EAPActivationReportFactory.create_batch(2, created_by=user2) + response2 = self.client.get('/api/v2/eap-activation-report/').json() + self.assertEqual(response2['results'][0]['created_by'], user2.id) + self.assertIn(report4.id, [data['id'] for data in response2['results']]) + self.assertNotIn([data['id'] for data in response2['results']], [data['id'] for data in response1['results']]) + + # try with users who has no any eap created + user3 = User.objects.create(username='ram') + self.client.force_authenticate(user=user3) + response3 = self.client.get('/api/v2/eap-activation-report/').json() + self.assertEqual(response3['count'], 5) + + def test_eap_upload_multiple_file(self): + file_count = EAPDocument.objects.count() + url = '/api/v2/eap-file/multiple/' + data = { + 'file': [open(self.file, 'rb'), open(self.file, 'rb'), open(self.file, 'rb')] + } + + self.authenticate() + response = self.client.post(url, data, format='multipart') + self.assert_201(response) + self.assertEqual(EAPDocument.objects.count(), file_count + 3) + diff --git a/eap/views.py b/eap/views.py index ac31f4b66..a648f38e8 100644 --- a/eap/views.py +++ b/eap/views.py @@ -6,7 +6,9 @@ response, permissions, mixins, + status, ) +from rest_framework.decorators import action from .models import ( EarlyActionIndicator, EAP, @@ -32,13 +34,38 @@ class EAPDocumentViewSet( def get_queryset(self): return EAPDocument.objects.all() + @action( + detail=False, + url_path='multiple', + methods=['POST'], + permission_classes=[permissions.IsAuthenticated], + ) + def multiple_file(self, request, pk=None, version=None): + # converts querydict to original dict + files = dict((request.data).lists())['file'] + data = [{'file': file} for file in files] + file_serializer = EAPDocumentSerializer(data=data, context={'request': request}, many=True) + if file_serializer.is_valid(): + file_serializer.save() + return response.Response(file_serializer.data, status=status.HTTP_201_CREATED) + else: + return response.Response(file_serializer.errors, status=status.HTTP_400_BAD_REQUEST) + class EAPViewSet(viewsets.ModelViewSet): serializer_class = EAPSerializer permission_classes = [permissions.IsAuthenticated] def get_queryset(self): - return EAP.objects.all().order_by('-created_at') + return EAP.objects.all().order_by( + '-created_at' + ).select_related( + 'country', + ).prefetch_related( + 'districts', + 'eap_reference__eap', + 'eap_partner__eap' + ) class EAPActivationReportViewSet(viewsets.ModelViewSet): diff --git a/test b/test new file mode 100644 index 000000000..4b2bbcd91 --- /dev/null +++ b/test @@ -0,0 +1,32 @@ +"id", + "created_at", + "modified_at", + "eap_number", + "approval_date", + "status", + "operational_timeframe", + "lead_time", + "eap_timeframe", + "num_of_people", + "total_budget", + "readiness_budget", + "pre_positioning_budget", + "early_action_budget", + "trigger_statement", + "overview", + "originator_name", + "originator_title", + "originator_email", + "originator_phone", + "nsc_name", + "nsc_title", + "nsc_email", + "nsc_phone", + "ifrc_focal_name", + "ifrc_focal_title", + "ifrc_focal_email", + "ifrc_focal_phone", + "created_by", + "modified_by", + "country", + "disaster_type", From aa7cf077b1a6ac7c50756c0be9640a9e3712dd3d Mon Sep 17 00:00:00 2001 From: rup Date: Mon, 8 Aug 2022 15:14:17 +0545 Subject: [PATCH 11/13] Optimized query number for eap list --- eap/serializers.py | 29 ++++++++++++++++------------- eap/views.py | 25 ++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/eap/serializers.py b/eap/serializers.py index d10daec7c..b79d35e9f 100644 --- a/eap/serializers.py +++ b/eap/serializers.py @@ -60,7 +60,7 @@ class Meta: read_only_fields = ('early_action',) -class PrioritizedRiskSerializer(serializers.ModelSerializer): +class PrioritizedRiskSerializer(serializers.ModelSerializer): class Meta: model = PrioritizedRisk fields = ('__all__') @@ -90,13 +90,10 @@ class Meta: class EAPDocumentSerializer(serializers.ModelSerializer): - created_by_details = UserNameSerializer(source='created_by', read_only=True) - file = serializers.FileField(required=False) class Meta: model = EAPDocument - fields = '__all__' - read_only_fields = ('created_by',) + fields = ['id', 'file'] def create(self, validated_data): validated_data['created_by'] = self.context['request'].user @@ -112,11 +109,11 @@ class EAPSerializer( districts_details = MiniDistrictSerializer(source='districts', many=True, read_only=True) references = EAPReferenceSerializer(source='eap_reference', many=True, required=False) partners = EAPPartnerSerializer(source='eap_partner', many=True, required=False) - # early_actions = EarlyActionSerializer(many=True) - # created_by_details = UserNameSerializer(source='created_by', read_only=True) - # modified_by_details = UserNameSerializer(source='modified_by', read_only=True) - # hazard_type_details = DisasterTypeSerializer(source='disaster_type', read_only=True) - # documents_details = EAPDocumentSerializer(source='documents', many=True, read_only=True, required=False) + early_actions = EarlyActionSerializer(many=True) + created_by_details = UserNameSerializer(source='created_by', read_only=True) + modified_by_details = UserNameSerializer(source='modified_by', read_only=True) + hazard_type_details = DisasterTypeSerializer(source='disaster_type', read_only=True) + documents_details = EAPDocumentSerializer(source='documents', many=True, read_only=True, required=False) status_display = serializers.CharField(source='get_status_display', read_only=True) class Meta: @@ -151,15 +148,21 @@ class Meta: "ifrc_focal_email", "ifrc_focal_phone", "created_by", + "created_by_details", "modified_by", + "modified_by_details", "country", - "disaster_type", - "status_display", "country_details", + # "districts", "districts_details", + "disaster_type", + "hazard_type_details", + "status_display", "references", "partners", - + "documents", + "documents_details", + "early_actions", ] def validate(self, validated_data): diff --git a/eap/views.py b/eap/views.py index a648f38e8..6386cec36 100644 --- a/eap/views.py +++ b/eap/views.py @@ -57,14 +57,23 @@ class EAPViewSet(viewsets.ModelViewSet): permission_classes = [permissions.IsAuthenticated] def get_queryset(self): + # return EAP.objects.all() return EAP.objects.all().order_by( '-created_at' ).select_related( 'country', + 'created_by', + # 'modified_by', + 'disaster_type', ).prefetch_related( 'districts', - 'eap_reference__eap', - 'eap_partner__eap' + 'documents', + 'early_actions', + 'early_actions__action', + 'early_actions__early_actions_prioritized_risk', + 'early_actions__indicators', + 'eap_reference', + 'eap_partner', ) @@ -73,7 +82,17 @@ class EAPActivationReportViewSet(viewsets.ModelViewSet): permission_classes = [permissions.IsAuthenticated] def get_queryset(self): - return EAPActivationReport.objects.all().order_by('-created_at') + return EAPActivationReport.objects.all().order_by( + '-created_at' + ).select_related( + 'created_by', + 'modified_by', + 'eap_activation', + 'ifrc_financial_report', + ).prefetch_related( + 'documents', + 'operational_plans', + ) class EAPOptionsView(views.APIView): From 7343ef787107c5979d553cdf75bd2bb127fcc513 Mon Sep 17 00:00:00 2001 From: rup Date: Tue, 9 Aug 2022 15:58:05 +0545 Subject: [PATCH 12/13] Removed html form form the browsable api --- eap/serializers.py | 73 +++++++++------------------------------------- eap/views.py | 8 ++--- main/settings.py | 3 +- main/utils.py | 20 +++++++++++++ 4 files changed, 39 insertions(+), 65 deletions(-) diff --git a/eap/serializers.py b/eap/serializers.py index b79d35e9f..dec67339b 100644 --- a/eap/serializers.py +++ b/eap/serializers.py @@ -8,6 +8,7 @@ CountrySerializer, MiniDistrictSerializer, ) +from api.models import District from eap.models import ( EAP, @@ -60,7 +61,7 @@ class Meta: read_only_fields = ('early_action',) -class PrioritizedRiskSerializer(serializers.ModelSerializer): +class PrioritizedRiskSerializer(serializers.ModelSerializer): class Meta: model = PrioritizedRisk fields = ('__all__') @@ -118,74 +119,26 @@ class EAPSerializer( class Meta: model = EAP - fields = [ - "id", - "created_at", - "modified_at", - "eap_number", - "approval_date", - "status", - "operational_timeframe", - "lead_time", - "eap_timeframe", - "num_of_people", - "total_budget", - "readiness_budget", - "pre_positioning_budget", - "early_action_budget", - "trigger_statement", - "overview", - "originator_name", - "originator_title", - "originator_email", - "originator_phone", - "nsc_name", - "nsc_title", - "nsc_email", - "nsc_phone", - "ifrc_focal_name", - "ifrc_focal_title", - "ifrc_focal_email", - "ifrc_focal_phone", - "created_by", - "created_by_details", - "modified_by", - "modified_by_details", - "country", - "country_details", - # "districts", - "districts_details", - "disaster_type", - "hazard_type_details", - "status_display", - "references", - "partners", - "documents", - "documents_details", - "early_actions", - ] - - def validate(self, validated_data): - if self.partial: - return validated_data - districts = validated_data.get('districts', None) - if districts: + fields = ('__all__') + + def validate(self, data): + if 'districts' in data: + districts = data.get('districts') or [] + country = data.get('country') for district in districts: - if district.country != validated_data['country']: + if district.country != country: raise serializers.ValidationError({ - 'district': ugettext('Different districts found for given country') + 'districts': ugettext('Different districts found for given country') }) - return validated_data + return data def create(self, validated_data): validated_data['created_by'] = self.context['request'].user - eap = super().create(validated_data) - return eap + return super().create(validated_data) def update(self, instance, validated_data): validated_data['modified_by'] = self.context['request'].user - eap = super().update(instance, validated_data) - return eap + return super().update(instance, validated_data) class EAPActivationSerializer(serializers.ModelSerializer): diff --git a/eap/views.py b/eap/views.py index 6386cec36..30fcd1ebc 100644 --- a/eap/views.py +++ b/eap/views.py @@ -1,5 +1,4 @@ # Create your views here. - from rest_framework import ( views, viewsets, @@ -57,16 +56,16 @@ class EAPViewSet(viewsets.ModelViewSet): permission_classes = [permissions.IsAuthenticated] def get_queryset(self): - # return EAP.objects.all() - return EAP.objects.all().order_by( + result = EAP.objects.all().order_by( '-created_at' ).select_related( 'country', 'created_by', - # 'modified_by', + 'modified_by', 'disaster_type', ).prefetch_related( 'districts', + 'districts__country', 'documents', 'early_actions', 'early_actions__action', @@ -75,6 +74,7 @@ def get_queryset(self): 'eap_reference', 'eap_partner', ) + return result class EAPActivationReportViewSet(viewsets.ModelViewSet): diff --git a/main/settings.py b/main/settings.py index 98fbca79d..0e3933d11 100644 --- a/main/settings.py +++ b/main/settings.py @@ -176,7 +176,8 @@ ), 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', - 'rest_framework.renderers.BrowsableAPIRenderer', + # 'rest_framework.renderers.BrowsableAPIRenderer', # it is comment out to reduce load time in browsable api + 'main.utils.BrowsableAPIRendererWithRawForms', # it is added to reduce load time in browsable api 'rest_framework_csv.renderers.PaginatedCSVRenderer', ), 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', diff --git a/main/utils.py b/main/utils.py index c6e3951cc..c06a50d02 100644 --- a/main/utils.py +++ b/main/utils.py @@ -1,5 +1,7 @@ from collections import defaultdict +from rest_framework.renderers import BrowsableAPIRenderer + def is_tableau(request): """ Checking the request for the 'tableau' parameter @@ -24,3 +26,21 @@ def get_merged_items_by_fields(items, fields, seperator=', '): field: seperator.join(data[field]) for field in fields } + + +class BrowsableAPIRendererWithRawForms(BrowsableAPIRenderer): # It is done to reduce load time in browsable api. + """ + Renders the browsable api, but excludes the normal forms. + Only show raw form. + """ + + def get_context(self, *args, **kwargs): + ctx = super().get_context(*args, **kwargs) + ctx['post_form'] = None + return ctx + + def get_rendered_html_form(self, data, view, method, request): + """Why render _any_ forms at all. This method should return + rendered HTML, so let's simply return an empty string. + """ + return "" From 74367b77fba8daea0cf5e32c3bd3ea7a124ad115 Mon Sep 17 00:00:00 2001 From: rup Date: Tue, 13 Sep 2022 15:11:09 +0545 Subject: [PATCH 13/13] Added caption for file upload --- api/drf_views.py | 23 ++++++++++----- api/serializers.py | 5 ++-- api/test_views.py | 18 ++++++++++-- eap/migrations/0009_auto_20220811_0836.py | 23 +++++++++++++++ eap/models.py | 2 +- eap/serializers.py | 19 ++++++++++--- eap/test_views.py | 34 ++++++++++++++++++++--- main/settings.py | 2 +- 8 files changed, 105 insertions(+), 21 deletions(-) create mode 100644 eap/migrations/0009_auto_20220811_0836.py diff --git a/api/drf_views.py b/api/drf_views.py index 99cf93763..84a146810 100644 --- a/api/drf_views.py +++ b/api/drf_views.py @@ -919,14 +919,18 @@ def create_event(self, report): def create_eap_activation(self, data, fieldreport): eap = EAP.objects.filter(id=data.pop('eap', None)).first() - documents = EAPDocument.objects.filter(id__in=data.pop('documents', None)) + documents_data = data.pop('documents', None) eap_activation = EAPActivation.objects.create( eap=eap, field_report=fieldreport, **data ) - if documents: - for document in documents: + if documents_data: + for data in documents_data: + document = EAPDocument.objects.filter(id=data['id']).first() + document.caption = data['caption'] + document.save(update_fields=['caption']) + # save m2m eap_activation.documents.add(document) return eap_activation @@ -999,13 +1003,18 @@ def update_eap_activation(self, data, fieldreport): from eap.serializers import EAPActivationSerializer eap_id = data.pop('eap', None) - document_ids = data.pop('documents', None) + documents_data = data.pop('documents', None) instance = EAPActivation.objects.get(field_report=fieldreport) eap_activation = EAPActivationSerializer().update(instance, data) instance.eap = EAP.objects.filter(id=eap_id).first() - if document_ids: - documents = EAPDocument.objects.filter(id__in=document_ids) - for document in documents: + + instance.documents.clear() + if documents_data: + for data in documents_data: + document = EAPDocument.objects.filter(id=data['id']).first() + document.caption = data['caption'] + document.save(update_fields=['caption']) + # save m2m instance.documents.add(document) instance.save(update_fields=['eap']) diff --git a/api/serializers.py b/api/serializers.py index 6f2f8ceaa..40500e185 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -8,6 +8,7 @@ from lang.serializers import ModelSerializer from lang.models import String + from .models import ( DisasterType, ExternalPartner, @@ -1062,8 +1063,6 @@ class Meta: class DetailFieldReportSerializer(FieldReportEnumDisplayMixin, ModelSerializer): - from eap.serializers import EAPActivationSerializer - user = UserSerializer() dtype = DisasterTypeSerializer() contacts = FieldReportContactSerializer(many=True) @@ -1078,6 +1077,8 @@ class DetailFieldReportSerializer(FieldReportEnumDisplayMixin, ModelSerializer): @staticmethod def get_eap_activation(obj): + from eap.serializers import EAPActivationSerializer + eap_activation = EAPActivation.objects.get(field_report=obj) eap_activation_data = EAPActivationSerializer(eap_activation).data return eap_activation_data diff --git a/api/test_views.py b/api/test_views.py index ee2dabe04..8ebcf37ee 100644 --- a/api/test_views.py +++ b/api/test_views.py @@ -115,7 +115,12 @@ def test_create_and_update(self): 'eap': eap1.id, 'description': 'test eap description', 'trigger_met_date': '2022-11-11 00:00', - 'documents': [document1.id], + 'documents': [ + { + "id": document1.id, + "caption": "test eap" + } + ], 'originator_name': 'test name', 'originator_title': 'test originator title', 'originator_email': 'test@email.com', @@ -181,7 +186,16 @@ def test_create_and_update(self): 'eap': eap2.id, 'description': 'test eap description updated', 'trigger_met_date': '2022-11-11 01:00', - 'documents': [document2.id], + 'documents': [ + { + "id": document1.id, + "caption": "test eap updated" + }, + { + "id": document2.id, + "caption": "test eap updated 2" + } + ], 'originator_name': 'test name', 'originator_title': 'test originator title', 'originator_email': 'test@email.com', diff --git a/eap/migrations/0009_auto_20220811_0836.py b/eap/migrations/0009_auto_20220811_0836.py new file mode 100644 index 000000000..161abfa60 --- /dev/null +++ b/eap/migrations/0009_auto_20220811_0836.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.28 on 2022-08-11 08:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eap', '0008_auto_20220804_0505'), + ] + + operations = [ + migrations.AddField( + model_name='eapdocument', + name='caption', + field=models.CharField(blank=True, max_length=225, null=True), + ), + migrations.AlterField( + model_name='earlyaction', + name='sector', + field=models.IntegerField(choices=[(0, 'Shelter, Housing And Settlements'), (1, 'Livelihoods'), (2, 'Multi-purpose Cash'), (3, 'Health And Care'), (4, 'Water, Sanitation And Hygiene'), (5, 'Protection, Gender And Inclusion'), (6, 'Education'), (7, 'Migration'), (8, 'Risk Reduction, Climate Adaptation And Recovery'), (9, 'Community Engagement And Accountability'), (10, 'Environment Sustainability'), (11, 'Shelter Cluster Coordination')], verbose_name='sector'), + ), + ] diff --git a/eap/models.py b/eap/models.py index a7d86eacc..faca72af6 100644 --- a/eap/models.py +++ b/eap/models.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from main.enums import TextChoices, IntegerChoices -from deployments.models import Sectors from api.models import ( Country, District, @@ -67,6 +66,7 @@ def __str__(self): class EAPDocument(models.Model): file = models.FileField(null=True, blank=True) + caption = models.CharField(max_length=225, null=True, blank=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_('Created by'), related_name='document_created_by', null=True, blank=True, on_delete=models.SET_NULL, diff --git a/eap/serializers.py b/eap/serializers.py index dec67339b..20099c57c 100644 --- a/eap/serializers.py +++ b/eap/serializers.py @@ -94,7 +94,7 @@ class EAPDocumentSerializer(serializers.ModelSerializer): class Meta: model = EAPDocument - fields = ['id', 'file'] + fields = ['id', 'file', 'caption', ] def create(self, validated_data): validated_data['created_by'] = self.context['request'].user @@ -114,7 +114,7 @@ class EAPSerializer( created_by_details = UserNameSerializer(source='created_by', read_only=True) modified_by_details = UserNameSerializer(source='modified_by', read_only=True) hazard_type_details = DisasterTypeSerializer(source='disaster_type', read_only=True) - documents_details = EAPDocumentSerializer(source='documents', many=True, read_only=True, required=False) + documents = EAPDocumentSerializer(many=True, required=False) status_display = serializers.CharField(source='get_status_display', read_only=True) class Meta: @@ -142,7 +142,18 @@ def update(self, instance, validated_data): class EAPActivationSerializer(serializers.ModelSerializer): - document_detail = EAPDocumentSerializer(source='document', read_only=True) + document_details = serializers.SerializerMethodField('get_eap_documents') + + @staticmethod + def get_eap_documents(obj): + eap_documents = obj.documents.all() + return [ + { + 'id': document.id, + 'file': document.file.url, + 'caption': document.caption + } for document in eap_documents + ] class Meta: model = EAPActivation @@ -184,7 +195,7 @@ class EAPActivationReportSerializer( operational_plans = OperationalPlanSerializer(many=True) created_by_details = UserNameSerializer(source='created_by', read_only=True) modified_by_details = UserNameSerializer(source='modified_by', read_only=True) - document_details = EAPDocumentSerializer(source='documents', read_only=True, many=True, required=False) + documents = EAPDocumentSerializer(many=True, required=False) ifrc_financial_report_details = EAPDocumentSerializer(source='ifrc_financial_report', read_only=True) class Meta: diff --git a/eap/test_views.py b/eap/test_views.py index 14f384126..fa75af8fe 100644 --- a/eap/test_views.py +++ b/eap/test_views.py @@ -76,7 +76,16 @@ def setUp(self): "early_action_budget": 2000, "trigger_statement": "test", "overview": "test", - "documents": [self.document1.id], + "documents": [ + { + "id": self.document1.id, + "caption": "test caption" + }, + { + "id": self.document2.id, + "caption": "test caption 2" + }, + ], "originator_name": "eap name", "originator_title": "eap title", "originator_email": "eap@gmail.com", @@ -168,7 +177,16 @@ def setUp(self): "number_of_people_reached": 1000, "description": "test eap activation report", "overall_objectives": "test eap activation report", - "documents": [self.document1.id, self.document2.id], + "documents": [ + { + "id": self.document1.id, + "caption": "test caption" + }, + { + "id": self.document2.id, + "caption": "test caption 2" + }, + ], "challenges_and_lesson": "test eap activation report", "general_lesson_and_recomendations": "test eap activation report", "ifrc_financial_report": self.document1.id, @@ -315,7 +333,16 @@ def test_create_and_update_eap_activation_report(self): # update eap_report data = self.eap_act_report_body data['description'] = 'updated description' - data['documents'] = [self.document1.id] + data['documents'] = [ + { + "id": self.document2.id, + "caption": "test caption updated" + }, + { + "id": self.document1.id, + "caption": "test caption updated" + } + ] data['ifrc_financial_report'] = self.document2.id data['operational_plans'] = [ { @@ -356,7 +383,6 @@ def test_create_and_update_eap_activation_report(self): self.assertEqual(updated.created_by.id, self.user.id) self.assertEqual(final_report_updated_resp['eap_activation'], self.eap_activation.id) self.assertEqual(final_report_updated_resp['ifrc_financial_report'], self.document2.id) - self.assertEqual(len(final_report_updated_resp['documents']), 1) self.assertEqual(len(final_report_updated_resp['operational_plans']), 1) self.assertEqual(len(final_report_updated_resp['operational_plans'][0]['early_actions_achievements']), 2) self.assertEqual(len(final_report_updated_resp['operational_plans'][0]['indicators']), 2) diff --git a/main/settings.py b/main/settings.py index 0e3933d11..82328901a 100644 --- a/main/settings.py +++ b/main/settings.py @@ -177,7 +177,7 @@ 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', # 'rest_framework.renderers.BrowsableAPIRenderer', # it is comment out to reduce load time in browsable api - 'main.utils.BrowsableAPIRendererWithRawForms', # it is added to reduce load time in browsable api + 'main.utils.BrowsableAPIRendererWithRawForms', # it is added to remove html form and reduce load time in browsable api 'rest_framework_csv.renderers.PaginatedCSVRenderer', ), 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',