def _create_report(self, domain=None, owner_id=None): domain = domain or self.domain owner_id = owner_id or self.user._id report = ReportNotification(domain=domain, owner_id=owner_id) report.save() self.addCleanup(report.delete) return report
def test_owner_can_delete_report(self, mock_success): report = self._create_report(owner_id=self.user._id) response = self.delete_scheduled_report(user=self.user, report_id=report._id) self.assertEqual(response.status_code, 302) with self.assertRaises(ResourceNotFound): ReportNotification.get(report._id)
def testIntervalReportDontIncludeOtherIntervals(self): ReportNotification(hour=1, minute=0, interval='hourly').save() ReportNotification(hour=1, minute=0, interval='daily').save() ReportNotification(hour=12, minute=0, day=4, interval='weekly').save() ReportNotification(hour=1, minute=0, interval='monthly').save() self._check('hourly', datetime(2014, 10, 1, 1, 0), 1) self._check('daily', datetime(2014, 10, 1, 1, 0), 1) self._check('weekly', datetime(2014, 10, 31, 12, 0), 1) self._check('monthly', datetime(2014, 10, 1, 1, 0), 1)
def test_domain_admin_can_delete_report(self, mock_success): domain_admin = self._create_user(username='******', is_admin=True) report = self._create_report(owner_id=self.user._id) response = self.delete_scheduled_report(user=domain_admin, report_id=report._id) self.assertEqual(response.status_code, 302) with self.assertRaises(ResourceNotFound): ReportNotification.get(report._id)
def _create_scheduled_report(self, domain=None, owner_id=None, config_ids=[]): report = ReportNotification(domain=domain, owner_id=owner_id, config_ids=config_ids) report.save() self.addCleanup(report.delete) return report
def create_report_notification(self, configs, owner_id): domain = configs[0].domain config_ids = [c._id for c in configs] rn = ReportNotification( domain=domain, config_ids=config_ids, owner_id=owner_id, interval='daily', ) rn.save() self.addCleanup(rn.delete) return rn
def delete_all_report_notifications(): for report in ReportNotification.view( 'reportconfig/all_notifications', include_docs=True, reduce=False, ).all(): report.delete()
def get_reports_by_domain(domain): key = [domain] reports = ReportNotification.view('reportconfig/user_notifications', reduce=False, include_docs=True, startkey=key, endkey=key + [{}]) return reports
def testDailyReportLenientWindow(self): ReportNotification(hour=12, minute=0, interval='daily').save() self._check('daily', datetime(2014, 10, 31, 12, 5), 1) # lenient window # but not too lenient self.assertRaises( ValueError, lambda: list( get_scheduled_report_ids( 'daily', end_datetime=datetime(2014, 10, 31, 12, 6))))
def test_get_scheduled_report_response(self): domain = self.domain report_config = ReportConfig.wrap({ "date_range": "last30", "days": 30, "domain": domain, "report_slug": "worker_activity", "report_type": "project_report", "owner_id": self.user._id, }) report_config.save() report = ReportNotification( hour=12, minute=None, day=30, interval='monthly', config_ids=[report_config._id] ) report.save() response = get_scheduled_report_response( couch_user=self.user, domain=domain, scheduled_report_id=report._id )[0] self.assertTrue(self.user.username in response.decode('utf-8'))
def send_report(notification_id): notification = ReportNotification.get(notification_id) # If the report's start date is later than today, return and do not send the email if notification.start_date and notification.start_date > datetime.today().date(): return try: notification.send() except UnsupportedScheduledReportError: pass
def testDefaultValue(self): now = datetime.utcnow() # This line makes sure that the date of the ReportNotification is an increment of 15 minutes ReportNotification(hour=now.hour, minute=(now.minute // 15) * 15, interval='daily').save() if now.minute % 15 <= 5: self._check('daily', None, 1) else: self.assertRaises( ValueError, lambda: list(get_scheduled_report_ids('daily', None)))
def send_report(notification_id): notification = ReportNotification.get(notification_id) # If the report's start date is later than today, return and do not send the email if notification.start_date and notification.start_date > datetime.today( ).date(): return try: notification.send() except UnsupportedScheduledReportError: pass
def test_get_scheduled_report_response(self): domain = self.domain report_config = ReportConfig.wrap({ "date_range": "last30", "days": 30, "domain": domain, "report_slug": "worker_activity", "report_type": "project_report", "owner_id": self.user._id, }) report_config.save() report = ReportNotification(hour=12, minute=None, day=30, interval='monthly', config_ids=[report_config._id]) report.save() response = get_scheduled_report_response( couch_user=self.user, domain=domain, scheduled_report_id=report._id)[0] self.assertTrue(self.user.username in response.decode('utf-8'))
def send_delayed_report(report_id): """ Sends a scheduled report, via celery background task. """ domain = ReportNotification.get(report_id).domain if (settings.SERVER_ENVIRONMENT == 'production' and any( re.match(pattern, domain) for pattern in settings.THROTTLE_SCHED_REPORTS_PATTERNS)): # This is to prevent a few scheduled reports from clogging up # the background queue. # https://manage.dimagi.com/default.asp?270029#BugEvent.1457969 send_report_throttled.delay(report_id) else: send_report.delay(report_id)
def send_delayed_report(report_id): """ Sends a scheduled report, via celery background task. """ domain = ReportNotification.get(report_id).domain if ( settings.SERVER_ENVIRONMENT == 'production' and any(re.match(pattern, domain) for pattern in settings.THROTTLE_SCHED_REPORTS_PATTERNS) ): # This is to prevent a few scheduled reports from clogging up # the background queue. # https://manage.dimagi.com/default.asp?270029#BugEvent.1457969 send_report_throttled.delay(report_id) else: send_report.delay(report_id)
def get_scheduled_report_ids(period, as_of=None): as_of = as_of or datetime.utcnow() assert period in ('daily', 'weekly', 'monthly'), period def _keys(period, as_of): minute = guess_reporting_minute(as_of) if minute == 0: # for legacy purposes, on the hour also include reports that didn't have a minute set minutes = (None, minute) else: minutes = (minute, ) if period == 'daily': for minute in minutes: yield { 'startkey': [period, as_of.hour, minute], 'endkey': [period, as_of.hour, minute, {}], } elif period == 'weekly': for minute in minutes: yield { 'key': [period, as_of.hour, minute, as_of.weekday()], } else: # monthly for minute in minutes: yield {'key': [period, as_of.hour, minute, as_of.day]} if as_of.day == monthrange(as_of.year, as_of.month)[1]: for day in range(as_of.day + 1, 32): for minute in minutes: yield {'key': [period, as_of.hour, minute, day]} try: keys = _keys(period, as_of) except ValueError: _soft_assert( False, "Celery was probably down for a while. Lots of reports are getting dropped!" ) raise StopIteration for key in keys: for result in ReportNotification.view("reportconfig/all_notifications", reduce=False, include_docs=False, **key).all(): yield result['id']
def get_scheduled_report_ids(period, start_datetime=None, end_datetime=None): end_datetime = end_datetime or datetime.utcnow() assert period in ('daily', 'weekly', 'monthly'), period if not start_datetime: start_datetime = _get_default_start_datetime(end_datetime) for target_point_in_time in _iter_15_minute_marks_in_range( start_datetime, end_datetime): keys = _make_all_notification_view_keys(period, target_point_in_time) for key in keys: for result in ReportNotification.view( "reportconfig/all_notifications", reduce=False, include_docs=False, **key).all(): yield result['id']
def get_scheduled_report_ids(period, start_datetime=None, end_datetime=None): end_datetime = end_datetime or datetime.utcnow() assert period in ('daily', 'weekly', 'monthly'), period if not start_datetime: start_datetime = _get_default_start_datetime(end_datetime) for target_point_in_time in _iter_15_minute_marks_in_range(start_datetime, end_datetime): keys = _make_all_notification_view_keys(period, target_point_in_time) for key in keys: for result in ReportNotification.view( "reportconfig/all_notifications", reduce=False, include_docs=False, **key ).all(): yield result['id']
def testMonthlyReportOnTheEndOfTheMonthDaysAfter31DontCount(self): ReportNotification(hour=12, minute=None, day=31, interval='monthly').save() ReportNotification(hour=12, minute=None, day=32, interval='monthly').save() self._check('monthly', datetime(2014, 10, 31, 12, 0), 1)
def testMonthlyReportBeforeTheEndOfTheMonth(self): ReportNotification(hour=12, minute=None, day=30, interval='monthly').save() ReportNotification(hour=12, minute=None, day=31, interval='monthly').save() self._check('monthly', datetime(2014, 10, 30, 12, 0), 1)
def testDailyReportWithMinute(self): ReportNotification(hour=12, minute=0, interval='daily').save() self._check('daily', datetime(2014, 10, 31, 12, 0), 1) self._check('daily', datetime(2014, 10, 31, 12, 30), 0)
def testDailyReportOtherHoursDontCount(self): ReportNotification(hour=12, minute=0, day=31, interval='daily').save() self._check('daily', datetime(2014, 10, 31, 11, 0), 0)
def testHourlyReportWithoutMinute(self): # We don't currently cater for minute-specific hourly reporting; # every report with 'hourly' interval will be sent on the zero-minute hour ReportNotification(hour=1, minute=None, interval='hourly').save() self._check('hourly', datetime(2014, 10, 31, 1, 0), 1)
def testHourlyReportOtherTypesDontCount(self): ReportNotification(hour=1, minute=0, interval='hourly').save() self._check('daily', datetime(2014, 10, 31, 1, 0), 0) self._check('weekly', datetime(2014, 10, 31, 1, 0), 0) self._check('monthly', datetime(2014, 10, 31, 1, 0), 0)
def testHourlyReportHourDontMatter(self): ReportNotification(hour=1, minute=0, interval='hourly').save() self._check('hourly', datetime(2014, 10, 31, 1, 0), 1) self._check('hourly', datetime(2014, 10, 31, 12, 0), 1) self._check('hourly', datetime(2014, 10, 31, 23, 0), 1)
def testDailyReportEmptyMinute(self): ReportNotification(hour=12, minute=None, interval='daily').save() self._check('daily', datetime(2014, 10, 31, 12, 0), 1) self._check('daily', datetime(2014, 10, 31, 12, 30), 0) # half hour shouldn't count
def testWeeklyReportOtherDaysDontCount(self): ReportNotification(hour=12, minute=0, day=4, interval='weekly').save() self._check('weekly', datetime(2014, 10, 30, 12, 0), 0)
def testMonthlyReportOtherDaysDontCount(self): ReportNotification(hour=12, minute=None, day=31, interval='monthly').save() self._check('monthly', datetime(2014, 10, 30, 12, 0), 0)
def testMonthlyReportOnTheEndOfTheMonthEmptyMinute(self): ReportNotification(hour=12, minute=None, day=30, interval='monthly').save() ReportNotification(hour=12, minute=None, day=31, interval='monthly').save() self._check('monthly', datetime(2014, 11, 30, 12, 0), 2)
class ScheduledReportForm(forms.Form): INTERVAL_CHOICES = [("daily", "Daily"), ("weekly", "Weekly"), ("monthly", "Monthly")] config_ids = forms.MultipleChoiceField( label=_("Saved report(s)"), validators=[MinLengthValidator(1)], help_text='Note: not all built-in reports support email delivery, so' ' some of your saved reports may not appear in this list') interval = forms.TypedChoiceField(label=_('Interval'), widget=SelectToggle( choices=INTERVAL_CHOICES, apply_bindings=True), choices=INTERVAL_CHOICES) day = forms.TypedChoiceField(label=_("Day"), coerce=int, required=False, choices=[(i, i) for i in range(0, 32)]) hour = forms.TypedChoiceField(label=_('Time'), coerce=int, choices=ReportNotification.hour_choices()) start_date = forms.DateField(label=_('Report Start Date'), required=False) send_to_owner = forms.BooleanField(label=_('Send to owner'), required=False) attach_excel = forms.BooleanField(label=_('Attach Excel Report'), required=False) recipient_emails = MultiEmailField(label=_('Other recipients'), required=False) email_subject = forms.CharField( required=False, help_text= 'Translated into recipient\'s language if set to "%(default_subject)s".' % { 'default_subject': DEFAULT_REPORT_NOTIF_SUBJECT, }, ) language = forms.ChoiceField(label=_('Language'), required=False, choices=[('', '')] + langcodes.get_all_langs_for_select(), widget=forms.Select()) def __init__(self, *args, **kwargs): self.helper = FormHelper() self.helper.form_class = 'form-horizontal' self.helper.form_id = 'id-scheduledReportForm' self.helper.label_class = 'col-sm-3 col-md-2' self.helper.field_class = 'col-sm-9 col-md-8 col-lg-6' self.helper.add_layout( crispy.Layout( crispy.Fieldset( ugettext("Configure Scheduled Report"), 'config_ids', 'interval', 'day', 'hour', 'start_date', crispy.Field( 'email_subject', css_class='input-xlarge', ), crispy.Field('send_to_owner'), crispy.Field('attach_excel'), 'recipient_emails', 'language', crispy.HTML( render_to_string( 'reports/partials/privacy_disclaimer.html'))), FormActions(crispy.Submit('submit_btn', 'Submit')))) super(ScheduledReportForm, self).__init__(*args, **kwargs) def clean(self): cleaned_data = super(ScheduledReportForm, self).clean() if cleaned_data["interval"] == "daily": del cleaned_data["day"] _verify_email(cleaned_data) return cleaned_data
def testMonthlyReportOnTheEndOfTheMonthWithMinuteHalfHour(self): ReportNotification(hour=12, minute=30, day=30, interval='monthly').save() ReportNotification(hour=12, minute=30, day=31, interval='monthly').save() self._check('monthly', datetime(2014, 11, 30, 12, 30), 2)
def testHourlyReportWithMinuteZero(self): ReportNotification(hour=1, minute=0, interval='hourly').save() self._check('hourly', datetime(2014, 10, 31, 1, 0), 1) self._check('hourly', datetime(2014, 10, 31, 1, 30), 0)
def test_scheduled_reports_identified(self): point_1 = datetime.datetime(2019, 3, 22, 22, 46, 0, 439979) # target point in time: datetime.datetime(2019, 3, 22, 23, 0, 0, 0) point_2 = datetime.datetime(2019, 3, 22, 23, 11, 38, 363898) test_cases = [ ('daily minute None', True, ReportNotification(hour=23, minute=None, interval='daily')), ('daily minute 0', True, ReportNotification(hour=23, minute=0, interval='daily')), ('daily minute off', False, ReportNotification(hour=23, minute=15, interval='daily')), ('daily hour off', False, ReportNotification(hour=22, minute=0, interval='daily')), ('weekly minute None', True, ReportNotification(hour=23, minute=None, day=4, interval='weekly')), ('weekly minute 0', True, ReportNotification(hour=23, minute=0, day=4, interval='weekly')), ('weekly minute off', False, ReportNotification(hour=23, minute=15, day=4, interval='weekly')), ('weekly hour off', False, ReportNotification(hour=22, minute=0, day=4, interval='weekly')), ('weekly day off', False, ReportNotification(hour=22, minute=0, day=3, interval='weekly')), ('monthly minute None', True, ReportNotification(hour=23, minute=None, day=22, interval='monthly')), ('monthly minute 0', True, ReportNotification(hour=23, minute=0, day=22, interval='monthly')), ('monthly minute off', False, ReportNotification(hour=23, minute=15, day=22, interval='monthly')), ('monthly hour off', False, ReportNotification(hour=22, minute=0, day=22, interval='monthly')), ('monthly day off', False, ReportNotification(hour=22, minute=0, day=20, interval='monthly')), ] create_records_for_scheduled_reports(fake_now_for_tests=point_1) for _, _, report in test_cases: report.save() report_ids = create_records_for_scheduled_reports( fake_now_for_tests=point_2) for description, should_fire, report in test_cases: if should_fire: self.assertIn( report._id, report_ids, "{}: should have fired but didn't".format(description)) else: self.assertNotIn( report._id, report_ids, "{}: shouldn't have fired but did".format(description))