diff --git a/akvo/rest/views/report.py b/akvo/rest/views/report.py
index 87c309492f..d0c53f47f7 100644
--- a/akvo/rest/views/report.py
+++ b/akvo/rest/views/report.py
@@ -94,7 +94,7 @@ def program_reports(request, program_pk):
serializer = ReportSerializer(queryset.distinct(), many=True)
result = []
for r in serializer.data:
- r['url'] = r['url'].replace('{organisation}', str(organisation.id))
+ r['url'] = r['url'].replace('{organisation}', str(organisation.id)).replace('&download=true', '')
result.append(r)
return Response({'results': result})
diff --git a/akvo/rsr/management/commands/send_report_via_email.py b/akvo/rsr/management/commands/send_report_via_email.py
new file mode 100644
index 0000000000..a6ab537c33
--- /dev/null
+++ b/akvo/rsr/management/commands/send_report_via_email.py
@@ -0,0 +1,8 @@
+from django.core.management.base import BaseCommand
+from akvo.rsr.views.py_reports.email_report import run_job
+
+
+class Command(BaseCommand):
+
+ def handle(self, *args, **options):
+ run_job()
diff --git a/akvo/rsr/migrations/0220_emailreportjob.py b/akvo/rsr/migrations/0220_emailreportjob.py
new file mode 100644
index 0000000000..4675b1fb1f
--- /dev/null
+++ b/akvo/rsr/migrations/0220_emailreportjob.py
@@ -0,0 +1,26 @@
+# Generated by Django 3.2.10 on 2022-09-01 07:09
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rsr', '0219_iatiactivityvalidationjob_iatiorganisationvalidationjob'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='EmailReportJob',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created_at', models.DateTimeField(auto_now_add=True, db_index=True, null=True)),
+ ('started_at', models.DateTimeField(null=True)),
+ ('finished_at', models.DateTimeField(null=True)),
+ ('attempts', models.PositiveSmallIntegerField(default=0)),
+ ('report', models.CharField(max_length=100)),
+ ('payload', models.JSONField(default=dict)),
+ ('recipient', models.EmailField(max_length=254)),
+ ],
+ ),
+ ]
diff --git a/akvo/rsr/models/__init__.py b/akvo/rsr/models/__init__.py
index fd31c57924..bc16fc0d4a 100644
--- a/akvo/rsr/models/__init__.py
+++ b/akvo/rsr/models/__init__.py
@@ -25,6 +25,7 @@
from .crs_add import CrsAdd, CrsAddOtherFlag
from .category import Category
from .employment import Employment
+from .email_report_job import EmailReportJob
from .focus_area import FocusArea
from .fss import Fss, FssForecast
from .goal import Goal
@@ -103,6 +104,7 @@
'CrsAddOtherFlag',
'DefaultPeriod',
'Employment',
+ 'EmailReportJob',
'FocusArea',
'Fss',
'FssForecast',
diff --git a/akvo/rsr/models/email_report_job.py b/akvo/rsr/models/email_report_job.py
new file mode 100644
index 0000000000..cb1a8ea591
--- /dev/null
+++ b/akvo/rsr/models/email_report_job.py
@@ -0,0 +1,21 @@
+from django.db import models
+from django.utils.timezone import now
+
+
+class EmailReportJob(models.Model):
+ created_at = models.DateTimeField(null=True, auto_now_add=True, db_index=True, editable=False)
+ started_at = models.DateTimeField(null=True)
+ finished_at = models.DateTimeField(null=True)
+ attempts = models.PositiveSmallIntegerField(default=0)
+ report = models.CharField(max_length=100)
+ payload = models.JSONField(default=dict)
+ recipient = models.EmailField()
+
+ def mark_started(self):
+ self.started_at = now()
+ self.attempts = self.attempts + 1
+ self.save(update_fields=['started_at', 'attempts'])
+
+ def mark_finished(self):
+ self.finished_at = now()
+ self.save(update_fields=['finished_at'])
diff --git a/akvo/rsr/tests/views/py_reports/__init__.py b/akvo/rsr/tests/views/py_reports/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/akvo/rsr/tests/views/py_reports/test_send_report_via_email.py b/akvo/rsr/tests/views/py_reports/test_send_report_via_email.py
new file mode 100644
index 0000000000..4f415b7202
--- /dev/null
+++ b/akvo/rsr/tests/views/py_reports/test_send_report_via_email.py
@@ -0,0 +1,50 @@
+from django.core import management
+from django.core import mail
+from django.urls import reverse
+from parameterized import parameterized
+from akvo.rsr.tests.base import BaseTestCase
+from akvo.rsr.models import EmailReportJob, Country
+from akvo.rsr.views.py_reports import (
+ program_overview_pdf_report,
+ program_overview_excel_report,
+ program_period_labels_overview_pdf_report,
+ results_indicators_with_map_pdf_reports,
+ nuffic_country_level_map_report,
+)
+
+
+class SendReportViaEmailTestCase(BaseTestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.program = self.create_program('Test program')
+ self.user = self.create_user('test@akvo.org', 'password', is_admin=True)
+ Country.objects.get_or_create(iso_code='nl')
+ self.c.login(username=self.user.email, password='password')
+ mail.outbox = []
+
+ def test_add_job(self):
+ response = self.c.get(reverse('py-reports-program-overview', args=(self.program.id,)))
+ self.assertEqual(202, response.status_code)
+ self.assertEqual(1, EmailReportJob.objects.count())
+ job = EmailReportJob.objects.first()
+ self.assertEqual(program_overview_pdf_report.REPORT_NAME, job.report)
+
+ @parameterized.expand([
+ ('py-reports-program-overview', '', program_overview_pdf_report.REPORT_NAME),
+ ('py-reports-program-overview-table', '', program_overview_excel_report.REPORT_NAME),
+ ('py-reports-program-period-labels-overview', '', program_period_labels_overview_pdf_report.REPORT_NAME),
+ ('py-reports-organisation-projects-results-indicators-map-overview', 'country=nl', results_indicators_with_map_pdf_reports.ORG_PROJECTS_REPORT_NAME),
+ ('py-reports-nuffic-country-level-report', 'country=nl', nuffic_country_level_map_report.REPORT_NAME),
+ ])
+ def test_send_report_via_email(self, url_name, query_params, report_name):
+ self.c.get(f"{reverse(url_name, args=(self.program.id,))}?{query_params}")
+ job = EmailReportJob.objects.first()
+ self.assertEqual(report_name, job.report)
+ self.assertEqual(1, EmailReportJob.objects.filter(finished_at__isnull=True).count())
+
+ management.call_command('send_report_via_email')
+
+ self.assertEqual(0, EmailReportJob.objects.filter(finished_at__isnull=True).count())
+ msg = mail.outbox[0]
+ self.assertEqual([self.user.email], msg.to)
diff --git a/akvo/rsr/views/py_reports/__init__.py b/akvo/rsr/views/py_reports/__init__.py
index 5075dab5ae..bc69c67977 100644
--- a/akvo/rsr/views/py_reports/__init__.py
+++ b/akvo/rsr/views/py_reports/__init__.py
@@ -19,7 +19,7 @@
from .results_indicators_with_map_pdf_reports import (
render_project_results_indicators_overview,
render_project_results_indicators_map_overview,
- render_organisation_projects_results_indicators_map_overview
+ add_org_projects_email_report_job,
)
from .kickstart_word_report import render_report as render_kickstart_report
from .eutf_narrative_word_report import render_report as render_eutf_narrative_word_report
@@ -33,11 +33,11 @@
render_report as render_results_indicators_excel_report
from .organisation_projects_overview_report import \
render_report as render_org_projects_overview_report
-from .program_overview_excel_report import render_report as render_program_overview_excel_report
-from .program_overview_pdf_report import render_report as render_program_overview_pdf_report
-from .program_period_labels_overview_pdf_report import render_program_period_lables_overview
+from .program_overview_excel_report import add_email_report_job as add_program_overview_excel_report_email_job
+from .program_overview_pdf_report import add_email_report_job as add_program_overview_pdf_report_email_job
+from .program_period_labels_overview_pdf_report import add_email_report_job as add_program_period_labels_overview
from .nuffic_country_level_map_report import \
- render_country_level_report as render_nuffic_country_level_report
+ add_email_report_job as add_nuffic_country_level_report_job
from .project_overview_pdf_report import render_report as render_project_overview_pdf_report
@@ -57,7 +57,6 @@ def check(request):
__all__ = [
'check',
'render_project_results_indicators_map_overview',
- 'render_organisation_projects_results_indicators_map_overview',
'render_project_results_indicators_excel_report',
'render_project_updates_excel_report',
'render_kickstart_report',
@@ -67,9 +66,10 @@ def check(request):
'render_eutf_project_results_table_excel_report',
'render_results_indicators_excel_report',
'render_org_projects_overview_report',
- 'render_program_overview_excel_report',
- 'render_program_overview_pdf_report',
- 'render_program_period_lables_overview',
- 'render_nuffic_country_level_report',
'render_project_overview_pdf_report',
+ 'add_org_projects_email_report_job',
+ 'add_program_overview_pdf_report_email_job',
+ 'add_program_overview_excel_report_email_job',
+ 'add_program_period_labels_overview',
+ 'add_nuffic_country_level_report_job',
]
diff --git a/akvo/rsr/views/py_reports/email_report.py b/akvo/rsr/views/py_reports/email_report.py
new file mode 100644
index 0000000000..0bf26cb701
--- /dev/null
+++ b/akvo/rsr/views/py_reports/email_report.py
@@ -0,0 +1,53 @@
+import logging
+
+from datetime import timedelta
+from django.db.models import Q
+from django.utils.timezone import now
+from akvo.rsr.models import EmailReportJob
+
+from . import (
+ program_overview_pdf_report,
+ program_overview_excel_report,
+ program_period_labels_overview_pdf_report,
+ results_indicators_with_map_pdf_reports,
+ nuffic_country_level_map_report,
+)
+
+TIMEOUT = timedelta(minutes=30)
+MAX_ATTEMPTS = 3
+HANDLER = {
+ program_overview_pdf_report.REPORT_NAME: program_overview_excel_report.handle_email_report,
+ program_overview_excel_report.REPORT_NAME: program_overview_excel_report.handle_email_report,
+ program_period_labels_overview_pdf_report.REPORT_NAME: program_period_labels_overview_pdf_report.handle_email_report,
+ results_indicators_with_map_pdf_reports.ORG_PROJECTS_REPORT_NAME: results_indicators_with_map_pdf_reports.handle_org_projects_email_report,
+ nuffic_country_level_map_report.REPORT_NAME: nuffic_country_level_map_report.handle_email_report,
+}
+
+logger = logging.getLogger(__name__)
+
+
+def run_job():
+ pending_jobs = _get_pending_jobs()
+ if not pending_jobs.exists():
+ return
+ job = pending_jobs.first()
+ job.mark_started()
+ try:
+ handler = _get_report_handler(job.report)
+ if handler:
+ handler(job.payload, job.recipient)
+ job.mark_finished()
+ except Exception:
+ logger.exception(f'Failed to genereate report {job.report} for {job.recipient}')
+
+
+def _get_pending_jobs():
+ started_timeout = now() - TIMEOUT
+ return EmailReportJob.objects\
+ .order_by('created_at')\
+ .filter(finished_at__isnull=True)\
+ .exclude(Q(attempts__gte=MAX_ATTEMPTS) | Q(started_at__gte=started_timeout))
+
+
+def _get_report_handler(report):
+ return HANDLER[report] if report in HANDLER else None
diff --git a/akvo/rsr/views/py_reports/nuffic_country_level_map_report.py b/akvo/rsr/views/py_reports/nuffic_country_level_map_report.py
index ff54f8ca05..9ac3b113d7 100644
--- a/akvo/rsr/views/py_reports/nuffic_country_level_map_report.py
+++ b/akvo/rsr/views/py_reports/nuffic_country_level_map_report.py
@@ -19,6 +19,68 @@
from . import utils
+REPORT_NAME = 'nuffic_country_level_map_report'
+
+
+@login_required
+def add_email_report_job(request, program_id):
+ program = get_object_or_404(Project, pk=program_id)
+ country = request.GET.get('country', '').strip()
+ if not country:
+ return HttpResponseBadRequest('Please provide the country code!')
+ show_comment = request.GET.get('comment', '').strip()
+ start_date = request.GET.get('period_start', '').strip()
+ end_date = request.GET.get('period_end', '').strip()
+ return utils.add_email_report_job(
+ report=REPORT_NAME,
+ payload={
+ 'program_id': program.id,
+ 'country': country,
+ 'show_comment': show_comment,
+ 'start_date': start_date,
+ 'end_date': end_date,
+ },
+ recipient=request.user.email
+ )
+
+
+def handle_email_report(params, recipient):
+ country = params.get('country')
+ show_comment = True if params.get('comment', '') == 'true' else False
+ start_date = utils.parse_date(params.get('period_start', ''), datetime(1900, 1, 1))
+ end_date = utils.parse_date(params.get('period_end', ''), datetime(2999, 12, 31))
+
+ country = Country.objects.get(iso_code=country)
+ program = Project.objects.get(pk=params['program_id'])
+
+ project_ids = program.descendants()\
+ .exclude(pk=program.id)\
+ .exclude(Q(title__icontains='test') | Q(subtitle__icontains='test'))\
+ .values_list('id', flat=True)
+ projects_in_country = Project.objects\
+ .filter(id__in=project_ids, locations__country=country)\
+ .distinct()
+ coordinates = [
+ Coordinate(location.latitude, location.longitude)
+ for project in projects_in_country
+ for location in project.locations.all()
+ if location.country == country
+ ]
+
+ now = datetime.today()
+
+ html = render_to_string('reports/nuffic-country-level-report.html', context={
+ 'title': 'Country level report for projects in {}'.format(country.name),
+ 'staticmap': get_staticmap_url(coordinates, Size(900, 600), zoom=11),
+ 'projects': build_view_objects(projects_in_country, start_date, end_date),
+ 'show_comment': show_comment,
+ 'today': now.strftime('%d-%b-%Y'),
+ })
+
+ filename = '{}-{}-country-report.pdf'.format(now.strftime('%Y%b%d'), country.iso_code)
+
+ return utils.send_pdf_report(html, recipient, filename)
+
@login_required
@with_download_indicator
diff --git a/akvo/rsr/views/py_reports/program_overview_excel_report.py b/akvo/rsr/views/py_reports/program_overview_excel_report.py
index 428b1860b8..a45a58255b 100644
--- a/akvo/rsr/views/py_reports/program_overview_excel_report.py
+++ b/akvo/rsr/views/py_reports/program_overview_excel_report.py
@@ -195,6 +195,32 @@ def get_dynamic_column_start(aggregate_targets):
return AGGREGATED_TARGET_VALUE_COLUMN + 1 if aggregate_targets else AGGREGATED_TARGET_VALUE_COLUMN
+REPORT_NAME = 'program_overview_excel_report'
+
+
+@login_required
+def add_email_report_job(request, program_id):
+ program = get_object_or_404(Project, pk=program_id)
+ return utils.add_email_report_job(
+ report=REPORT_NAME,
+ payload={
+ 'program_id': program.id,
+ 'period_start': request.GET.get('period_start', '').strip(),
+ 'period_end': request.GET.get('period_end', '').strip(),
+ },
+ recipient=request.user.email
+ )
+
+
+def handle_email_report(params, recipient):
+ program = Project.objects.prefetch_related('results').get(pk=params['program_id'])
+ start_date = utils.parse_date(params.get('period_start', ''))
+ end_date = utils.parse_date(params.get('period_end', ''))
+ wb = generate_workbok(program, start_date, end_date)
+ filename = '{}-{}-program-overview-report.xlsx'.format(datetime.today().strftime('%Y%b%d'), program.id)
+ utils.send_excel_report(wb, recipient, filename)
+
+
@login_required
@with_download_indicator
def render_report(request, program_id):
diff --git a/akvo/rsr/views/py_reports/program_overview_pdf_report.py b/akvo/rsr/views/py_reports/program_overview_pdf_report.py
index 4ddd238cbc..90406557c9 100644
--- a/akvo/rsr/views/py_reports/program_overview_pdf_report.py
+++ b/akvo/rsr/views/py_reports/program_overview_pdf_report.py
@@ -21,6 +21,43 @@
from . import utils
+REPORT_NAME = 'program_overview_pdf_report'
+
+
+@login_required
+def add_email_report_job(request, program_id):
+ program = get_object_or_404(Project, pk=program_id)
+ return utils.add_email_report_job(
+ report=REPORT_NAME,
+ payload={
+ 'program_id': program.id,
+ 'period_start': request.GET.get('period_start', '').strip(),
+ 'period_end': request.GET.get('period_end', '').strip(),
+ },
+ recipient=request.user.email
+ )
+
+
+def handle_email_report(params, recipient):
+ now = datetime.today()
+ program = Project.objects.prefetch_related('results').get(pk=params['program_id'])
+ start_date = utils.parse_date(params.get('period_start', ''))
+ end_date = utils.parse_date(params.get('period_end', ''))
+ program_view = build_view_object(program, start_date or datetime(1900, 1, 1), end_date or (datetime.today() + relativedelta(years=10)))
+ coordinates = [
+ Coordinate(loc.latitude, loc.longitude)
+ for loc in program_view.locations
+ if loc and loc.latitude and loc.longitude
+ ]
+ html = render_to_string('reports/program-overview.html', context={
+ 'program': program_view,
+ 'staticmap': get_staticmap_url(coordinates, Size(900, 600)),
+ 'start_date': start_date,
+ 'end_date': end_date,
+ })
+ filename = '{}-program-{}-overview.pdf'.format(now.strftime('%Y%b%d'), program.id)
+ utils.send_pdf_report(html, recipient, filename)
+
class ProgramProxy(utils.ProjectProxy):
def __init__(self, project, results={}):
diff --git a/akvo/rsr/views/py_reports/program_period_labels_overview_pdf_report.py b/akvo/rsr/views/py_reports/program_period_labels_overview_pdf_report.py
index 23891eaaed..40fce96eb7 100644
--- a/akvo/rsr/views/py_reports/program_period_labels_overview_pdf_report.py
+++ b/akvo/rsr/views/py_reports/program_period_labels_overview_pdf_report.py
@@ -22,6 +22,36 @@
from . import utils
+REPORT_NAME = 'program_period_labels_overview_pdf_report'
+
+
+@login_required
+def add_email_report_job(request, program_id):
+ program = get_object_or_404(Project, pk=program_id)
+ return utils.add_email_report_job(
+ report=REPORT_NAME,
+ payload={
+ 'program_id': program.id
+ },
+ recipient=request.user.email
+ )
+
+
+def handle_email_report(params, recipient):
+ program = Project.objects.prefetch_related('locations', 'partners', 'related_projects').get(pk=params['program_id'])
+ program_view = build_view_object(program)
+ now = datetime.today()
+ html = render_to_string(
+ 'reports/program-period-labels-overview.html',
+ context={
+ 'project': program_view,
+ 'today': now.strftime('%d-%b-%Y'),
+ }
+ )
+ filename = '{}-{}-program-labeled-period-overview.pdf'.format(now.strftime('%Y%b%d'), program.id)
+
+ return utils.send_pdf_report(html, recipient, filename)
+
@login_required
@with_download_indicator
diff --git a/akvo/rsr/views/py_reports/results_indicators_with_map_pdf_reports.py b/akvo/rsr/views/py_reports/results_indicators_with_map_pdf_reports.py
index 77e5ec5ac2..e51b489c05 100644
--- a/akvo/rsr/views/py_reports/results_indicators_with_map_pdf_reports.py
+++ b/akvo/rsr/views/py_reports/results_indicators_with_map_pdf_reports.py
@@ -19,6 +19,71 @@
from . import utils
+ORG_PROJECTS_REPORT_NAME = 'organisation_projects_results_indicators_map_overview'
+
+
+@login_required
+def add_org_projects_email_report_job(request, program_id):
+ program = get_object_or_404(Project, pk=program_id)
+ country = request.GET.get('country', '').strip()
+ if not country:
+ return HttpResponseBadRequest('Please provide the country code!')
+ show_comment = request.GET.get('comment', '').strip()
+ start_date = request.GET.get('period_start', '').strip()
+ end_date = request.GET.get('period_end', '').strip()
+ return utils.add_email_report_job(
+ report=ORG_PROJECTS_REPORT_NAME,
+ payload={
+ 'program_id': program.id,
+ 'country': country,
+ 'show_comment': show_comment,
+ 'start_date': start_date,
+ 'end_date': end_date,
+ },
+ recipient=request.user.email
+ )
+
+
+def handle_org_projects_email_report(params, recipient):
+ country = params.get('country')
+ show_comment = True if params.get('comment', '') == 'true' else False
+ start_date = utils.parse_date(params.get('period_start', ''), datetime(1900, 1, 1))
+ end_date = utils.parse_date(params.get('period_end', ''), datetime(2999, 12, 31))
+
+ country = Country.objects.get(iso_code=country)
+ project_hierarchy = ProjectHierarchy.objects.get(root_project=params['program_id'])
+ organisation = Organisation.objects.prefetch_related(
+ 'projects',
+ 'projects__results',
+ 'projects__results__indicators',
+ 'projects__results__indicators__periods'
+ ).get(pk=project_hierarchy.organisation.id)
+ projects = organisation.all_projects().filter(primary_location__country=country)
+ coordinates = [
+ Coordinate(p.primary_location.latitude, p.primary_location.longitude)
+ for p
+ in projects
+ if p.primary_location
+ ]
+
+ now = datetime.today()
+ html = render_to_string(
+ 'reports/organisation-projects-results-indicators-map-overview.html',
+ context={
+ 'title': 'Results and indicators overview for projects in {}'.format(country.name),
+ 'staticmap': get_staticmap_url(coordinates, Size(900, 600)),
+ 'projects': [build_view_object(p, start_date, end_date) for p in projects],
+ 'show_comment': show_comment,
+ 'today': now.strftime('%d-%b-%Y'),
+ }
+ )
+
+ filename = '{}-{}-{}-projects-results-indicators-overview.pdf'.format(
+ now.strftime('%Y%b%d'), organisation.id, country.iso_code
+ )
+
+ return utils.send_pdf_report(html, recipient, filename)
+
@login_required
@with_download_indicator
diff --git a/akvo/rsr/views/py_reports/utils.py b/akvo/rsr/views/py_reports/utils.py
index 381c2222ba..e4fe49618b 100644
--- a/akvo/rsr/views/py_reports/utils.py
+++ b/akvo/rsr/views/py_reports/utils.py
@@ -16,10 +16,50 @@
from functools import cached_property
from weasyprint import HTML
from weasyprint.fonts import FontConfiguration
-from akvo.rsr.models import Partnership
+from akvo.rsr.models import Partnership, EmailReportJob
from akvo.rsr.project_overview import DisaggregationTarget, IndicatorType
from akvo.rsr.models.result.utils import QUANTITATIVE, QUALITATIVE, PERCENTAGE_MEASURE, calculate_percentage
-from akvo.utils import ObjectReaderProxy, ensure_decimal
+from akvo.utils import ObjectReaderProxy, ensure_decimal, rsr_send_mail
+
+
+def add_email_report_job(report, payload, recipient):
+ EmailReportJob.objects.create(
+ report=report,
+ payload=payload,
+ recipient=recipient
+ )
+ return HttpResponse('Your report is being prepared. It will be sent to your email in a few moments.', status=202)
+
+
+def send_pdf_report(html, recipient, filename='reports.pdf'):
+ font_config = FontConfiguration()
+ pdf = HTML(string=html).write_pdf(font_config=font_config)
+ attachments = [{'filename': filename, 'content': pdf, 'mimetype': 'application/pdf'}]
+ rsr_send_mail(
+ [recipient],
+ subject='reports/email/subject.txt',
+ message='reports/email/message.txt',
+ html_message='reports/email/message.html',
+ attachments=attachments
+ )
+
+
+def send_excel_report(workbook, recipient, filename='report.xlsx'):
+ stream = io.BytesIO()
+ workbook.save(stream)
+ stream.seek(0)
+ attachments = [{
+ 'filename': filename,
+ 'content': stream.read(),
+ 'mimetype': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+ }]
+ rsr_send_mail(
+ [recipient],
+ subject='reports/email/subject.txt',
+ message='reports/email/message.txt',
+ html_message='reports/email/message.html',
+ attachments=attachments
+ )
def make_pdf_response(html, filename='reports.pdf'):
@@ -385,7 +425,7 @@ def disaggregations(self):
disaggregations[category][type]['numerator'] += (d['numerator'] or 0)
disaggregations[category][type]['denominator'] += (d['denominator'] or 0)
if self.is_percentage:
- for category, types in disaggregations.item():
+ for category, types in disaggregations.items():
for type in types.keys():
disaggregations[category][type]['value'] = calculate_percentage(
disaggregations[category][type]['numerator'],
diff --git a/akvo/settings/90-finish.conf b/akvo/settings/90-finish.conf
index 8381bb9d4e..e7ff0c2e06 100644
--- a/akvo/settings/90-finish.conf
+++ b/akvo/settings/90-finish.conf
@@ -103,6 +103,7 @@ LOGGING = {
CRONTAB_COMMAND_SUFFIX = '> /proc/1/fd/1 2>/proc/1/fd/2'
CRONJOBS = [
('* * * * *', 'django.core.management.call_command', ['iati_export']),
+ ('* * * * *', 'django.core.management.call_command', ['send_report_via_email']),
('10 2 * * *', 'django.core.management.call_command', ['a4a_optimy_import']),
('*/5 * * * *', 'django.core.management.call_command', ['perform_iati_checks']),
('0 0 * * *', 'django.core.management.call_command', ['cleanup_untitled_and_unpublished_projects']),
diff --git a/akvo/templates/reports/email/message.html b/akvo/templates/reports/email/message.html
new file mode 100644
index 0000000000..fa6466479e
--- /dev/null
+++ b/akvo/templates/reports/email/message.html
@@ -0,0 +1,107 @@
+
+
+
+
+
+ Your Akvo RSR report is ready
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ You’re all set! Your report is attached.
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+
+
+
+
diff --git a/akvo/templates/reports/email/message.txt b/akvo/templates/reports/email/message.txt
new file mode 100644
index 0000000000..148cf88215
--- /dev/null
+++ b/akvo/templates/reports/email/message.txt
@@ -0,0 +1 @@
+You’re all set! Your report is attached.
diff --git a/akvo/templates/reports/email/subject.txt b/akvo/templates/reports/email/subject.txt
new file mode 100644
index 0000000000..8f4b7e8c05
--- /dev/null
+++ b/akvo/templates/reports/email/subject.txt
@@ -0,0 +1 @@
+Your Akvo RSR report is ready
diff --git a/akvo/urls.py b/akvo/urls.py
index 3be1817428..61d562893b 100644
--- a/akvo/urls.py
+++ b/akvo/urls.py
@@ -153,7 +153,7 @@
name='py-reports-project-results-indicators-map-overview'),
url(r'^py-reports/program/(?P\d+)/projects-results-indicators-map-overview/$',
- py_reports.render_organisation_projects_results_indicators_map_overview,
+ py_reports.add_org_projects_email_report_job,
name='py-reports-organisation-projects-results-indicators-map-overview'),
url(r'^py-reports/project/(?P\d+)/results-indicators-table/$',
@@ -205,18 +205,19 @@
name='py-reports-organisation-projects-overview'),
url(r'^py-reports/program/(?P\d+)/program-overview-table/$',
- py_reports.render_program_overview_excel_report,
+ py_reports.add_program_overview_excel_report_email_job,
name='py-reports-program-overview-table'),
url(r'^py-reports/program/(?P\d+)/program-overview/$',
- py_reports.render_program_overview_pdf_report,
+ py_reports.add_program_overview_pdf_report_email_job,
name='py-reports-program-overview'),
url(r'^py-reports/program/(?P\d+)/program-labeled-periods-overview/$',
- py_reports.render_program_period_lables_overview,
- name='py-reports-program-overview'),
+ py_reports.add_program_period_labels_overview,
+ name='py-reports-program-period-labels-overview'),
+
url(r'^py-reports/program/(?P\d+)/nuffic-country-level-report/$',
- py_reports.render_nuffic_country_level_report,
+ py_reports.add_nuffic_country_level_report_job,
name='py-reports-nuffic-country-level-report'),
# IATI file
diff --git a/scripts/deployment/pip/requirements/3_dev.txt b/scripts/deployment/pip/requirements/3_dev.txt
index 92d79fec98..98d297af26 100644
--- a/scripts/deployment/pip/requirements/3_dev.txt
+++ b/scripts/deployment/pip/requirements/3_dev.txt
@@ -632,6 +632,10 @@ packaging==20.4 \
# via
# bleach
# matplotlib
+parameterized==0.8.1 \
+ --hash=sha256:41bbff37d6186430f77f900d777e5bb6a24928a1c46fb1de692f8b52b8833b5c \
+ --hash=sha256:9cbb0b69a03e8695d68b3399a8a5825200976536fe1cb79db60ed6a4c8c9efe9
+ # via -r scripts/deployment/pip/requirements/dev.in
parso==0.7.1 \
--hash=sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea \
--hash=sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9
diff --git a/scripts/deployment/pip/requirements/dev.in b/scripts/deployment/pip/requirements/dev.in
index 162fa22d83..e835fcd2e2 100644
--- a/scripts/deployment/pip/requirements/dev.in
+++ b/scripts/deployment/pip/requirements/dev.in
@@ -7,6 +7,9 @@ flake8
# for parallel tests
tblib
+# for parameterized tests
+parameterized
+
# Debug perf
django-debug-toolbar