From f95ba18da99ec68972a7e100872ad4afd71452e2 Mon Sep 17 00:00:00 2001 From: Matt Riley Date: Fri, 22 Nov 2024 16:22:32 -0500 Subject: [PATCH 01/10] Added EnterpriseSMSReport --- corehq/apps/enterprise/enterprise.py | 88 ++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/corehq/apps/enterprise/enterprise.py b/corehq/apps/enterprise/enterprise.py index 352119c6ea00..b38695e61086 100644 --- a/corehq/apps/enterprise/enterprise.py +++ b/corehq/apps/enterprise/enterprise.py @@ -1,4 +1,6 @@ import re +from django.db.models import OuterRef, Subquery, Count +from django.db.models.functions import Coalesce from datetime import datetime, timedelta from django.utils.translation import gettext as _ @@ -20,6 +22,7 @@ from corehq.apps.es import forms as form_es from corehq.apps.es.users import UserES from corehq.apps.export.dbaccessors import ODataExportFetcher +from corehq.apps.sms.models import SMS, OUTGOING, INCOMING from corehq.apps.users.dbaccessors import ( get_all_user_rows, get_mobile_user_count, @@ -34,6 +37,7 @@ class EnterpriseReport: MOBILE_USERS = 'mobile_users' FORM_SUBMISSIONS = 'form_submissions' ODATA_FEEDS = 'odata_feeds' + SMS = 'sms' DATE_ROW_FORMAT = '%Y/%m/%d %H:%M:%S' @@ -67,6 +71,8 @@ def create(cls, slug, account_id, couch_user, **kwargs): report = EnterpriseFormReport(account, couch_user, **kwargs) elif slug == cls.ODATA_FEEDS: report = EnterpriseODataReport(account, couch_user, **kwargs) + elif slug == cls.SMS: + report = EnterpriseSMSReport(account, couch_user, **kwargs) if report: report.slug = slug @@ -383,3 +389,85 @@ def _get_individual_export_rows(self, exports, export_line_counts): ) return rows + + +class EnterpriseSMSReport(EnterpriseReport): + title = gettext_lazy('SMS Sent') + MAX_DATE_RANGE_DAYS = 90 + + def __init__(self, account, couch_user, start_date=None, end_date=None, num_days=30): + super().__init__(account, couch_user) + + if not end_date: + end_date = datetime.utcnow() + elif isinstance(end_date, str): + end_date = datetime.fromisoformat(end_date) + + if start_date: + if isinstance(start_date, str): + start_date = datetime.fromisoformat(start_date) + self.datespan = DateSpan(start_date, end_date) + self.subtitle = _("{} to {}").format( + start_date.date(), + end_date.date(), + ) + else: + self.datespan = DateSpan(end_date - timedelta(days=num_days), end_date) + self.subtitle = _("past {} days").format(num_days) + + if self.datespan.enddate - self.datespan.startdate > timedelta(days=self.MAX_DATE_RANGE_DAYS): + raise TooMuchRequestedDataError( + _('Date ranges with more than {} days are not supported').format(self.MAX_DATE_RANGE_DAYS) + ) + + def total_for_domain(self, domain_obj): + query = SMS.objects.filter( + domain=domain_obj.name, + direction=OUTGOING, + date__gte=self.datespan.startdate, + date__lt=self.datespan.enddate_adjusted + ) + + return query.count() + + def create_count_subquery(self, **kwargs): + return Coalesce( + Subquery( + SMS.objects.filter( + domain=OuterRef('domain'), + date__gte=self.datespan.startdate, + date__lt=self.datespan.enddate_adjusted, + **kwargs + ) + .values('domain') + .annotate(count=Count('pk')) + .values('count') + ), + 0 + ) + + @property + def headers(self): + headers = super().headers + headers = [_('Project Space Name'), _('# Sent'), _('# Received'), _('# Errors')] + + return headers + + def rows_for_domain(self, domain_obj): + sent_subquery = self.create_count_subquery(direction=OUTGOING, processed=True) + received_subquery = self.create_count_subquery(direction=INCOMING, processed=True) + sent_subquery = self.create_count_subquery(direction=OUTGOING) + received_subquery = self.create_count_subquery(direction=INCOMING) + error_subquery = self.create_count_subquery(error=True) + + return ( + SMS.objects + .filter( + domain=domain_obj.name, + ) + .values('domain').distinct() + .annotate(sent_count=sent_subquery) + .annotate(received_count=received_subquery) + .annotate(error_count=error_subquery) + .values_list('domain', 'sent_count', 'received_count', 'error_count') + ) From 182ed6a0b8fe737117088f472a955fc0f232b18c Mon Sep 17 00:00:00 2001 From: Matt Riley Date: Fri, 22 Nov 2024 19:28:21 -0500 Subject: [PATCH 02/10] Added "SMS Sent" tile to enterprise dashboard --- .../enterprise/js/enterprise_dashboard.js | 61 +++++++++++++++++-- .../enterprise/enterprise_dashboard.html | 4 +- corehq/apps/enterprise/views.py | 13 ++-- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/corehq/apps/enterprise/static/enterprise/js/enterprise_dashboard.js b/corehq/apps/enterprise/static/enterprise/js/enterprise_dashboard.js index 82e88704b63d..63acd7307e88 100644 --- a/corehq/apps/enterprise/static/enterprise/js/enterprise_dashboard.js +++ b/corehq/apps/enterprise/static/enterprise/js/enterprise_dashboard.js @@ -60,7 +60,47 @@ hqDefine("enterprise/js/enterprise_dashboard", [ return self; }; - var DateRangeModal = function (datePicker, presetOptions, maxDateRangeDays, tileDisplay) { + var SMSTile = function (datePicker) { + var self = {}; + self.endDate = ko.observable(moment().utc()); + self.startDate = ko.observable(self.endDate().clone().subtract(30, "days")); + self.presetType = ko.observable(PRESET_LAST_30); + self.customDateRangeDisplay = ko.observable(datePicker.optionsStore.input.value); + + self.presetText = ko.pureComputed(function () { + if (self.presetType() !== PRESET_CUSTOM) { + return dateRangePresetOptions.find(ele => ele.id === self.presetType()).text; + } else { + return self.customDateRangeDisplay(); + } + }); + + self.onApply = function (preset, startDate, endDate) { + self.startDate(startDate); + self.endDate(endDate); + self.presetType(preset); + self.customDateRangeDisplay(datePicker.optionsStore.input.value); + + updateDisplayTotal($("#sms"), { + "start_date": startDate.toISOString(), + "end_date": endDate.toISOString(), + }); + }; + + return self; + }; + + var DateRangeModal = function ($modal, datePicker, presetOptions, maxDateRangeDays, tileMap) { + let tileDisplay = null; + $modal.on('show.bs.modal', function (event) { + var button = $(event.relatedTarget); + tileDisplay = tileMap[button.data('sender')]; + + self.presetType(tileDisplay.presetType()); + self.customStartDate(tileDisplay.startDate().clone()); + self.customEndDate(tileDisplay.endDate().clone()); + }); + var self = {}; self.presetOptions = presetOptions; self.presetType = ko.observable(PRESET_LAST_30); @@ -198,12 +238,21 @@ hqDefine("enterprise/js/enterprise_dashboard", [ moment() ); + const $dateRangeModal = $('#enterpriseFormsDaterange'); + const formSubmissionsDisplay = MobileFormSubmissionsTile(datePicker); + const smsDisplay = SMSTile(datePicker); const maxDateRangeDays = initialPageData.get("max_date_range_days"); - const dateRangeModal = DateRangeModal(datePicker, dateRangePresetOptions, maxDateRangeDays, formSubmissionsDisplay); - $("#dateRangeDisplay").koApplyBindings(formSubmissionsDisplay); - $("#enterpriseFormsDaterange").koApplyBindings( + const displayMap = { + "form_submission": formSubmissionsDisplay, + "sms": smsDisplay, + }; + const dateRangeModal = DateRangeModal($dateRangeModal, datePicker, dateRangePresetOptions, maxDateRangeDays, displayMap); + + $("#form_submission_dateRangeDisplay").koApplyBindings(formSubmissionsDisplay); + $("#sms_dateRangeDisplay").koApplyBindings(smsDisplay); + $dateRangeModal.koApplyBindings( dateRangeModal ); @@ -233,7 +282,9 @@ hqDefine("enterprise/js/enterprise_dashboard", [ $button.enableButton(); }, }; - if (slug === "form_submissions") { + + const dateRangeSlugs = ["form_submissions", "sms"]; + if (dateRangeSlugs.includes(slug)) { requestParams["data"] = { "start_date": dateRangeModal.startDate().toISOString(), "end_date": dateRangeModal.endDate().toISOString(), diff --git a/corehq/apps/enterprise/templates/enterprise/enterprise_dashboard.html b/corehq/apps/enterprise/templates/enterprise/enterprise_dashboard.html index 02c3f5ffb4e6..fd6bb38cc742 100644 --- a/corehq/apps/enterprise/templates/enterprise/enterprise_dashboard.html +++ b/corehq/apps/enterprise/templates/enterprise/enterprise_dashboard.html @@ -19,7 +19,9 @@
{{ report.title }}
{% if report.title == "Mobile Form Submissions" %} - + + {% elif report.title == "SMS Sent" %} + {% else %}
{{ report.subtitle|default:" " }}
{% endif %} diff --git a/corehq/apps/enterprise/views.py b/corehq/apps/enterprise/views.py index 169d1ed15ef6..0d1d5a524efb 100644 --- a/corehq/apps/enterprise/views.py +++ b/corehq/apps/enterprise/views.py @@ -80,6 +80,7 @@ def enterprise_dashboard(request, domain): EnterpriseReport.MOBILE_USERS, EnterpriseReport.FORM_SUBMISSIONS, EnterpriseReport.ODATA_FEEDS, + EnterpriseReport.SMS, )], 'current_page': { 'page_name': _('Enterprise Dashboard'), @@ -93,8 +94,9 @@ def enterprise_dashboard(request, domain): @login_and_domain_required def enterprise_dashboard_total(request, domain, slug): kwargs = {} - if slug == EnterpriseReport.FORM_SUBMISSIONS: - kwargs = get_form_submission_report_kwargs(request) + date_range_slugs = [EnterpriseReport.FORM_SUBMISSIONS, EnterpriseReport.SMS] + if slug in date_range_slugs: + kwargs = get_date_range_kwargs(request) try: report = EnterpriseReport.create(slug, request.account.id, request.couch_user, **kwargs) except TooMuchRequestedDataError as e: @@ -141,8 +143,9 @@ def _get_export_filename(request, slug): @login_and_domain_required def enterprise_dashboard_email(request, domain, slug): kwargs = {} - if slug == EnterpriseReport.FORM_SUBMISSIONS: - kwargs = get_form_submission_report_kwargs(request) + date_range_slugs = [EnterpriseReport.FORM_SUBMISSIONS, EnterpriseReport.SMS] + if slug in date_range_slugs: + kwargs = get_date_range_kwargs(request) try: report = EnterpriseReport.create(slug, request.account.id, request.couch_user, **kwargs) except TooMuchRequestedDataError as e: @@ -158,7 +161,7 @@ def enterprise_dashboard_email(request, domain, slug): return JsonResponse({'message': message}) -def get_form_submission_report_kwargs(request): +def get_date_range_kwargs(request): kwargs = {} start_date = request.GET.get('start_date') end_date = request.GET.get('end_date') From 8f45845989d38a926f4b1d4cfaa06c771d634103 Mon Sep 17 00:00:00 2001 From: Matt Riley Date: Mon, 25 Nov 2024 13:11:18 -0500 Subject: [PATCH 03/10] Added Enterprise SMS API --- corehq/apps/enterprise/api/api.py | 2 ++ corehq/apps/enterprise/api/resources.py | 39 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/corehq/apps/enterprise/api/api.py b/corehq/apps/enterprise/api/api.py index e3703aae5edd..fc53b3102a7b 100644 --- a/corehq/apps/enterprise/api/api.py +++ b/corehq/apps/enterprise/api/api.py @@ -6,6 +6,7 @@ MobileUserResource, ODataFeedResource, WebUserResource, + SMSResource, ) v1_api = Api(api_name='v1') @@ -14,3 +15,4 @@ v1_api.register(MobileUserResource()) v1_api.register(FormSubmissionResource()) v1_api.register(ODataFeedResource()) +v1_api.register(SMSResource()) diff --git a/corehq/apps/enterprise/api/resources.py b/corehq/apps/enterprise/api/resources.py index 09d5b85d120d..94c12568ae35 100644 --- a/corehq/apps/enterprise/api/resources.py +++ b/corehq/apps/enterprise/api/resources.py @@ -6,6 +6,7 @@ from django.utils.translation import gettext as _ from dateutil import tz +from datetime import timezone from tastypie import fields, http from tastypie.exceptions import ImmediateHttpResponse @@ -314,6 +315,44 @@ def get_primary_keys(self): return ('user_id',) +class SMSResource(ODataEnterpriseReportResource): + domain = fields.CharField() + num_sent = fields.IntegerField() + num_received = fields.IntegerField() + num_error = fields.IntegerField() + + REPORT_SLUG = EnterpriseReport.SMS + + def get_report_task(self, request): + start_date = request.GET.get('startdate', None) + if start_date: + start_date = str(datetime.fromisoformat(start_date).astimezone(timezone.utc)) + + end_date = request.GET.get('enddate', None) + if end_date: + end_date = str(datetime.fromisoformat(end_date).astimezone(timezone.utc)) + + account = BillingAccount.get_account_by_domain(request.domain) + return generate_enterprise_report.s( + self.REPORT_SLUG, + account.id, + request.couch_user.username, + start_date=start_date, + end_date=end_date + ) + + def dehydrate(self, bundle): + bundle.data['domain'] = bundle.obj[0] + bundle.data['num_sent'] = bundle.obj[1] + bundle.data['num_received'] = bundle.obj[2] + bundle.data['num_error'] = bundle.obj[3] + + return bundle + + def get_primary_keys(self): + return ('domain',) + + class ODataFeedResource(ODataEnterpriseReportResource): ''' A Resource for listing all Domain-level OData feeds which belong to the Enterprise. From 72b7bef06d67115fc4fec7520765d640a980cdd7 Mon Sep 17 00:00:00 2001 From: Matt Riley Date: Mon, 25 Nov 2024 13:43:12 -0500 Subject: [PATCH 04/10] Add empty entry for domains lacking SMS messages --- corehq/apps/enterprise/enterprise.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/corehq/apps/enterprise/enterprise.py b/corehq/apps/enterprise/enterprise.py index b38695e61086..88e3d4211fa1 100644 --- a/corehq/apps/enterprise/enterprise.py +++ b/corehq/apps/enterprise/enterprise.py @@ -460,7 +460,7 @@ def rows_for_domain(self, domain_obj): received_subquery = self.create_count_subquery(direction=INCOMING) error_subquery = self.create_count_subquery(error=True) - return ( + query = ( SMS.objects .filter( domain=domain_obj.name, @@ -471,3 +471,9 @@ def rows_for_domain(self, domain_obj): .annotate(error_count=error_subquery) .values_list('domain', 'sent_count', 'received_count', 'error_count') ) + + domain_results = list(query) + if domain_results: + return domain_results + else: + return [(domain_obj.name, 0, 0, 0),] From 4d80d9fc0963bacc4a24a0e78d60ae79f79c8fef Mon Sep 17 00:00:00 2001 From: Matt Riley Date: Mon, 25 Nov 2024 14:45:37 -0500 Subject: [PATCH 05/10] Corrected small mistakes in initial SMS report implementation --- corehq/apps/enterprise/enterprise.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/corehq/apps/enterprise/enterprise.py b/corehq/apps/enterprise/enterprise.py index 88e3d4211fa1..0a532eeabb6a 100644 --- a/corehq/apps/enterprise/enterprise.py +++ b/corehq/apps/enterprise/enterprise.py @@ -448,7 +448,6 @@ def create_count_subquery(self, **kwargs): @property def headers(self): - headers = super().headers headers = [_('Project Space Name'), _('# Sent'), _('# Received'), _('# Errors')] return headers @@ -456,8 +455,6 @@ def headers(self): def rows_for_domain(self, domain_obj): sent_subquery = self.create_count_subquery(direction=OUTGOING, processed=True) received_subquery = self.create_count_subquery(direction=INCOMING, processed=True) - sent_subquery = self.create_count_subquery(direction=OUTGOING) - received_subquery = self.create_count_subquery(direction=INCOMING) error_subquery = self.create_count_subquery(error=True) query = ( From 3f58ff310201ce528aad5eeb964e6ed111280a43 Mon Sep 17 00:00:00 2001 From: Matt Riley Date: Mon, 25 Nov 2024 16:34:46 -0500 Subject: [PATCH 06/10] Simplified SMS Report query logic --- corehq/apps/enterprise/enterprise.py | 44 +++++----------------------- 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/corehq/apps/enterprise/enterprise.py b/corehq/apps/enterprise/enterprise.py index 0a532eeabb6a..609aed1f9e1c 100644 --- a/corehq/apps/enterprise/enterprise.py +++ b/corehq/apps/enterprise/enterprise.py @@ -1,6 +1,5 @@ import re -from django.db.models import OuterRef, Subquery, Count -from django.db.models.functions import Coalesce +from django.db.models import Count from datetime import datetime, timedelta from django.utils.translation import gettext as _ @@ -430,22 +429,6 @@ def total_for_domain(self, domain_obj): return query.count() - def create_count_subquery(self, **kwargs): - return Coalesce( - Subquery( - SMS.objects.filter( - domain=OuterRef('domain'), - date__gte=self.datespan.startdate, - date__lt=self.datespan.enddate_adjusted, - **kwargs - ) - .values('domain') - .annotate(count=Count('pk')) - .values('count') - ), - 0 - ) - @property def headers(self): headers = [_('Project Space Name'), _('# Sent'), _('# Received'), _('# Errors')] @@ -453,24 +436,11 @@ def headers(self): return headers def rows_for_domain(self, domain_obj): - sent_subquery = self.create_count_subquery(direction=OUTGOING, processed=True) - received_subquery = self.create_count_subquery(direction=INCOMING, processed=True) - error_subquery = self.create_count_subquery(error=True) + results = SMS.objects.filter(domain=domain_obj.name) \ + .values('direction', 'error').annotate(direction_count=Count('pk')) - query = ( - SMS.objects - .filter( - domain=domain_obj.name, - ) - .values('domain').distinct() - .annotate(sent_count=sent_subquery) - .annotate(received_count=received_subquery) - .annotate(error_count=error_subquery) - .values_list('domain', 'sent_count', 'received_count', 'error_count') - ) + num_sent = sum([result['direction_count'] for result in results if result['direction'] == OUTGOING]) + num_received = sum([result['direction_count'] for result in results if result['direction'] == INCOMING]) + num_errors = sum([result['direction_count'] for result in results if result['error']]) - domain_results = list(query) - if domain_results: - return domain_results - else: - return [(domain_obj.name, 0, 0, 0),] + return [(domain_obj.name, num_sent, num_received, num_errors), ] From fc4fccdf5a1f35f682deee56395310c127614c83 Mon Sep 17 00:00:00 2001 From: Matt Riley Date: Mon, 2 Dec 2024 11:29:58 -0500 Subject: [PATCH 07/10] Updated SMS Usage tile title --- corehq/apps/enterprise/enterprise.py | 2 +- .../enterprise/templates/enterprise/enterprise_dashboard.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/corehq/apps/enterprise/enterprise.py b/corehq/apps/enterprise/enterprise.py index 609aed1f9e1c..7b9203429d0b 100644 --- a/corehq/apps/enterprise/enterprise.py +++ b/corehq/apps/enterprise/enterprise.py @@ -391,7 +391,7 @@ def _get_individual_export_rows(self, exports, export_line_counts): class EnterpriseSMSReport(EnterpriseReport): - title = gettext_lazy('SMS Sent') + title = gettext_lazy('SMS Usage') MAX_DATE_RANGE_DAYS = 90 def __init__(self, account, couch_user, start_date=None, end_date=None, num_days=30): diff --git a/corehq/apps/enterprise/templates/enterprise/enterprise_dashboard.html b/corehq/apps/enterprise/templates/enterprise/enterprise_dashboard.html index fd6bb38cc742..f666a63d66e4 100644 --- a/corehq/apps/enterprise/templates/enterprise/enterprise_dashboard.html +++ b/corehq/apps/enterprise/templates/enterprise/enterprise_dashboard.html @@ -20,7 +20,7 @@
{{ report.title }}
{% if report.title == "Mobile Form Submissions" %} - {% elif report.title == "SMS Sent" %} + {% elif report.title == "SMS Usage" %} {% else %}
{{ report.subtitle|default:" " }}
From 8310efd046ed9a70859397466b507bf5e1b82a21 Mon Sep 17 00:00:00 2001 From: Matt Riley Date: Mon, 2 Dec 2024 11:36:11 -0500 Subject: [PATCH 08/10] Renamed SMS Usage report headers --- corehq/apps/enterprise/enterprise.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/corehq/apps/enterprise/enterprise.py b/corehq/apps/enterprise/enterprise.py index 7b9203429d0b..16f850c1e9e7 100644 --- a/corehq/apps/enterprise/enterprise.py +++ b/corehq/apps/enterprise/enterprise.py @@ -431,7 +431,7 @@ def total_for_domain(self, domain_obj): @property def headers(self): - headers = [_('Project Space Name'), _('# Sent'), _('# Received'), _('# Errors')] + headers = [_('Project Space'), _('# Sent'), _('# Received'), _('# Errors')] return headers From c1816549f05dfaac9f8902e106b6a2d3896330e9 Mon Sep 17 00:00:00 2001 From: Matt Riley Date: Mon, 2 Dec 2024 11:49:14 -0500 Subject: [PATCH 09/10] move date range extraction to utility function --- corehq/apps/enterprise/api/resources.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/corehq/apps/enterprise/api/resources.py b/corehq/apps/enterprise/api/resources.py index 94c12568ae35..1d49b526d323 100644 --- a/corehq/apps/enterprise/api/resources.py +++ b/corehq/apps/enterprise/api/resources.py @@ -324,13 +324,7 @@ class SMSResource(ODataEnterpriseReportResource): REPORT_SLUG = EnterpriseReport.SMS def get_report_task(self, request): - start_date = request.GET.get('startdate', None) - if start_date: - start_date = str(datetime.fromisoformat(start_date).astimezone(timezone.utc)) - - end_date = request.GET.get('enddate', None) - if end_date: - end_date = str(datetime.fromisoformat(end_date).astimezone(timezone.utc)) + start_date, end_date = get_date_range_from_request(request.GET) account = BillingAccount.get_account_by_domain(request.domain) return generate_enterprise_report.s( @@ -353,6 +347,18 @@ def get_primary_keys(self): return ('domain',) +def get_date_range_from_request(request_dict): + start_date = request_dict.get('startdate', None) + if start_date: + start_date = str(datetime.fromisoformat(start_date).astimezone(timezone.utc)) + + end_date = request_dict.get('enddate', None) + if end_date: + end_date = str(datetime.fromisoformat(end_date).astimezone(timezone.utc)) + + return (start_date, end_date,) + + class ODataFeedResource(ODataEnterpriseReportResource): ''' A Resource for listing all Domain-level OData feeds which belong to the Enterprise. From 1adcaf384f41033b97d3377f9185ae91c9a4562a Mon Sep 17 00:00:00 2001 From: Matt Riley Date: Mon, 2 Dec 2024 12:08:03 -0500 Subject: [PATCH 10/10] Fixed SMS Report query --- corehq/apps/enterprise/enterprise.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/corehq/apps/enterprise/enterprise.py b/corehq/apps/enterprise/enterprise.py index 16f850c1e9e7..bbd1489da7ed 100644 --- a/corehq/apps/enterprise/enterprise.py +++ b/corehq/apps/enterprise/enterprise.py @@ -422,7 +422,9 @@ def __init__(self, account, couch_user, start_date=None, end_date=None, num_days def total_for_domain(self, domain_obj): query = SMS.objects.filter( domain=domain_obj.name, + processed=True, direction=OUTGOING, + error=False, date__gte=self.datespan.startdate, date__lt=self.datespan.enddate_adjusted ) @@ -436,11 +438,17 @@ def headers(self): return headers def rows_for_domain(self, domain_obj): - results = SMS.objects.filter(domain=domain_obj.name) \ - .values('direction', 'error').annotate(direction_count=Count('pk')) + results = SMS.objects.filter( + domain=domain_obj.name, + processed=True, + date__gte=self.datespan.startdate, + date__lt=self.datespan.enddate_adjusted + ).values('direction', 'error').annotate(direction_count=Count('pk')) - num_sent = sum([result['direction_count'] for result in results if result['direction'] == OUTGOING]) - num_received = sum([result['direction_count'] for result in results if result['direction'] == INCOMING]) + num_sent = sum([result['direction_count'] for result in results + if result['direction'] == OUTGOING and not result['error']]) + num_received = sum([result['direction_count'] for result in results + if result['direction'] == INCOMING and not result['error']]) num_errors = sum([result['direction_count'] for result in results if result['error']]) return [(domain_obj.name, num_sent, num_received, num_errors), ]