def saved_report_context_data(self): """ Returns a dictionary with every saved report config associated with the report """ def _get_context_for_saved_report(report_config): if report_config: report_config_data = report_config.to_json() report_config_data['filters'].update( report_config.get_date_range()) return report_config_data else: return ReportConfig.default() context = { 'report_configs': [ _get_context_for_saved_report(saved_report) for saved_report in ReportConfig.by_domain_and_owner(self.domain, self.request.couch_user._id, report_slug=self.slug) ], 'default_config': _get_context_for_saved_report(self.saved_report_config), 'datespan_filters': ReportConfig.datespan_filter_choices(self.datespan_filters, self.lang), } return context
def saved_report_context_data(self): def _get_context_for_saved_report(report_config): if report_config: report_config_data = report_config.to_json() report_config_data['filters'].update( report_config.get_date_range()) return report_config_data else: return ReportConfig.default() saved_report_config_id = self.request.GET.get('config_id') saved_report_config = get_document_or_404(ReportConfig, self.domain, saved_report_config_id) \ if saved_report_config_id else None return { 'report_configs': [ _get_context_for_saved_report(saved_report) for saved_report in ReportConfig.by_domain_and_owner(self.domain, self.request.couch_user._id, report_slug=self.slug) ], 'default_config': _get_context_for_saved_report(saved_report_config), 'datespan_filters': ReportConfig.datespan_filter_choices(self.datespan_filters, self.lang), }
def _migrate_report_filters(apps, schema_editor): """ Migrates ReportConfig filters with multiple values from CHOICE_DELIMITER-delimited strings to lists. """ key = ["name slug"] results = ReportConfig.get_db().view( "reportconfig/configs_by_domain", reduce=False, include_docs=True, startkey=key, endkey=key + [{}] ) count = 0 for result in results: dirty = False doc = result['doc'] config = ReportConfig.wrap(doc) for name, value in config['filters'].items(): if isinstance(value, str) and CHOICE_DELIMITER in value: print("Updating config {} filter {}".format(config._id, name)) config['filters'][name] = value.split(CHOICE_DELIMITER) dirty = True if dirty: count = count + 1 config.save() print("Updated {} configs".format(count))
def _update_initial_context(self): """ Intention: Don't override. """ report_configs = ReportConfig.by_domain_and_owner(self.domain, self.request.couch_user._id, report_slug=self.slug) current_config_id = self.request.GET.get('config_id', '') default_config = ReportConfig.default() def is_editable_datespan(field): if isinstance(field, six.string_types): soft_assert_type_text(field) field_fn = to_function(field) if isinstance(field, six.string_types) else field return issubclass(field_fn, DatespanFilter) and field_fn.is_editable has_datespan = any([is_editable_datespan(field) for field in self.fields]) self.context.update( report=dict( title=self.rendered_report_title, description=self.description, section_name=self.section_name, slug=self.slug, sub_slug=None, type=self.dispatcher.prefix, url_root=self.url_root, is_async=self.asynchronous, is_exportable=self.exportable, dispatcher=self.dispatcher, filter_set=self.filter_set, needs_filters=self.needs_filters, has_datespan=has_datespan, show=( self.override_permissions_check or self.request.couch_user.can_view_some_reports(self.domain) ), is_emailable=self.emailable, is_export_all = self.exportable_all, is_printable=self.printable, is_admin=self.is_admin_report, special_notice=self.special_notice, report_title=self.report_title or self.rendered_report_title, report_subtitles=self.report_subtitles, export_target=self.export_target, js_options=self.js_options, custom_filter_action_template=( self.custom_filter_action_template if hasattr(self, 'custom_filter_action_template') else False ), ), current_config_id=current_config_id, default_config=default_config, report_configs=report_configs, show_time_notice=self.show_time_notice, domain=self.domain, layout_flush_content=self.flush_layout )
def test_can_delete_saved_report(self): report = self._create_saved_report(domain=self.domain, user_id=self.user._id) response = self.delete_config(self.domain, report._id) self.assertEqual(response.status_code, 200) with self.assertRaises(ResourceNotFound): ReportConfig.get(report._id)
def _create_saved_report(self, domain=None, user_id=None): config = ReportConfig(domain=domain or self.domain, owner_id=user_id or self.user._id, name='Test', description='Test Saved Report', report_slug='worker_activity', report_type='project_report') config.save() self.addCleanup(config.delete) return config
def test_other_admin_can_edit_shared_saved_report(self, *args): config1 = self.create_report_config( domain=self.DOMAIN, owner_id=self.admin_user._id, name='Name', description='', ) # Create ReportNotification as to make confi1 shared self.create_report_notification([config1], owner_id=self.admin_user._id) ReportConfig.shared_on_domain.clear(ReportConfig, self.DOMAIN, only_id=True) new_description = 'This is a description' post_data = { 'description': new_description, 'name': config1.name, '_id': config1._id, } self.log_user_in(self.other_admin_user.username) response = self.client.post( self.URL, json.dumps(post_data), content_type='application/json;charset=UTF-8', ) self.assertEqual(response.status_code, 200) updated_config = ReportConfig.get(config1._id) self.assertTrue(updated_config.description, new_description)
def test_non_admin_cannot_edit_other_shared_configs(self): config1 = self.create_report_config( domain=self.DOMAIN, owner_id=self.admin_user._id, name='Name', description='', ) post_data = { 'description': 'Malicious description', 'name': config1.name, '_id': config1._id, } self.log_user_in(self.non_admin_user.username) try: _response = self.client.post( self.URL, json.dumps(post_data), content_type='application/json;charset=UTF-8', ) except Exception as e: self.assertTrue(e.__class__ == AssertionError) # Validate that config1 is untouched original_config = ReportConfig.get(config1._id) self.assertEqual(original_config.description, '')
def _get_context_for_saved_report(report_config): if report_config: report_config_data = report_config.to_json() report_config_data['filters'].update(report_config.get_date_range()) return report_config_data else: return ReportConfig.default()
def clean(self): name = self.cleaned_data['name'] _id = self.cleaned_data['_id'] user_configs = ReportConfig.by_domain_and_owner( self.domain, self.user_id) if not _id and name in [c.name for c in user_configs]: raise forms.ValidationError( "A saved report with the name '%(name)s' already exists." % { 'name': name, }) date_range = self.cleaned_data['date_range'] if (self.cleaned_data['report_type'] == ConfigurableReportView.prefix and not self.cleaned_data['datespan_slug']): self.cleaned_data['date_range'] = None else: if date_range == 'last7': self.cleaned_data['days'] = 7 elif date_range == 'last30': self.cleaned_data['days'] = 30 elif (date_range == 'lastn' and self.cleaned_data.get('days') is None and self.cleaned_data['report_type'] != ConfigurableReportView.prefix): raise forms.ValidationError( "Field 'days' was expected but not provided.") return self.cleaned_data
def clean(self): name = self.cleaned_data['name'] _id = self.cleaned_data['_id'] user_configs = ReportConfig.by_domain_and_owner(self.domain, self.user_id) if not _id and name in [c.name for c in user_configs]: raise forms.ValidationError( "A saved report with the name '%(name)s' already exists." % { 'name': name, } ) date_range = self.cleaned_data['date_range'] if ( self.cleaned_data['report_type'] == ConfigurableReportView.prefix and not self.cleaned_data['datespan_slug'] ): self.cleaned_data['date_range'] = None else: if date_range == 'last7': self.cleaned_data['days'] = 7 elif date_range == 'last30': self.cleaned_data['days'] = 30 elif (date_range == 'lastn' and self.cleaned_data.get('days') is None and self.cleaned_data['report_type'] != ConfigurableReportView.prefix): raise forms.ValidationError( "Field 'days' was expected but not provided." ) return self.cleaned_data
def test_saved_report_serialized_filters(self): report_config = ReportConfig.wrap({ "domain": 'saved-report-tests', "report_slug": "worker_activity", "owner_id": '0123456789', "report_type": ConfigurableReportView.prefix, "filters": { "date-start": "2020-01-01", "date-end": "2020-12-31", "test-filter": "test" } }) report_config._id = 'abc123' self.assertEqual(date, type(report_config.filters['date-start'])) self.assertEqual(date, type(report_config.filters['date-end'])) filters = report_config.serialized_filters expected_filters = { "date-start": "2020-01-01", "date-end": "2020-12-31", "test-filter": "test" } self.assertEqual(filters, expected_filters)
def test_admin_can_edit_normal_config(self, *args): config1 = self.create_report_config( domain=self.DOMAIN, owner_id=self.admin_user._id, name='Name', description='', ) new_description = 'This is a description' post_data = { 'description': new_description, 'name': config1.name, '_id': config1._id, } self.log_user_in(self.admin_user.username) response = self.client.post( self.URL, json.dumps(post_data), content_type='application/json;charset=UTF-8', ) self.assertEqual(response.status_code, 200) updated_config = ReportConfig.get(config1._id) self.assertTrue(updated_config.description, new_description)
def test_domain_has_shared_configs(self): config = ReportConfig(domain=self.DOMAIN, owner_id=self.OWNER_ID) config.save() self.addCleanup(config.delete) self._create_scheduled_report( domain=self.DOMAIN, owner_id=self.OWNER_ID, config_ids=[config._id], ) # Clear cached value ReportConfig.shared_on_domain.clear(ReportConfig, domain=self.DOMAIN) configs = list(ReportConfig.shared_on_domain(self.DOMAIN)) self.assertEqual(len(configs), 1) self.assertEqual(configs[0]._id, config._id)
def total(self): key = ["name", self.request.domain, self.request.couch_user._id] results = ReportConfig.get_db().view( 'reportconfig/configs_by_domain', include_docs=False, startkey=key, endkey=key+[{}], reduce=True, ).all() return results[0]['value'] if results else 0
def total(self): key = ["name", self.request.domain, self.request.couch_user._id] results = ReportConfig.get_db().view( 'reportconfig/configs_by_domain', include_docs=False, startkey=key, endkey=key + [{}], reduce=True, ).all() return results[0]['value'] if results else 0
def test_config_used_in_multiple_report_notifications(self): config = ReportConfig(domain=self.DOMAIN, owner_id=self.OWNER_ID) config.save() self.addCleanup(config.delete) self._create_scheduled_report( domain=self.DOMAIN, owner_id=self.OWNER_ID, config_ids=[config._id], ) self._create_scheduled_report( domain=self.DOMAIN, owner_id=self.OWNER_ID, config_ids=[config._id], ) configs = list(ReportConfig.shared_on_domain(self.DOMAIN)) self.assertEqual(len(configs), 1) self.assertEqual(configs[0]._id, config._id)
def test_no_shared_configs(self): config = self.create_report_config(self.DOMAIN, self.admin_user._id) configs = ReportConfig.by_domain_and_owner( self.DOMAIN, self.admin_user._id, include_shared=True, stale=False, ) self.assertEqual(len(configs), 1) self.assertEqual(configs[0]._id, config._id)
def _paginated_items(self, items_per_page, skip): reports = ReportConfig.by_domain_and_owner(self.request.domain, self.request.couch_user._id, limit=items_per_page, skip=skip) for report in reports: yield self._fmt_item(report.name, report.url, description="%(desc)s (%(date)s)" % { 'desc': report.description, 'date': report.date_description, }, full_name=report.full_name)
def saved_report_context_data(self): def _get_context_for_saved_report(report_config): if report_config: report_config_data = report_config.to_json() report_config_data['filters'].update(report_config.get_date_range()) return report_config_data else: return ReportConfig.default() saved_report_config_id = self.request.GET.get('config_id') saved_report_config = get_document_or_404(ReportConfig, self.domain, saved_report_config_id) \ if saved_report_config_id else None return { 'report_configs': [ _get_context_for_saved_report(saved_report) for saved_report in ReportConfig.by_domain_and_owner( self.domain, self.request.couch_user._id, report_slug=self.slug ) ], 'default_config': _get_context_for_saved_report(saved_report_config), 'datespan_filters': ReportConfig.datespan_filter_choices(self.datespan_filters, self.lang), }
def test_with_other_owner_shared_config(self): _config = self.create_report_config(self.DOMAIN, self.admin_user._id) config2 = self.create_report_config(self.DOMAIN, self.other_admin_user._id) self.create_report_notification([config2], owner_id=self.other_admin_user._id) configs = ReportConfig.by_domain_and_owner( self.DOMAIN, self.admin_user._id, include_shared=True, stale=False, ) self.assertEqual(len(configs), 2)
def handle(self, report_slug, *args, **options): kwargs = {'stale': settings.COUCH_STALE_QUERY} key = ["name slug"] result = cache_core.cached_view( ReportConfig.get_db(), "reportconfig/configs_by_domain", reduce=False, include_docs=False, startkey=key, endkey=key + [{}], **kwargs) for report_config in result: domain, owner_id, slug = report_config['key'][1:4] if slug == report_slug: print("%s, %s, %s" % ( domain, owner_id, slug ))
def _paginated_items(self, items_per_page, skip): reports = ReportConfig.by_domain_and_owner( self.request.domain, self.request.couch_user._id, limit=items_per_page, skip=skip ) for report in reports: yield self._fmt_item( report.name, report.url, description="%(desc)s (%(date)s)" % { 'desc': report.description, 'date': report.date_description, }, full_name=report.full_name )
def test_with_shared_config(self): config = self.create_report_config(self.DOMAIN, self.admin_user._id) self.create_report_notification([config], owner_id=self.admin_user._id) # Clear cached value ReportConfig.shared_on_domain.clear(ReportConfig, domain=self.DOMAIN) configs = ReportConfig.by_domain_and_owner( self.DOMAIN, self.admin_user._id, include_shared=True, stale=False, ) self.assertEqual(len(configs), 1) self.assertEqual(configs[0]._id, config._id)
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 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_email_report(self, recipient_emails, domain, report_slug, report_type, request_data, once, cleaned_data): """ Function invokes send_HTML_email to email the html text report. If the report is too large to fit into email then a download link is sent via email to download report :Parameter recipient_list: list of recipient to whom email is to be sent :Parameter domain: domain name :Parameter report_slug: report slug :Parameter report_type: type of the report :Parameter request_data: Dict containing request data :Parameter once boolean argument specifying whether the report is once off report or scheduled report :Parameter cleaned_data: Dict containing cleaned data from the submitted form """ from corehq.apps.reports.views import _render_report_configs, render_full_report_notification user_id = request_data['couch_user'] couch_user = CouchUser.get_by_user_id(user_id) mock_request = HttpRequest() mock_request.method = 'GET' mock_request.GET = request_data['GET'] config = ReportConfig() # see ReportConfig.query_string() object.__setattr__(config, '_id', 'dummy') config.name = _("Emailed report") config.report_type = report_type config.report_slug = report_slug config.owner_id = user_id config.domain = domain config.start_date = request_data['datespan'].startdate.date() if request_data['datespan'].enddate: config.date_range = 'range' config.end_date = request_data['datespan'].enddate.date() else: config.date_range = 'since' GET = dict(six.iterlists(request_data['GET'])) exclude = ['startdate', 'enddate', 'subject', 'send_to_owner', 'notes', 'recipient_emails'] filters = {} for field in GET: if field not in exclude: filters[field] = GET.get(field) config.filters = filters subject = cleaned_data['subject'] or _("Email report from CommCare HQ") try: content = _render_report_configs( mock_request, [config], domain, user_id, couch_user, True, lang=couch_user.language, notes=cleaned_data['notes'], once=once )[0] body = render_full_report_notification(None, content).content for recipient in recipient_emails: send_HTML_email(subject, recipient, body, email_from=settings.DEFAULT_FROM_EMAIL, smtp_exception_skip_list=LARGE_FILE_SIZE_ERROR_CODES) except Exception as er: notify_exception( None, message="Encountered error while generating report or sending email", details={ 'subject': subject, 'recipients': str(recipient_emails), 'error': er, } ) if getattr(er, 'smtp_code', None) in LARGE_FILE_SIZE_ERROR_CODES or type(er) == ESError: # If the email doesn't work because it is too large to fit in the HTML body, # send it as an excel attachment. report_state = { 'request': request_data, 'request_params': json_request(request_data['GET']), 'domain': domain, 'context': {}, } export_all_rows_task(config.report, report_state, recipient_list=recipient_emails) else: self.retry(exc=er)
def send_email_report(self, recipient_emails, domain, report_slug, report_type, request_data, once, cleaned_data): """ Function invokes send_HTML_email to email the html text report. If the report is too large to fit into email then a download link is sent via email to download report :Parameter recipient_list: list of recipient to whom email is to be sent :Parameter domain: domain name :Parameter report_slug: report slug :Parameter report_type: type of the report :Parameter request_data: Dict containing request data :Parameter once boolean argument specifying whether the report is once off report or scheduled report :Parameter cleaned_data: Dict containing cleaned data from the submitted form """ from corehq.apps.reports.views import _render_report_configs, render_full_report_notification user_id = request_data['couch_user'] couch_user = CouchUser.get_by_user_id(user_id) mock_request = HttpRequest() mock_request.method = 'GET' mock_request.GET = request_data['GET'] config = ReportConfig() # see ReportConfig.query_string() object.__setattr__(config, '_id', 'dummy') config.name = _("Emailed report") config.report_type = report_type config.report_slug = report_slug config.owner_id = user_id config.domain = domain config.start_date = request_data['datespan'].startdate.date() if request_data['datespan'].enddate: config.date_range = 'range' config.end_date = request_data['datespan'].enddate.date() else: config.date_range = 'since' GET = dict(six.iterlists(request_data['GET'])) exclude = [ 'startdate', 'enddate', 'subject', 'send_to_owner', 'notes', 'recipient_emails' ] filters = {} for field in GET: if field not in exclude: filters[field] = GET.get(field) config.filters = filters subject = cleaned_data['subject'] or _("Email report from CommCare HQ") content = _render_report_configs(mock_request, [config], domain, user_id, couch_user, True, lang=couch_user.language, notes=cleaned_data['notes'], once=once)[0] body = render_full_report_notification(None, content).content try: for recipient in recipient_emails: send_HTML_email( subject, recipient, body, email_from=settings.DEFAULT_FROM_EMAIL, smtp_exception_skip_list=LARGE_FILE_SIZE_ERROR_CODES) except Exception as er: if getattr(er, 'smtp_code', None) in LARGE_FILE_SIZE_ERROR_CODES: # If the smtp server rejects the email because of its large size. # Then sends the report download link in the email. report_state = { 'request': request_data, 'request_params': json_request(request_data['GET']), 'domain': domain, 'context': {}, } export_all_rows_task(config.report, report_state, recipient_list=recipient_emails) else: self.retry(exc=er)
def test_domain_does_not_have_shared_configs(self): self.assertEqual(len(ReportConfig.shared_on_domain(self.DOMAIN)), 0)
def create_report_config(self, domain, owner_id, **kwargs): rc = ReportConfig(domain=domain, owner_id=owner_id, **kwargs) rc.save() self.addCleanup(rc.delete) return rc