diff --git a/opentech/apply/projects/forms.py b/opentech/apply/projects/forms.py index e5e84899d6e4a6df4c7fa6f28f39d28df143d49a..dbbf3125d0489889c21298d70705c3974c74398a 100644 --- a/opentech/apply/projects/forms.py +++ b/opentech/apply/projects/forms.py @@ -399,6 +399,10 @@ class ReportEditForm(forms.ModelForm): self.fields['file_list'].queryset = self.report_files self.user = user + # Cant change the privacy of a submitted report + if self.instance.current: + del self.fields['public'] + @transaction.atomic def save(self, commit=True): is_draft = 'save' in self.data @@ -414,6 +418,10 @@ class ReportEditForm(forms.ModelForm): if is_draft: self.instance.draft = version else: + # If this is the first submission of the report we track that as the + # submitted date of the report + if not self.instance.submitted: + self.instance.submitted = version.submitted self.instance.current = version self.instance.draft = None diff --git a/opentech/apply/projects/migrations/0029_report_submitted.py b/opentech/apply/projects/migrations/0029_report_submitted.py new file mode 100644 index 0000000000000000000000000000000000000000..635f0b796fed614a7adc5a0b34583c445df72221 --- /dev/null +++ b/opentech/apply/projects/migrations/0029_report_submitted.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.11 on 2019-10-31 09:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('application_projects', '0028_report_draft'), + ] + + operations = [ + migrations.AddField( + model_name='report', + name='submitted', + field=models.DateTimeField(null=True), + ), + ] diff --git a/opentech/apply/projects/models.py b/opentech/apply/projects/models.py index b792ddcf96c2e083bcf7e92198c0dfd26e4a5a01..36388f8742c78a3fd296949dfca5dff5e0be5b44 100644 --- a/opentech/apply/projects/models.py +++ b/opentech/apply/projects/models.py @@ -601,6 +601,7 @@ class ReportConfig(models.Model): report, _ = self.project.reports.update_or_create( project=self.project, end_date__gte=today, + current__isnull=True, defaults={'end_date': next_due_date} ) return report @@ -621,6 +622,7 @@ class Report(models.Model): public = models.BooleanField(default=True) end_date = models.DateField() project = models.ForeignKey("Project", on_delete=models.CASCADE, related_name="reports") + submitted = models.DateTimeField(null=True) current = models.OneToOneField( "ReportVersion", on_delete=models.CASCADE, @@ -645,7 +647,9 @@ class Report(models.Model): @property def is_very_late(self): - more_than_one_report_late = self.project.reports.filter(end_date__gt=self.end_date).count() >= 2 + more_than_one_report_late = self.project.reports.filter( + Q(end_date__gt=self.end_date) & Q(end_date__lt=timezone.now().date()) + ).count() >= 1 not_submitted = not self.current return not_submitted and more_than_one_report_late @@ -655,12 +659,12 @@ class Report(models.Model): @property def submitted_date(self): - if self.current: - return self.current.submitted.date() + if self.submitted: + return self.submitted.date() @cached_property def start_date(self): - last_report = self.project.reports.order_by('current__submitted').filter(end_date__lt=self.end_date).first() + last_report = self.project.reports.filter(end_date__lt=self.end_date).first() if last_report: return last_report.end_date + relativedelta(days=1) diff --git a/opentech/apply/projects/templates/application_projects/includes/reports.html b/opentech/apply/projects/templates/application_projects/includes/reports.html index be0b115cb57d7301818f82dc8dfcc7d2e4d5e0e0..81d83f5c25fcffb327ef0358c95d69e23242d542 100644 --- a/opentech/apply/projects/templates/application_projects/includes/reports.html +++ b/opentech/apply/projects/templates/application_projects/includes/reports.html @@ -33,6 +33,9 @@ <span class="payment-block__mobile-label">Privacy: </span>{% if report.public %}Public{% else %}Private{% endif %} </td> <td> + {% if request.user.is_apply_staff %} + <a href="{% url "apply:projects:reports:edit" pk=report.pk %}">edit</a> + {% endif %} </td> </tr> {% empty %} diff --git a/opentech/apply/projects/tests/factories.py b/opentech/apply/projects/tests/factories.py index 994bfc33153dbe7f49527b8d25132b63479bba55..bdca5a9a5f48247178b4df1fb4eb8d0d96bf22a0 100644 --- a/opentech/apply/projects/tests/factories.py +++ b/opentech/apply/projects/tests/factories.py @@ -175,6 +175,7 @@ class ReportVersionFactory(factory.DjangoModelFactory): obj.report.draft = obj else: obj.report.current = obj + obj.report.submitted = obj.submitted obj.report.save() @@ -189,7 +190,7 @@ class ReportFactory(factory.DjangoModelFactory): past_due = factory.Trait( end_date=factory.LazyFunction(lambda: timezone.now() - relativedelta(days=1)) ) - submitted = factory.Trait( + is_submitted = factory.Trait( version=factory.RelatedFactory(ReportVersionFactory, 'report', draft=False, relate=True) ) is_draft = factory.Trait( diff --git a/opentech/apply/projects/tests/test_models.py b/opentech/apply/projects/tests/test_models.py index 91e2540fb654860f107101e55054634a8f88f888..15abe89865880e4d7c6ed08300a08e9132772d68 100644 --- a/opentech/apply/projects/tests/test_models.py +++ b/opentech/apply/projects/tests/test_models.py @@ -177,7 +177,6 @@ class TestPaymentRequestsQueryset(TestCase): class TestReportConfigCalculations(TestCase): - @property def today(self): return timezone.now().date() @@ -254,3 +253,47 @@ class TestReportConfigCalculations(TestCase): report = config.current_due_report() self.assertEqual(Report.objects.count(), 2) self.assertEqual(report.end_date, self.today + relativedelta(days=3)) + + def test_submitted_report_unaffected(self): + config = ReportConfigFactory() + report = ReportFactory(is_submitted=True, project=config.project, end_date=self.today + relativedelta(days=1)) + next_report = config.current_due_report() + self.assertNotEqual(report, next_report) + + +class TestReport(TestCase): + @property + def today(self): + return timezone.now().date() + + def from_today(self, days): + return self.today + relativedelta(days=days) + + def test_not_late_if_one_ahead(self): + report = ReportFactory(end_date=self.from_today(-3)) + ReportFactory(project=report.project) + self.assertFalse(report.is_very_late) + + def test_late_if_two_ahead(self): + report = ReportFactory(end_date=self.from_today(-3)) + ReportFactory(end_date=self.from_today(-2), project=report.project) + ReportFactory(project=report.project) + self.assertTrue(report.is_very_late) + + def test_not_late_if_two_ahead_but_one_in_future(self): + report = ReportFactory(end_date=self.from_today(-3)) + ReportFactory(project=report.project) + ReportFactory(end_date=self.from_today(2), project=report.project) + self.assertFalse(report.is_very_late) + + def test_start_date(self): + yesterday = self.from_today(-1) + ReportFactory(end_date=yesterday) + report = ReportFactory(end_date=self.from_today(1)) + self.assertEqual(report.start_date, self.today) + + def test_start_date_with_submitted(self): + yesterday = self.from_today(-1) + ReportFactory(end_date=yesterday) + report = ReportFactory(end_date=self.from_today(1), is_submitted=True) + self.assertEqual(report.start_date, self.today) diff --git a/opentech/apply/projects/tests/test_views.py b/opentech/apply/projects/tests/test_views.py index 3cebdb7cf264e55508ce8f401a9cfa76d962d2e7..ff727e84d8582d3e33ae93d17b665c9845a490fb 100644 --- a/opentech/apply/projects/tests/test_views.py +++ b/opentech/apply/projects/tests/test_views.py @@ -37,6 +37,7 @@ from .factories import ( PaymentRequestFactory, ProjectFactory, ReportFactory, + ReportVersionFactory, ) @@ -1284,7 +1285,7 @@ class TestStaffSubmitReport(BaseViewTestCase): self.assertIsNone(report.draft) def test_edit_submitted_report(self): - report = ReportFactory(submitted=True) + report = ReportFactory(is_submitted=True) self.assertEqual(report.versions.first(), report.current) response = self.post_page(report, {'content': 'Some text', 'public': True, 'save': ' Save'}) report.refresh_from_db() @@ -1293,10 +1294,28 @@ class TestStaffSubmitReport(BaseViewTestCase): self.assertEqual(report.versions.last(), report.draft) self.assertEqual(report.versions.first(), report.current) + def test_resubmit_submitted_report(self): + yesterday = timezone.now() - relativedelta(days=1) + version = ReportVersionFactory(submitted=yesterday) + report = version.report + report.current = version + report.submitted = version.submitted + report.save() + self.assertEqual(report.submitted, yesterday) + self.assertEqual(report.versions.first(), report.current) + response = self.post_page(report, {'content': 'Some text', 'public': True}) + report.refresh_from_db() + self.assertRedirects(response, self.absolute_url(report.project.get_absolute_url())) + self.assertEqual(report.versions.last().content, 'Some text') + self.assertEqual(report.versions.last(), report.current) + self.assertIsNone(report.draft) + self.assertEqual(report.submitted.date(), yesterday.date()) + self.assertEqual(report.current.submitted.date(), timezone.now().date()) + def test_can_submit_future_report(self): submitted_report = ReportFactory( end_date=timezone.now() + relativedelta(days=1), - submitted=True, + is_submitted=True, ) future_report = ReportFactory( end_date=timezone.now() + relativedelta(days=3), @@ -1306,12 +1325,22 @@ class TestStaffSubmitReport(BaseViewTestCase): self.assertEqual(response.status_code, 404) def test_change_privacy(self): - report = ReportFactory() + report = ReportFactory(public=True) response = self.post_page(report, {'content': 'Some text', 'public': False}) report.refresh_from_db() self.assertRedirects(response, self.absolute_url(report.project.get_absolute_url())) self.assertFalse(report.public) + def test_cant_change_privacy_submitted(self): + report = ReportFactory( + is_submitted=True, + public=True, + ) + response = self.post_page(report, {'content': 'Some text', 'public': False}) + report.refresh_from_db() + self.assertRedirects(response, self.absolute_url(report.project.get_absolute_url())) + self.assertTrue(report.public) + class TestApplicantSubmitReport(BaseViewTestCase): base_view_name = 'edit' @@ -1369,7 +1398,7 @@ class TestApplicantSubmitReport(BaseViewTestCase): self.assertIsNone(report.draft) def test_cant_edit_submitted_report(self): - report = ReportFactory(submitted=True, project__user=self.user) + report = ReportFactory(is_submitted=True, project__user=self.user) self.assertEqual(report.versions.first(), report.current) response = self.post_page(report, {'content': 'Some text', 'public': True, 'save': ' Save'}) self.assertEqual(response.status_code, 404) diff --git a/opentech/apply/projects/views/report.py b/opentech/apply/projects/views/report.py index af02271ac0280ffb71de1bb27ab2f979150c4439..f728f49a24f37c21dfbb9cca892dfe01c676c93e 100644 --- a/opentech/apply/projects/views/report.py +++ b/opentech/apply/projects/views/report.py @@ -73,7 +73,16 @@ class ReportUpdateView(ReportAccessMixin, UpdateView): def form_valid(self, form): response = super().form_valid(form) - if not form.instance.draft: + should_notify = True + if self.object.draft: + # It was a draft submission + should_notify = False + else: + if self.object.submitted != self.object.current.submitted: + # It was a staff edit - post submission + should_notify = False + + if should_notify: messenger( MESSAGES.SUBMIT_REPORT, request=self.request,