diff --git a/opentech/apply/projects/models.py b/opentech/apply/projects/models.py index 36388f8742c78a3fd296949dfca5dff5e0be5b44..b99e4a0e41b60add16948c50bbd9308889174b96 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 81d83f5c25fcffb327ef0358c95d69e23242d542..ae2af06007938b28b828a24b2273d1b8aaf2def1 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 0000000000000000000000000000000000000000..249c07e8cf668de7372e7dda07039cfcb057daf4 --- /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 72add7fbde5d671ee6d6e25fc96d420d040cb283..3296a547ab57f663cd8b0f1a13c84f10f3fd1f95 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 ff727e84d8582d3e33ae93d17b665c9845a490fb..c7e09c139ad4882b91b2956a0a316db8c94e76e2 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 eb512ba0e93d0a600ab57efda1300217930b2885..91d07064912a1cf56181221a03e1563292de055a 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 f728f49a24f37c21dfbb9cca892dfe01c676c93e..70c2603ca579608d91f52f8df08b0f7582dee4ef 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: