From b794ea39e9c3066a0939fe7c58387e2f1b56d1a8 Mon Sep 17 00:00:00 2001 From: Todd Dembrey <todd.dembrey@torchbox.com> Date: Tue, 5 Nov 2019 15:04:33 +0000 Subject: [PATCH] Add basic management command to trigger reminders about reports --- opentech/apply/dashboard/views.py | 2 +- .../management/commands/notify_report_due.py | 23 ++++++++++ opentech/apply/projects/models.py | 9 +++- opentech/apply/projects/tests/factories.py | 9 ++++ .../apply/projects/tests/test_commands.py | 46 +++++++++++++++++++ 5 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 opentech/apply/projects/management/commands/notify_report_due.py create mode 100644 opentech/apply/projects/tests/test_commands.py diff --git a/opentech/apply/dashboard/views.py b/opentech/apply/dashboard/views.py index 9b963a58f..acfcb4929 100644 --- a/opentech/apply/dashboard/views.py +++ b/opentech/apply/dashboard/views.py @@ -342,7 +342,7 @@ class ApplicantDashboardView(MultiTableMixin, TemplateView): return context def get_active_project_data(self, user): - return Project.objects.filter(user=user).in_progress().for_table() + return Project.objects.filter(user=user).active().for_table() def get_active_submissions(self, user): active_subs = ApplicationSubmission.objects.filter( diff --git a/opentech/apply/projects/management/commands/notify_report_due.py b/opentech/apply/projects/management/commands/notify_report_due.py new file mode 100644 index 000000000..d0162caed --- /dev/null +++ b/opentech/apply/projects/management/commands/notify_report_due.py @@ -0,0 +1,23 @@ +from dateutil.relativedelta import relativedelta + +from django.core.management.base import BaseCommand +from django.utils import timezone + +from opentech.apply.projects.models import Project + + +class Command(BaseCommand): + help = 'Notify users that they have a report due soon' + + def add_arguments(self, parser): + parser.add_argument('days', type=int) + + def handle(self, *args, **options): + due_date = timezone.now().date() + relativedelta(days=options['days']) + for project in Project.objects.in_progress(): + next_report = project.report_config.current_due_report() + if next_report.end_date == due_date: + # Notify about the due report + self.stdout.write( + self.style.SUCCESS(f'Notified project: {project.id}') + ) diff --git a/opentech/apply/projects/models.py b/opentech/apply/projects/models.py index eb6b6d08b..c652a2a24 100644 --- a/opentech/apply/projects/models.py +++ b/opentech/apply/projects/models.py @@ -285,9 +285,16 @@ PROJECT_STATUS_CHOICES = [ class ProjectQuerySet(models.QuerySet): - def in_progress(self): + def active(self): + "Projects that are not finished" return self.exclude(status=COMPLETE) + def in_progress(self): + "Projects that users need to interact with, submitting reports or payment request" + return self.filter( + status__in=(IN_PROGRESS, CLOSING,) + ) + def complete(self): return self.filter(status=COMPLETE) diff --git a/opentech/apply/projects/tests/factories.py b/opentech/apply/projects/tests/factories.py index 1768a12cc..a585a8b0b 100644 --- a/opentech/apply/projects/tests/factories.py +++ b/opentech/apply/projects/tests/factories.py @@ -10,6 +10,7 @@ from opentech.apply.funds.tests.factories import ApplicationSubmissionFactory from opentech.apply.projects.models import ( Contract, DocumentCategory, + IN_PROGRESS, PacketFile, PaymentReceipt, PaymentRequest, @@ -97,6 +98,9 @@ class ProjectFactory(factory.DjangoModelFactory): in_approval = factory.Trait( is_locked=True, ) + in_progress = factory.Trait( + status=IN_PROGRESS, + ) class ContractFactory(factory.DjangoModelFactory): @@ -155,6 +159,11 @@ class ReportConfigFactory(factory.DjangoModelFactory): model = ReportConfig django_get_or_create = ('project',) + class Params: + weeks = factory.Trait( + frequency=ReportConfig.WEEK, + ) + class ReportVersionFactory(factory.DjangoModelFactory): report = factory.SubFactory("opentech.apply.projects.tests.factories.ReportFactory") diff --git a/opentech/apply/projects/tests/test_commands.py b/opentech/apply/projects/tests/test_commands.py new file mode 100644 index 000000000..b0ee1023a --- /dev/null +++ b/opentech/apply/projects/tests/test_commands.py @@ -0,0 +1,46 @@ +from io import StringIO + +from dateutil.relativedelta import relativedelta + +from django.core.management import call_command +from django.test import TestCase +from django.utils import timezone + +from .factories import ( + ProjectFactory, + ReportConfigFactory, + ReportFactory, +) + + +class TestNotifyReportDue(TestCase): + def test_notify_report_due_in_7_days(self): + in_a_week = timezone.now() + relativedelta(days=7) + ReportConfigFactory(schedule_start=in_a_week, project__in_progress=True) + out = StringIO() + call_command('notify_report_due', 7, stdout=out) + self.assertIn('Notified project', out.getvalue()) + + def test_dont_notify_report_due_in_7_days_already_submitted(self): + in_a_week = timezone.now() + relativedelta(days=7) + config = ReportConfigFactory(schedule_start=in_a_week) + ReportFactory( + project=config.project, + is_submitted=True, + end_date=config.schedule_start, + ) + out = StringIO() + call_command('notify_report_due', 7, stdout=out) + self.assertNotIn('Notified project', out.getvalue()) + + def test_dont_notify_project_not_in_progress(self): + ProjectFactory() + out = StringIO() + call_command('notify_report_due', 7, stdout=out) + self.assertNotIn('Notified project', out.getvalue()) + + def test_dont_notify_project_closed(self): + ProjectFactory() + out = StringIO() + call_command('notify_report_due', 7, stdout=out) + self.assertNotIn('Notified project', out.getvalue()) -- GitLab