From c161fbbd91fa5f74bacb2f83bac84e86037391cd Mon Sep 17 00:00:00 2001 From: Ema Ciupe Date: Wed, 5 Jun 2024 15:15:00 +0300 Subject: [PATCH 1/6] [ch29971] Admin agreements: add-the-ability-to-revert-a-terminated-pca --- src/etools/applications/partners/admin.py | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/etools/applications/partners/admin.py b/src/etools/applications/partners/admin.py index b46b8b5e72..051dc0ac62 100644 --- a/src/etools/applications/partners/admin.py +++ b/src/etools/applications/partners/admin.py @@ -6,6 +6,7 @@ from django.forms import SelectMultiple from django.http.response import HttpResponseRedirect from django.urls import reverse +from django.utils import timezone from django.utils.html import format_html from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ @@ -17,6 +18,7 @@ from unicef_attachments.admin import AttachmentSingleInline from unicef_attachments.models import Attachment from unicef_snapshot.admin import ActivityInline, SnapshotModelAdmin +from unicef_snapshot.models import Activity from etools.applications.partners.exports import PartnerExport from etools.applications.partners.forms import InterventionAttachmentForm # TODO intervention sector locations cleanup @@ -715,6 +717,7 @@ class AgreementAdmin( HiddenPartnerMixin, CountryUsersAdminMixin, RestrictedEditAdminMixin, + ExtraUrlMixin, SnapshotModelAdmin, ): staff_only = False @@ -811,6 +814,29 @@ def get_interventions_admin_urls(self, objs): urls.append(formatted_url) return urls + @button(permission=lambda request, obj: request.user.groups.filter(name='RSS').exists() and + obj.status == Agreement.TERMINATED and obj.end > timezone.now().date()) + def revert_termination(self, request, pk): + agreement = Agreement.objects.get(pk=pk) + agreement.status = Agreement.SIGNED + agreement.save(update_fields=['status']) + terminated_interventions = agreement.interventions.filter(status=Intervention.TERMINATED) + for i in terminated_interventions: + intervention_activities = Activity.objects.filter( + target_content_type=ContentType.objects.get_for_model(Intervention), + target_object_id=i.id, + action=Activity.UPDATE, + change__status__after=Intervention.TERMINATED, + ) + if not intervention_activities.exists(): + continue + previous_status = intervention_activities.last().change['status']['before'] + i.status = previous_status + + Intervention.objects.bulk_update(terminated_interventions, fields=['status']) + + return HttpResponseRedirect(reverse('admin:partners_agreement_change', args=[pk])) + class FileTypeAdmin(RestrictedEditAdmin): From d65616a1a9aa49ccb9326581e994981576cd4151 Mon Sep 17 00:00:00 2001 From: Ema Ciupe Date: Fri, 7 Jun 2024 15:44:31 +0300 Subject: [PATCH 2/6] [ch29971] Admin agreements: add-the-ability-to-revert-a-terminated-pca --- src/etools/applications/partners/admin.py | 26 +++++++++- .../applications/partners/tests/test_admin.py | 52 ++++++++++++++++++- 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/etools/applications/partners/admin.py b/src/etools/applications/partners/admin.py index 051dc0ac62..7c9228007b 100644 --- a/src/etools/applications/partners/admin.py +++ b/src/etools/applications/partners/admin.py @@ -1,3 +1,5 @@ +import os + from django.conf import settings from django.contrib import admin, messages from django.contrib.admin.utils import quote @@ -711,6 +713,11 @@ class AgreementAttachmentInline(AttachmentSingleInline): code = 'partners_agreement' +class AgreementTerminationDocAttachmentInline(AttachmentSingleInline): + verbose_name_plural = _('Termination Doc Attachment') + code = 'partners_agreement_termination_doc' + + class AgreementAdmin( AttachmentInlineAdminMixin, ExportMixin, @@ -769,6 +776,7 @@ class AgreementAdmin( inlines = [ ActivityInline, AgreementAttachmentInline, + AgreementTerminationDocAttachmentInline ] def has_module_permission(self, request): @@ -819,8 +827,19 @@ def get_interventions_admin_urls(self, objs): def revert_termination(self, request, pk): agreement = Agreement.objects.get(pk=pk) agreement.status = Agreement.SIGNED + termination_doc = Attachment.objects.get( + code='partners_agreement_termination_doc', + content_type=ContentType.objects.get_for_model(Agreement), + object_id=agreement.pk + ) + if os.path.isfile(termination_doc.file.path): + os.remove(termination_doc.file.path) + agreement.termination_doc.remove(termination_doc) + agreement.save(update_fields=['status']) + terminated_interventions = agreement.interventions.filter(status=Intervention.TERMINATED) + interventions_to_update = [] for i in terminated_interventions: intervention_activities = Activity.objects.filter( target_content_type=ContentType.objects.get_for_model(Intervention), @@ -830,10 +849,13 @@ def revert_termination(self, request, pk): ) if not intervention_activities.exists(): continue + previous_status = intervention_activities.last().change['status']['before'] - i.status = previous_status + if previous_status in [Intervention.SIGNED, Intervention.ACTIVE, Intervention.SUSPENDED]: + i.status = previous_status + interventions_to_update.append(i) - Intervention.objects.bulk_update(terminated_interventions, fields=['status']) + Intervention.objects.bulk_update(interventions_to_update, fields=['status']) return HttpResponseRedirect(reverse('admin:partners_agreement_change', args=[pk])) diff --git a/src/etools/applications/partners/tests/test_admin.py b/src/etools/applications/partners/tests/test_admin.py index 4df826a664..ef1da6c995 100644 --- a/src/etools/applications/partners/tests/test_admin.py +++ b/src/etools/applications/partners/tests/test_admin.py @@ -1,15 +1,18 @@ import datetime +import os.path from django.contrib.admin.sites import AdminSite +from django.core.files.uploadedfile import SimpleUploadedFile from unicef_snapshot.models import Activity +from etools.applications.attachments.tests.factories import AttachmentFactory from etools.applications.core.tests.cases import BaseTenantTestCase from etools.applications.partners.admin import AgreementAdmin, InterventionAdmin from etools.applications.partners.models import Agreement, Intervention from etools.applications.partners.tests.factories import AgreementFactory, InterventionFactory, PartnerFactory from etools.applications.reports.tests.factories import CountryProgrammeFactory -from etools.applications.users.tests.factories import UserFactory +from etools.applications.users.tests.factories import CountryFactory, GroupFactory, RealmFactory, UserFactory class MockRequest: @@ -103,3 +106,50 @@ def test_save_model_update(self): "after": Agreement.TERMINATED } }) + + def test_revert_termination(self): + RealmFactory( + user=self.user, + country=CountryFactory(), + organization=self.user.profile.organization, + group=GroupFactory(name='RSS') + ) + + agreement = AgreementFactory( + partner=self.partner, status=Agreement.TERMINATED, end=datetime.date.today() + datetime.timedelta(days=7)) + a = AttachmentFactory( + code='partners_agreement_termination_doc', + content_object=agreement, + file=SimpleUploadedFile('simple_file.txt', b'simple_file.txt'), + ) + agreement.termination_doc.add(a) + suspended_pd = InterventionFactory( + agreement=agreement, + title='Intervention 1', + status=Intervention.SUSPENDED, + ) + ia = InterventionAdmin(Intervention, self.site) + suspended_pd.status = Intervention.TERMINATED + ia.save_model(self.request, suspended_pd, {}, True) + + closed_pd = InterventionFactory( + agreement=agreement, + title='Intervention 1', + status=Intervention.CLOSED, + ) + closed_pd.status = Intervention.TERMINATED + ia.save_model(self.request, closed_pd, {}, True) + + aa = AgreementAdmin(Agreement, self.site) + aa.revert_termination(self.request, pk=agreement.pk) + + agreement.refresh_from_db() + self.assertEqual(agreement.status, Agreement.SIGNED) + self.assertEqual(agreement.termination_doc.count(), 0) + self.assertFalse(os.path.exists(a.file.path)) + + suspended_pd.refresh_from_db() + self.assertEqual(suspended_pd.status, Intervention.SUSPENDED) + + closed_pd.refresh_from_db() + self.assertEqual(closed_pd.status, Intervention.TERMINATED) From 1f60284e0f72cb5af536e6de1e24c754a99af5de Mon Sep 17 00:00:00 2001 From: Ema Ciupe Date: Fri, 7 Jun 2024 16:01:16 +0300 Subject: [PATCH 3/6] locales --- .../applications/partners/locale/ar/LC_MESSAGES/django.po | 5 ++++- .../applications/partners/locale/es/LC_MESSAGES/django.po | 5 ++++- .../applications/partners/locale/fr/LC_MESSAGES/django.po | 5 ++++- .../applications/partners/locale/pt/LC_MESSAGES/django.po | 5 ++++- .../applications/partners/locale/ru/LC_MESSAGES/django.po | 5 ++++- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/etools/applications/partners/locale/ar/LC_MESSAGES/django.po b/src/etools/applications/partners/locale/ar/LC_MESSAGES/django.po index 09cea1a9b0..ff714b3d0c 100644 --- a/src/etools/applications/partners/locale/ar/LC_MESSAGES/django.po +++ b/src/etools/applications/partners/locale/ar/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-05-21 04:41+0000\n" +"POT-Creation-Date: 2024-06-07 12:58+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -64,6 +64,9 @@ msgstr "إطار النهج المنسق للتحويلات النقدية" msgid "Signed Amendment" msgstr "التعديل الموقع" +msgid "Termination Doc Attachment" +msgstr "" + msgid "Agreement Details" msgstr "تفاصيل الاتفاقية" diff --git a/src/etools/applications/partners/locale/es/LC_MESSAGES/django.po b/src/etools/applications/partners/locale/es/LC_MESSAGES/django.po index 5001826676..b8cf1826db 100644 --- a/src/etools/applications/partners/locale/es/LC_MESSAGES/django.po +++ b/src/etools/applications/partners/locale/es/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: et-partners2-bk\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-05-21 04:41+0000\n" +"POT-Creation-Date: 2024-06-07 12:58+0000\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -55,6 +55,9 @@ msgstr "Hact" msgid "Signed Amendment" msgstr "Enmienda firmada" +msgid "Termination Doc Attachment" +msgstr "" + msgid "Agreement Details" msgstr "Detalles del acuerdo" diff --git a/src/etools/applications/partners/locale/fr/LC_MESSAGES/django.po b/src/etools/applications/partners/locale/fr/LC_MESSAGES/django.po index e993259a2f..2d3c8cc946 100644 --- a/src/etools/applications/partners/locale/fr/LC_MESSAGES/django.po +++ b/src/etools/applications/partners/locale/fr/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-05-21 04:41+0000\n" +"POT-Creation-Date: 2024-06-07 12:58+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -63,6 +63,9 @@ msgstr "Hact" msgid "Signed Amendment" msgstr "Amendement signé" +msgid "Termination Doc Attachment" +msgstr "" + msgid "Agreement Details" msgstr "Détails de l'accord" diff --git a/src/etools/applications/partners/locale/pt/LC_MESSAGES/django.po b/src/etools/applications/partners/locale/pt/LC_MESSAGES/django.po index 57e8b4c536..9f55cadb7d 100644 --- a/src/etools/applications/partners/locale/pt/LC_MESSAGES/django.po +++ b/src/etools/applications/partners/locale/pt/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-05-21 04:41+0000\n" +"POT-Creation-Date: 2024-06-07 12:58+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -63,6 +63,9 @@ msgstr "HACT" msgid "Signed Amendment" msgstr "Aditivo assinado" +msgid "Termination Doc Attachment" +msgstr "" + msgid "Agreement Details" msgstr "Detalhes do Acordo" diff --git a/src/etools/applications/partners/locale/ru/LC_MESSAGES/django.po b/src/etools/applications/partners/locale/ru/LC_MESSAGES/django.po index 9728509bdc..c7c9adc4f5 100644 --- a/src/etools/applications/partners/locale/ru/LC_MESSAGES/django.po +++ b/src/etools/applications/partners/locale/ru/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-05-21 04:41+0000\n" +"POT-Creation-Date: 2024-06-07 12:58+0000\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -61,6 +61,9 @@ msgstr "ГПДП" msgid "Signed Amendment" msgstr "Подписанная поправка" +msgid "Termination Doc Attachment" +msgstr "" + msgid "Agreement Details" msgstr "Детали соглашения" From d963c930a6d32529cbb75d26c8f4c7ab504aedd1 Mon Sep 17 00:00:00 2001 From: Ema Ciupe Date: Fri, 7 Jun 2024 17:29:39 +0300 Subject: [PATCH 4/6] CR: undo delete file from disk --- src/etools/applications/partners/admin.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/etools/applications/partners/admin.py b/src/etools/applications/partners/admin.py index 7c9228007b..bc8073c38b 100644 --- a/src/etools/applications/partners/admin.py +++ b/src/etools/applications/partners/admin.py @@ -827,14 +827,15 @@ def get_interventions_admin_urls(self, objs): def revert_termination(self, request, pk): agreement = Agreement.objects.get(pk=pk) agreement.status = Agreement.SIGNED - termination_doc = Attachment.objects.get( - code='partners_agreement_termination_doc', - content_type=ContentType.objects.get_for_model(Agreement), - object_id=agreement.pk - ) - if os.path.isfile(termination_doc.file.path): - os.remove(termination_doc.file.path) - agreement.termination_doc.remove(termination_doc) + try: + termination_doc = Attachment.objects.get( + code='partners_agreement_termination_doc', + content_type=ContentType.objects.get_for_model(Agreement), + object_id=agreement.pk + ) + agreement.termination_doc.remove(termination_doc) + except Attachment.DoesNotExist: + pass agreement.save(update_fields=['status']) From 7f434b37ebc83973992504f201acf1091d0cc7ce Mon Sep 17 00:00:00 2001 From: Ema Ciupe Date: Fri, 7 Jun 2024 17:52:02 +0300 Subject: [PATCH 5/6] flake --- src/etools/applications/partners/admin.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/etools/applications/partners/admin.py b/src/etools/applications/partners/admin.py index bc8073c38b..49b95a8f5c 100644 --- a/src/etools/applications/partners/admin.py +++ b/src/etools/applications/partners/admin.py @@ -1,5 +1,3 @@ -import os - from django.conf import settings from django.contrib import admin, messages from django.contrib.admin.utils import quote From c094ecd0ee2bf0816ea4da1b3cc9c5d398678e7c Mon Sep 17 00:00:00 2001 From: Ema Ciupe Date: Fri, 7 Jun 2024 20:07:29 +0300 Subject: [PATCH 6/6] fixed test --- src/etools/applications/partners/tests/test_admin.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/etools/applications/partners/tests/test_admin.py b/src/etools/applications/partners/tests/test_admin.py index ef1da6c995..ca4201a4d4 100644 --- a/src/etools/applications/partners/tests/test_admin.py +++ b/src/etools/applications/partners/tests/test_admin.py @@ -1,5 +1,4 @@ import datetime -import os.path from django.contrib.admin.sites import AdminSite from django.core.files.uploadedfile import SimpleUploadedFile @@ -146,7 +145,6 @@ def test_revert_termination(self): agreement.refresh_from_db() self.assertEqual(agreement.status, Agreement.SIGNED) self.assertEqual(agreement.termination_doc.count(), 0) - self.assertFalse(os.path.exists(a.file.path)) suspended_pd.refresh_from_db() self.assertEqual(suspended_pd.status, Intervention.SUSPENDED)