From 7c0b5cd71881c7ac8014a9dc7093907b278c2732 Mon Sep 17 00:00:00 2001 From: Todd Dembrey <todd.dembrey@torchbox.com> Date: Mon, 4 Nov 2019 09:26:53 +0000 Subject: [PATCH] Feature/gh 1624 view submitted reports (#1640) * Add detail view for submitted reports * Use the private storage url for the report files * Redirect old private report files to the report detail page --- opentech/apply/projects/models.py | 6 ++ .../includes/reports.html | 1 + .../application_projects/report_detail.html | 40 +++++++++++ .../application_projects/report_form.html | 4 +- opentech/apply/projects/tests/test_views.py | 67 +++++++++++++++++++ opentech/apply/projects/urls.py | 2 + opentech/apply/projects/views/report.py | 34 ++++++++-- 7 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 opentech/apply/projects/templates/application_projects/report_detail.html diff --git a/opentech/apply/projects/models.py b/opentech/apply/projects/models.py index 36388f874..b99e4a0e4 100644 --- a/opentech/apply/projects/models.py +++ b/opentech/apply/projects/models.py @@ -641,6 +641,9 @@ class Report(models.Model): class Meta: ordering = ('-end_date',) + def get_absolute_url(self): + return reverse('apply:projects:reports:detail', kwargs={'pk': self.pk}) + @property def past_due(self): return timezone.now().date() > self.end_date @@ -694,3 +697,6 @@ class ReportPrivateFiles(models.Model): def __str__(self): return self.filename + + def get_absolute_url(self): + return reverse('apply:projects:reports:document', kwargs={'pk': self.report.report_id, 'file_pk': self.pk}) diff --git a/opentech/apply/projects/templates/application_projects/includes/reports.html b/opentech/apply/projects/templates/application_projects/includes/reports.html index 81d83f5c2..ae2af0600 100644 --- a/opentech/apply/projects/templates/application_projects/includes/reports.html +++ b/opentech/apply/projects/templates/application_projects/includes/reports.html @@ -36,6 +36,7 @@ {% if request.user.is_apply_staff %} <a href="{% url "apply:projects:reports:edit" pk=report.pk %}">edit</a> {% endif %} + <a href="{% url "apply:projects:reports:detail" pk=report.pk %}">view</a> </td> </tr> {% empty %} diff --git a/opentech/apply/projects/templates/application_projects/report_detail.html b/opentech/apply/projects/templates/application_projects/report_detail.html new file mode 100644 index 000000000..249c07e8c --- /dev/null +++ b/opentech/apply/projects/templates/application_projects/report_detail.html @@ -0,0 +1,40 @@ +{% extends "base-apply.html" %} +{% load static bleach_tags %} + +{% block title %}Report | {{ object.project.title }}{% endblock %} +{% block content %} +<div class="admin-bar"> + <div class="admin-bar__inner"> + <a class="admin-bar__back-link" href="{{ object.project.get_absolute_url }}"> + Project + </a> + <h2 class="heading heading--no-margin">Report for the period {{ report.start_date }} to {{ report.end_date }}</h2> + <h5 class="heading heading--no-margin">{{ object.project.title }}</h5> + </div> +</div> + +<div class="wrapper wrapper--light-grey-bg wrapper--form wrapper--sidebar"> + <div class="wrapper--sidebar--inner"> + <h3> + {% if object.public %} + Public + {% else %} + Private + {% endif %} + </h3> + {{ object.current.content|bleach|safe }} + {% for file in object.current.files.all %} + {% if forloop.first %} + <h4>Files</h4> + <ul> + {% endif %} + + <li><a href="{{ file.get_absolute_url }}">{{ file.filename }}</a></li> + + {% if forloop.last %} + </ul> + {% endif %} + {% endfor %} + </div> +</div> +{% endblock %} diff --git a/opentech/apply/projects/templates/application_projects/report_form.html b/opentech/apply/projects/templates/application_projects/report_form.html index 72add7fbd..3296a547a 100644 --- a/opentech/apply/projects/templates/application_projects/report_form.html +++ b/opentech/apply/projects/templates/application_projects/report_form.html @@ -1,14 +1,14 @@ {% extends "base-apply.html" %} {% load static %} -{% block title %}Report | {{ object.project.title }}{% endblock %} +{% block title %}Edit Report | {{ object.project.title }}{% endblock %} {% block content %} <div class="admin-bar"> <div class="admin-bar__inner"> <a class="admin-bar__back-link" href="{{ object.project.get_absolute_url }}"> Project </a> - <h2 class="heading heading--no-margin">Report for the period ending {{ report.end_date }}</h2> + <h2 class="heading heading--no-margin">Report for the period {{ report.start_date }} to {{ report.end_date }}</h2> <h5 class="heading heading--no-margin">{{ object.project.title }}</h5> </div> </div> diff --git a/opentech/apply/projects/tests/test_views.py b/opentech/apply/projects/tests/test_views.py index ff727e84d..c7e09c139 100644 --- a/opentech/apply/projects/tests/test_views.py +++ b/opentech/apply/projects/tests/test_views.py @@ -1407,3 +1407,70 @@ class TestApplicantSubmitReport(BaseViewTestCase): report = ReportFactory() response = self.post_page(report, {'content': 'Some text', 'public': True}) self.assertEqual(response.status_code, 403) + + +class TestStaffReportDetail(BaseViewTestCase): + base_view_name = 'detail' + url_name = 'funds:projects:reports:{}' + user_factory = StaffFactory + + def get_kwargs(self, instance): + return { + 'pk': instance.pk, + } + + def test_can_access_submitted_report(self): + report = ReportFactory(is_submitted=True) + response = self.get_page(report) + self.assertEqual(response.status_code, 200) + + def test_cant_access_draft_report(self): + report = ReportFactory(is_draft=True) + response = self.get_page(report) + self.assertEqual(response.status_code, 404) + + def test_cant_access_future_report(self): + report = ReportFactory(end_date=timezone.now() + relativedelta(days=1)) + response = self.get_page(report) + self.assertEqual(response.status_code, 404) + + +class TestApplicantReportDetail(BaseViewTestCase): + base_view_name = 'detail' + url_name = 'funds:projects:reports:{}' + user_factory = StaffFactory + + def get_kwargs(self, instance): + return { + 'pk': instance.pk, + } + + def test_can_access_own_submitted_report(self): + report = ReportFactory(is_submitted=True, project__user=self.user) + response = self.get_page(report) + self.assertEqual(response.status_code, 200) + + def test_cant_access_own_draft_report(self): + report = ReportFactory(is_draft=True, project__user=self.user) + response = self.get_page(report) + self.assertEqual(response.status_code, 404) + + def test_cant_access_own_future_report(self): + report = ReportFactory(end_date=timezone.now() + relativedelta(days=1), project__user=self.user) + response = self.get_page(report) + self.assertEqual(response.status_code, 404) + + def test_cant_access_other_submitted_report(self): + report = ReportFactory(is_submitted=True) + response = self.get_page(report) + self.assertEqual(response.status_code, 200) + + def test_cant_access_other_draft_report(self): + report = ReportFactory(is_draft=True) + response = self.get_page(report) + self.assertEqual(response.status_code, 404) + + def test_cant_access_other_future_report(self): + report = ReportFactory(end_date=timezone.now() + relativedelta(days=1)) + response = self.get_page(report) + self.assertEqual(response.status_code, 404) diff --git a/opentech/apply/projects/urls.py b/opentech/apply/projects/urls.py index eb512ba0e..91d070649 100644 --- a/opentech/apply/projects/urls.py +++ b/opentech/apply/projects/urls.py @@ -14,6 +14,7 @@ from .views import ( ProjectListView, ProjectOverviewView, ProjectPrivateMediaView, + ReportDetailView, ReportPrivateMedia, ReportUpdateView, ) @@ -43,6 +44,7 @@ urlpatterns = [ ], 'payments'))), path('reports/', include(([ path('<int:pk>/', include([ + path('', ReportDetailView.as_view(), name='detail'), path('edit/', ReportUpdateView.as_view(), name='edit'), path('documents/<int:file_pk>/', ReportPrivateMedia.as_view(), name="document"), ])), diff --git a/opentech/apply/projects/views/report.py b/opentech/apply/projects/views/report.py index f728f49a2..70c2603ca 100644 --- a/opentech/apply/projects/views/report.py +++ b/opentech/apply/projects/views/report.py @@ -1,16 +1,18 @@ from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import UserPassesTestMixin +from django.core.exceptions import PermissionDenied from django.http import Http404 +from django.shortcuts import get_object_or_404, redirect from django.utils.decorators import method_decorator -from django.core.exceptions import PermissionDenied from django.views.generic import ( - UpdateView + DetailView, + UpdateView, ) from opentech.apply.activity.messaging import MESSAGES, messenger from opentech.apply.utils.storage import PrivateMediaView -from ..models import Report, ReportConfig +from ..models import Report, ReportConfig, ReportPrivateFiles from ..forms import ReportEditForm @@ -34,6 +36,17 @@ class ReportAccessMixin: return super().dispatch(request, *args, **kwargs) +@method_decorator(login_required, name='dispatch') +class ReportDetailView(ReportAccessMixin, DetailView): + model = Report + + def dispatch(self, *args, **kwargs): + report = self.get_object() + if not report.current: + raise Http404 + return super().dispatch(*args, **kwargs) + + @method_decorator(login_required, name='dispatch') class ReportUpdateView(ReportAccessMixin, UpdateView): form_class = ReportEditForm @@ -101,13 +114,22 @@ class ReportPrivateMedia(UserPassesTestMixin, PrivateMediaView): def dispatch(self, *args, **kwargs): report_pk = self.kwargs['pk'] self.report = get_object_or_404(Report, pk=report_pk) + file_pk = kwargs.get('file_pk') + self.document = get_object_or_404( + ReportPrivateFiles.objects, + report__report=self.report, + pk=file_pk + ) + + if not hasattr(self.document.report, 'live_for_report'): + # this is not a document in the live report + # send the user to the report page to see latest version + return redirect(self.report.get_absolute_url()) return super().dispatch(*args, **kwargs) def get_media(self, *args, **kwargs): - file_pk = kwargs.get('file_pk') - document = get_object_or_404(self.report.files, pk=file_pk) - return document.document + return self.document.document def test_func(self): if self.request.user.is_apply_staff: -- GitLab