def get_copy_ajax_response(self, rule, copy_to_project_name): if not self.allow_copy: return JsonResponse({ 'status': 'error', 'error_msg': _("You do not have permission to copy alerts."), }) destination_project = Domain.get_by_name(copy_to_project_name) if ( destination_project is None or destination_project.is_snapshot or not self.request.couch_user.has_permission(copy_to_project_name, 'edit_data') ): return JsonResponse({ 'status': 'error', 'error_msg': _("Destination project not found."), }) # Use the same copy method as the exchange uses, which will # return None if the rule can't be copied, otherwise will # copy the rule as inactive. copied_rule = rule.copy_conditional_alert(copy_to_project_name, allow_custom_references=True) if copied_rule is None: return JsonResponse({ 'status': 'error', 'error_msg': _("This rule includes references that cannot be copied."), }) initiate_messaging_rule_run(copied_rule.domain, copied_rule.pk) return JsonResponse({ 'status': 'success', 'rule': self._format_rule_for_json(rule), })
def get_activate_ajax_response(self, active_flag, rule): """ When we deactivate a conditional alert from the UI, we are only deactivating the schedule that sends the content. The rule itself stays active. This is because we want to be keeping all the schedule instances up to date (though inactive), so that if the schedule is reactivated, we don't send a large quantity of stale messages. """ with transaction.atomic(): schedule = rule.get_messaging_rule_schedule() if active_flag and not self.can_use_inbound_sms and schedule.memoized_uses_sms_survey: return HttpResponseBadRequest( "Cannot create or edit survey reminders because subscription " "does not have access to inbound SMS") if active_flag and (rule.references_parent_case or schedule.references_parent_case): return HttpResponseBadRequest( "Cannot reactivate alerts that reference parent case properties" ) schedule.active = active_flag schedule.save() initiate_messaging_rule_run(self.domain, rule.pk) return JsonResponse({'status': 'success'})
def get_copy_ajax_response(self, rule, copy_to_project_name): if not self.allow_copy: return JsonResponse({ 'status': 'error', 'error_msg': _("You do not have permission to copy alerts."), }) destination_project = Domain.get_by_name(copy_to_project_name) if (destination_project is None or destination_project.is_snapshot or not self.request.couch_user.has_permission( copy_to_project_name, 'edit_data')): return JsonResponse({ 'status': 'error', 'error_msg': _("Destination project not found."), }) # Use the same copy method as the exchange uses, which will # return None if the rule can't be copied, otherwise will # copy the rule as inactive. copied_rule = rule.copy_conditional_alert(copy_to_project_name, allow_custom_references=True) if copied_rule is None: return JsonResponse({ 'status': 'error', 'error_msg': _("This rule includes references that cannot be copied."), }) initiate_messaging_rule_run(copied_rule.domain, copied_rule.pk) return JsonResponse({'status': 'success'})
def get_restart_ajax_response(self, rule): helper = MessagingRuleProgressHelper(rule.pk) if self.limit_rule_restarts and helper.rule_initiation_key_is_set(): minutes_remaining = helper.rule_initiation_key_minutes_remaining() return JsonResponse({'status': 'error', 'minutes_remaining': minutes_remaining}) initiate_messaging_rule_run(rule.domain, rule.pk) return JsonResponse({'status': 'success'})
def get_restart_ajax_response(self, rule): helper = MessagingRuleProgressHelper(rule.pk) if self.limit_rule_restarts and helper.rule_initiation_key_is_set(): minutes_remaining = helper.rule_initiation_key_minutes_remaining() return JsonResponse({'status': 'error', 'minutes_remaining': minutes_remaining}) initiate_messaging_rule_run(rule.domain, rule.pk) return JsonResponse({ 'status': 'success', 'rule': self._format_rule_for_json(rule), })
def post(self, request, *args, **kwargs): if self.async_response is not None: return self.async_response basic_info_form_valid = self.basic_info_form.is_valid() criteria_form_valid = self.criteria_form.is_valid() schedule_form_valid = self.schedule_form.is_valid() if self.read_only_mode: # Don't allow making changes to rules that have custom # criteria/actions unless the user has permission to return HttpResponseBadRequest() if basic_info_form_valid and criteria_form_valid and schedule_form_valid: if not self.is_system_admin and ( self.criteria_form.requires_system_admin_to_save or self.schedule_form.requires_system_admin_to_save ): # Don't allow adding custom criteria/actions to rules # unless the user has permission to return HttpResponseBadRequest() if not self.can_use_inbound_sms and self.schedule_form.uses_sms_survey: return HttpResponseBadRequest( "Cannot create or edit survey reminders because subscription " "does not have access to inbound SMS" ) with transaction.atomic(): if self.rule: rule = self.rule else: rule = AutomaticUpdateRule( domain=self.domain, active=True, workflow=AutomaticUpdateRule.WORKFLOW_SCHEDULING, ) rule.name = self.basic_info_form.cleaned_data['name'] self.criteria_form.save_criteria(rule) self.schedule_form.save_rule_action_and_schedule(rule) if not ( self.new_reminders_migrator and self.schedule_form.cleaned_data['skip_running_rule_post_save'] ): initiate_messaging_rule_run(rule.domain, rule.pk) return HttpResponseRedirect(reverse(ConditionalAlertListView.urlname, args=[self.domain])) return self.get(request, *args, **kwargs)
def get_activate_ajax_response(self, active_flag, rule): """ When we deactivate a conditional alert from the UI, we are only deactivating the schedule that sends the content. The rule itself stays active. This is because we want to be keeping all the schedule instances up to date (though inactive), so that if the schedule is reactivated, we don't send a large quantity of stale messages. """ with transaction.atomic(): schedule = rule.get_messaging_rule_schedule() schedule.active = active_flag schedule.save() initiate_messaging_rule_run(self.domain, rule.pk) return HttpResponse()
def post(self, request, *args, **kwargs): if self.async_response is not None: return self.async_response basic_info_form_valid = self.basic_info_form.is_valid() criteria_form_valid = self.criteria_form.is_valid() schedule_form_valid = self.schedule_form.is_valid() if self.read_only_mode: # Don't allow making changes to rules that have custom # criteria/actions unless the user has permission to return HttpResponseBadRequest() if basic_info_form_valid and criteria_form_valid and schedule_form_valid: if not self.is_system_admin and ( self.criteria_form.requires_system_admin_to_save or self.schedule_form.requires_system_admin_to_save ): # Don't allow adding custom criteria/actions to rules # unless the user has permission to return HttpResponseBadRequest() if not self.can_use_inbound_sms and self.schedule_form.uses_sms_survey: return HttpResponseBadRequest( "Cannot create or edit survey reminders because subscription " "does not have access to inbound SMS" ) with transaction.atomic(): if self.rule: rule = self.rule else: rule = AutomaticUpdateRule( domain=self.domain, active=True, workflow=AutomaticUpdateRule.WORKFLOW_SCHEDULING, ) rule.name = self.basic_info_form.cleaned_data['name'] self.criteria_form.save_criteria(rule) self.schedule_form.save_rule_action_and_schedule(rule) initiate_messaging_rule_run(rule.domain, rule.pk) return HttpResponseRedirect(reverse(ConditionalAlertListView.urlname, args=[self.domain])) return self.get(request, *args, **kwargs)
def _deactivate_schedules(domain, survey_only=False): """ The subscription changes are executed within a transaction, so we need to make sure any celery tasks only get started after the transaction commits. """ from corehq.messaging.tasks import initiate_messaging_rule_run for broadcast in _get_active_immediate_broadcasts(domain, survey_only=survey_only): AlertSchedule.objects.filter(schedule_id=broadcast.schedule_id).update( active=False) # We have to generate this function outside of this context otherwise it will be # bound to the name broadcast which changes over the course of iteration transaction.on_commit( get_refresh_alert_schedule_instances_call(broadcast)) for broadcast in _get_active_scheduled_broadcasts(domain, survey_only=survey_only): TimedSchedule.objects.filter(schedule_id=broadcast.schedule_id).update( active=False) # We have to generate this function outside of this context otherwise it will be # bound to the name broadcast which changes over the course of iteration transaction.on_commit( get_refresh_timed_schedule_instances_call(broadcast)) for rule in _get_active_scheduling_rules(domain, survey_only=survey_only): """ Deactivating a scheduling rule involves only deactivating the schedule, and leaving the rule active. See ConditionalAlertListView.get_activate_ajax_response for more information. """ with transaction.atomic(): schedule = rule.get_messaging_rule_schedule() if isinstance(schedule, AlertSchedule): AlertSchedule.objects.filter( schedule_id=schedule.schedule_id).update(active=False) elif isinstance(schedule, TimedSchedule): TimedSchedule.objects.filter( schedule_id=schedule.schedule_id).update(active=False) else: raise TypeError("Expected AlertSchedule or TimedSchedule") initiate_messaging_rule_run(rule)
def get_activate_ajax_response(self, active_flag, rule): """ When we deactivate a conditional alert from the UI, we are only deactivating the schedule that sends the content. The rule itself stays active. This is because we want to be keeping all the schedule instances up to date (though inactive), so that if the schedule is reactivated, we don't send a large quantity of stale messages. """ with transaction.atomic(): schedule = rule.get_messaging_rule_schedule() if active_flag and not self.can_use_inbound_sms and schedule.memoized_uses_sms_survey: return HttpResponseBadRequest( "Cannot create or edit survey reminders because subscription " "does not have access to inbound SMS" ) if active_flag and (rule.references_parent_case or schedule.references_parent_case): return HttpResponseBadRequest( "Cannot reactivate alerts that reference parent case properties" ) if active_flag and (schedule.memoized_uses_ivr_survey or schedule.memoized_uses_sms_callback): return HttpResponseBadRequest( "Cannot activate alerts which use IVR or SMS Callback use cases since they " "are no longer supported." ) schedule.active = active_flag schedule.save() initiate_messaging_rule_run(self.domain, rule.pk) return JsonResponse({ 'status': 'success', 'rule': self._format_rule_for_json(rule), })
def _deactivate_schedules(domain, survey_only=False): """ The subscription changes are executed within a transaction, so we need to make sure any celery tasks only get started after the transaction commits. """ from corehq.messaging.tasks import initiate_messaging_rule_run for broadcast in _get_active_immediate_broadcasts(domain, survey_only=survey_only): AlertSchedule.objects.filter(schedule_id=broadcast.schedule_id).update(active=False) # We have to generate this function outside of this context otherwise it will be # bound to the name broadcast which changes over the course of iteration transaction.on_commit(get_refresh_alert_schedule_instances_call(broadcast)) for broadcast in _get_active_scheduled_broadcasts(domain, survey_only=survey_only): TimedSchedule.objects.filter(schedule_id=broadcast.schedule_id).update(active=False) # We have to generate this function outside of this context otherwise it will be # bound to the name broadcast which changes over the course of iteration transaction.on_commit(get_refresh_timed_schedule_instances_call(broadcast)) for rule in _get_active_scheduling_rules(domain, survey_only=survey_only): """ Deactivating a scheduling rule involves only deactivating the schedule, and leaving the rule active. See ConditionalAlertListView.get_activate_ajax_response for more information. """ with transaction.atomic(): schedule = rule.get_messaging_rule_schedule() if isinstance(schedule, AlertSchedule): AlertSchedule.objects.filter(schedule_id=schedule.schedule_id).update(active=False) elif isinstance(schedule, TimedSchedule): TimedSchedule.objects.filter(schedule_id=schedule.schedule_id).update(active=False) else: raise TypeError("Expected AlertSchedule or TimedSchedule") initiate_messaging_rule_run(domain.name, rule.pk)
def handle(self, domain, filename, **options): domain_obj = Domain.get_by_name(domain) if domain_obj is None: raise CommandError("Project space '%s' not found" % domain) if not project_is_on_new_reminders(domain_obj): raise CommandError( "Project space '%s' does not have new reminders enabled" % domain) json_rules = [] with open_for_json_read(filename) as f: for line in f: json_rules.append(json.loads(line)) print("Importing %s rules..." % len(json_rules)) rules = [] with transaction.atomic(): for entry in json_rules: json_rule = SimpleSchedulingRule(entry['rule']) schedule_type = entry['schedule']['schedule_type'] if schedule_type == SIMPLE_SMS_DAILY_SCHEDULE_WITH_TIME: json_schedule = SimpleSMSDailyScheduleWithTime( entry['schedule']) schedule = TimedSchedule.create_simple_daily_schedule( domain, TimedEvent(time=json_schedule.time), SMSContent(message=json_schedule.message), total_iterations=json_schedule.total_iterations, start_offset=json_schedule.start_offset, start_day_of_week=json_schedule.start_day_of_week, extra_options=json_schedule.extra_options.to_json(), repeat_every=json_schedule.repeat_every, ) elif schedule_type == SIMPLE_SMS_ALERT_SCHEDULE: json_schedule = SimpleSMSAlertSchedule(entry['schedule']) schedule = AlertSchedule.create_simple_alert( domain, SMSContent(message=json_schedule.message), extra_options=json_schedule.extra_options.to_json(), ) else: raise CommandError("Unexpected schedule_type: %s" % schedule_type) rule = AutomaticUpdateRule.objects.create( domain=domain, name=json_rule.name, case_type=json_rule.case_type, active=True, filter_on_server_modified=False, workflow=AutomaticUpdateRule.WORKFLOW_SCHEDULING, ) for criterion in json_rule.criteria: rule.add_criteria( MatchPropertyDefinition, property_name=criterion.property_name, property_value=criterion.property_value, match_type=criterion.match_type, ) rule.add_action( CreateScheduleInstanceActionDefinition, alert_schedule_id=schedule.schedule_id if isinstance( schedule, AlertSchedule) else None, timed_schedule_id=schedule.schedule_id if isinstance( schedule, TimedSchedule) else None, recipients=json_rule.recipients, reset_case_property_name=json_rule. reset_case_property_name, start_date_case_property=json_rule. start_date_case_property, specific_start_date=json_rule.specific_start_date, scheduler_module_info=json_rule.scheduler_module_info. to_json(), ) rules.append(rule) print("Import complete. Starting instance refresh tasks...") for rule in rules: initiate_messaging_rule_run(rule.domain, rule.pk) print("Done.")
def refresh_schedule_instances(self): initiate_messaging_rule_run(self.rule.domain, self.rule.pk)
def upload(self, workbook): self.msgs = [] success_count = 0 worksheet = workbook.get_worksheet(title=self.sheet_name) errors = self.get_worksheet_errors(worksheet) if errors: return errors # Most rules are represented by a single row, but rules with custom schedules have one row per event. # Read through the worksheet, grouping rows by rule id and caching rule definitions. condensed_rows = defaultdict(list) rules_by_id = {} for index, row in enumerate(worksheet, start=2): # one-indexed, plus header row if not row.get('id', None): self.msgs.append( (messages.error, _("Row {index} in '{sheet_name}' sheet is missing " "an id.").format(index=index, sheet_name=self.sheet_name))) continue if row['id'] in condensed_rows: # This is the 2nd (or 3rd, 4th, ...) row for a rule we've already seen condensed_rows[row['id']].append(row) continue try: rule = AutomaticUpdateRule.objects.get( pk=row['id'], domain=self.domain, workflow=AutomaticUpdateRule.WORKFLOW_SCHEDULING, deleted=False, ) except AutomaticUpdateRule.DoesNotExist: self.msgs.append(( messages.error, _("""Could not find rule for row {index} in '{sheet_name}' sheet, """ """with id {id}""").format(index=index, id=row['id'], sheet_name=self.sheet_name))) continue if rule.locked_for_editing: self.msgs.append(( messages.error, _("Row {index} in '{sheet_name}' sheet, with rule id {id}, " "is currently processing and cannot be updated.").format( index=index, id=row['id'], sheet_name=self.sheet_name))) continue if not isinstance( rule.get_messaging_rule_schedule().memoized_events[0]. content, SMSContent): self.msgs.append(( messages.error, _("Row {index} in '{sheet_name}' sheet, with rule id {id}, " "does not use SMS content.").format( index=index, id=row['id'], sheet_name=self.sheet_name))) continue rules_by_id[row['id']] = rule condensed_rows[row['id']].append(row) # Update the condensed set of rules for rule_id, rows in condensed_rows.items(): rule = rules_by_id[rule_id] schedule = rule.get_messaging_rule_schedule() send_frequency = ScheduleForm.get_send_frequency_by_ui_type( schedule.ui_type) if send_frequency in (ScheduleForm.SEND_CUSTOM_DAILY, ScheduleForm.SEND_CUSTOM_IMMEDIATE): # Check that user provided one row for each event in the custom schedule all_events = rule.get_messaging_rule_schedule().memoized_events expected = len( [e for e in all_events if self.event_is_relevant(e)]) actual = len(rows) if expected != actual and actual != len(all_events): self.msgs.append(( messages.error, _("Could not update rule with id {id} in '{sheet_name}' " "sheet: expected {expected} row(s) but found " "{actual}.").format(id=rule.id, sheet_name=self.sheet_name, expected=expected, actual=actual))) continue with transaction.atomic(): try: dirty = self.update_rule(rule, rows) except RuleUpdateError as e: self.msgs.append(( messages.error, _("Error updating rule with id {id} in '{sheet_name}' " "sheet: {detail}").format(id=rule.id, sheet_name=self.sheet_name, detail=str(e)))) continue if dirty: rule.save() initiate_messaging_rule_run(rule) success_count += 1 self.msgs.append( (messages.success, _("Updated {count} rule(s) in '{sheet_name}' sheet").format( count=success_count, sheet_name=self.sheet_name))) return self.msgs
def handle(self, domain, filename, **options): domain_obj = Domain.get_by_name(domain) if domain_obj is None: raise CommandError("Project space '%s' not found" % domain) json_rules = [] with open_for_json_read(filename) as f: for line in f: json_rules.append(json.loads(line)) print("Importing %s rules..." % len(json_rules)) rules = [] with transaction.atomic(): for entry in json_rules: json_rule = SimpleSchedulingRule(entry['rule']) schedule_type = entry['schedule']['schedule_type'] if schedule_type == SIMPLE_SMS_DAILY_SCHEDULE_WITH_TIME: json_schedule = SimpleSMSDailyScheduleWithTime(entry['schedule']) schedule = TimedSchedule.create_simple_daily_schedule( domain, TimedEvent(time=json_schedule.time), SMSContent(message=json_schedule.message), total_iterations=json_schedule.total_iterations, start_offset=json_schedule.start_offset, start_day_of_week=json_schedule.start_day_of_week, extra_options=json_schedule.extra_options.to_json(), repeat_every=json_schedule.repeat_every, ) elif schedule_type == SIMPLE_SMS_ALERT_SCHEDULE: json_schedule = SimpleSMSAlertSchedule(entry['schedule']) schedule = AlertSchedule.create_simple_alert( domain, SMSContent(message=json_schedule.message), extra_options=json_schedule.extra_options.to_json(), ) else: raise CommandError("Unexpected schedule_type: %s" % schedule_type) rule = AutomaticUpdateRule.objects.create( domain=domain, name=json_rule.name, case_type=json_rule.case_type, active=True, filter_on_server_modified=False, workflow=AutomaticUpdateRule.WORKFLOW_SCHEDULING, ) for criterion in json_rule.criteria: rule.add_criteria( MatchPropertyDefinition, property_name=criterion.property_name, property_value=criterion.property_value, match_type=criterion.match_type, ) rule.add_action( CreateScheduleInstanceActionDefinition, alert_schedule_id=schedule.schedule_id if isinstance(schedule, AlertSchedule) else None, timed_schedule_id=schedule.schedule_id if isinstance(schedule, TimedSchedule) else None, recipients=json_rule.recipients, reset_case_property_name=json_rule.reset_case_property_name, start_date_case_property=json_rule.start_date_case_property, specific_start_date=json_rule.specific_start_date, scheduler_module_info=json_rule.scheduler_module_info.to_json(), ) rules.append(rule) print("Import complete. Starting instance refresh tasks...") for rule in rules: initiate_messaging_rule_run(rule.domain, rule.pk) print("Done.")