class PinpointBackendForm(BackendForm): project_id = TrimmedCharField( label=_("Project ID"), required=True ) region = TrimmedCharField( label=_("Region"), required=True ) access_key = TrimmedCharField( label=_("Access Key"), required=True ) secret_access_key = TrimmedCharField( label=_("Secret Access Key"), required=True ) @property def gateway_specific_fields(self): return crispy.Fieldset( _("Pinpoint Settings"), 'project_id', 'region', 'access_key', 'secret_access_key' )
class TelerivetBackendForm(BackendForm): api_key = TrimmedCharField( label=ugettext_lazy("API Key"), ) project_id = TrimmedCharField( label=ugettext_lazy("Project ID"), ) phone_id = TrimmedCharField( label=ugettext_lazy("Phone ID"), ) webhook_secret = TrimmedCharField( label=ugettext_lazy("Webhook Secret"), ) @property def gateway_specific_fields(self): return crispy.Fieldset( _("Telerivet (Android) Settings"), 'api_key', 'project_id', 'phone_id', 'webhook_secret', ) def clean_webhook_secret(self): # Circular import from corehq.messaging.smsbackends.telerivet.models import SQLTelerivetBackend value = self.cleaned_data['webhook_secret'] backend = SQLTelerivetBackend.by_webhook_secret(value) if backend and backend.pk != self._cchq_backend_id: raise ValidationError(_("Already in use.")) return value
class TelerivetOutgoingSMSForm(Form): api_key = TrimmedCharField(label=ugettext_lazy("API Key"), required=True) project_id = TrimmedCharField(label=ugettext_lazy("Project ID"), required=True) phone_id = TrimmedCharField(label=ugettext_lazy("Phone ID"), required=True) def __init__(self, *args, **kwargs): super(TelerivetOutgoingSMSForm, self).__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_class = 'form form-horizontal' self.helper.label_class = 'col-sm-2 col-md-1' self.helper.field_class = 'col-sm-4 col-md-3' self.helper.layout = Layout( Div( hqcrispy.B3MultiField( _("API Key"), Div(hqcrispy.MultiInlineField('api_key', ng_model='apiKey')), get_rmi_error_placeholder('apiKeyError'), ng_class="{'has-error': apiKeyError}"), hqcrispy.B3MultiField( _("Project ID"), Div( hqcrispy.MultiInlineField('project_id', ng_model='projectId')), get_rmi_error_placeholder('projectIdError'), ng_class="{'has-error': projectIdError}"), hqcrispy.B3MultiField( _("Phone ID"), Div( hqcrispy.MultiInlineField('phone_id', ng_model='phoneId')), get_rmi_error_placeholder('phoneIdError'), ng_class="{'has-error': phoneIdError}")))
class IvoryCoastMTNBackendForm(BackendForm): customer_id = TrimmedCharField( label=gettext_lazy("Customer ID"), required=True, ) username = TrimmedCharField( label=gettext_lazy("Username"), required=True, ) password = TrimmedCharField( label=gettext_lazy("Password"), required=True, ) sender_id = TrimmedCharField( label=gettext_lazy("Sender ID"), required=True, ) @property def gateway_specific_fields(self): return crispy.Fieldset( _("Ivory Coast MTN Settings"), 'customer_id', 'username', 'password', 'sender_id', )
class AppositBackendForm(BackendForm): application_id = TrimmedCharField( label=ugettext_lazy("Application Id"), ) application_token = TrimmedCharField( label=ugettext_lazy("Application Token"), ) from_number = TrimmedCharField( label=ugettext_lazy("From Number"), ) host = TrimmedCharField( label=ugettext_lazy("Host"), ) @property def gateway_specific_fields(self): return crispy.Fieldset( _("Apposit Settings"), 'application_id', 'application_token', 'from_number', 'host', ) def clean_host(self): value = self.cleaned_data.get("host") if is_url_or_host_banned(value): raise ValidationError(_("Invalid Host")) return value
class HttpBackendForm(BackendForm): url = TrimmedCharField(label=ugettext_noop("URL"), ) message_param = TrimmedCharField( label=ugettext_noop("Message Parameter"), ) number_param = TrimmedCharField( label=ugettext_noop("Phone Number Parameter"), ) include_plus = BooleanField( required=False, label=ugettext_noop("Include '+' in Phone Number"), ) method = ChoiceField( label=ugettext_noop("HTTP Request Method"), choices=(("GET", "GET"), ("POST", "POST")), ) additional_params = RecordListField( input_name="additional_params", label=ugettext_noop("Additional Parameters"), ) def __init__(self, *args, **kwargs): if "initial" in kwargs and "additional_params" in kwargs["initial"]: additional_params_dict = kwargs["initial"]["additional_params"] kwargs["initial"]["additional_params"] = [{ "name": key, "value": value } for key, value in additional_params_dict.items()] super(HttpBackendForm, self).__init__(*args, **kwargs) def clean_url(self): value = self.cleaned_data.get("url") if is_url_or_host_banned(value): raise ValidationError(_("Invalid URL")) return value def clean_additional_params(self): value = self.cleaned_data.get("additional_params") result = {} for pair in value: name = pair["name"].strip() value = pair["value"].strip() if name == "" or value == "": raise ValidationError("Please enter both name and value.") if name in result: raise ValidationError("Parameter name entered twice: %s" % name) result[name] = value return result @property def gateway_specific_fields(self): return crispy.Fieldset( _("HTTP Settings"), 'url', 'method', 'message_param', 'number_param', 'include_plus', 'additional_params', )
class InfobipBackendForm(BackendForm): account_sid = TrimmedCharField(label=_("Account SID"), ) auth_token = TrimmedCharField(label=_("Auth Token"), ) scenario_key = TrimmedCharField(label=_("Scenario Key"), ) @property def gateway_specific_fields(self): return crispy.Fieldset(_("Infobip Settings"), 'account_sid', 'auth_token', 'scenario_key')
class TrumpiaBackendForm(BackendForm): username = TrimmedCharField(label=gettext_lazy("Username"), required=True) api_key = TrimmedCharField(label=gettext_lazy("API Key"), required=True) @property def gateway_specific_fields(self): return crispy.Fieldset( _("Trumpia Settings"), 'username', 'api_key', )
class MegamobileBackendForm(BackendForm): api_account_name = TrimmedCharField(label=_("API Account Name"), ) source_identifier = TrimmedCharField(label=_("Source Identifier"), ) @property def gateway_specific_fields(self): return crispy.Fieldset( _("Megamobile Settings"), 'api_account_name', 'source_identifier', )
class GrapevineBackendForm(BackendForm): affiliate_code = TrimmedCharField(label=_("Affiliate Code"), ) authentication_code = TrimmedCharField(label=_("Authentication Code"), ) @property def gateway_specific_fields(self): return crispy.Fieldset( _("Grapevine Settings"), 'affiliate_code', 'authentication_code', )
class TwilioBackendForm(BackendForm, LoadBalancingBackendFormMixin): account_sid = TrimmedCharField(label=_("Account SID"), ) auth_token = TrimmedCharField(label=_("Auth Token"), ) @property def gateway_specific_fields(self): return crispy.Fieldset( _("Twilio Settings"), 'account_sid', 'auth_token', )
class TelerivetOutgoingSMSForm(Form): api_key = TrimmedCharField(label=ugettext_lazy("API Key"), required=True) project_id = TrimmedCharField(label=ugettext_lazy("Project ID"), required=True) phone_id = TrimmedCharField(label=ugettext_lazy("Phone ID"), required=True) def __init__(self, *args, **kwargs): super(TelerivetOutgoingSMSForm, self).__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_class = 'form form-horizontal' self.helper.label_class = 'col-sm-2 col-md-1' self.helper.field_class = 'col-sm-4 col-md-3' self.helper.layout = Layout( Div( hqcrispy.B3MultiField( _("API Key"), Div( hqcrispy.MultiInlineField( 'api_key', data_bind='value: apiKey', )), crispy.Div( css_class="help-block", data_bind="visible: apiKeyError, text: apiKeyError", ), data_bind="css: {'has-error': apiKeyError}", ), hqcrispy.B3MultiField( _("Project ID"), Div( hqcrispy.MultiInlineField( 'project_id', data_bind='value: projectId', )), crispy.Div( css_class="help-block", data_bind= "visible: projectIdError, text: projectIdError", ), data_bind="css: {'has-error': projectIdError}", ), hqcrispy.B3MultiField( _("Phone ID"), Div( hqcrispy.MultiInlineField( 'phone_id', data_bind='value: phoneId', )), crispy.Div( css_class="help-block", data_bind="visible: phoneIdError, text: phoneIdError", ), data_bind="css: {'has-error': phoneIdError}", )))
class UnicelBackendForm(BackendForm): username = TrimmedCharField(label=_("Username"), ) password = TrimmedCharField(label=_("Password"), ) sender = TrimmedCharField(label=_("Sender ID"), ) @property def gateway_specific_fields(self): return crispy.Fieldset( _("Unicel Settings"), 'username', 'password', 'sender', )
class PushBackendForm(BackendForm): channel = TrimmedCharField(label=ugettext_lazy("Channel"), ) service = TrimmedCharField(label=ugettext_lazy("Service"), ) password = TrimmedCharField(label=ugettext_lazy("Password"), ) @property def gateway_specific_fields(self): return crispy.Fieldset( _("Push Settings"), 'channel', 'service', 'password', )
class SMSGHBackendForm(BackendForm): from_number = TrimmedCharField(label=ugettext_lazy("From Number"), ) client_id = TrimmedCharField(label=ugettext_lazy("Client Id"), ) client_secret = TrimmedCharField(label=ugettext_lazy("Client Secret"), ) @property def gateway_specific_fields(self): return crispy.Fieldset( _("SMSGH Settings"), 'from_number', 'client_id', 'client_secret', )
class ICDSBackendForm(BackendForm): username = TrimmedCharField(label=_('Username'), ) pin = TrimmedCharField(label=_('PIN'), ) sender_id = TrimmedCharField(label=_('Sender ID'), ) @property def gateway_specific_fields(self): return crispy.Fieldset( _("ICDS Settings"), 'username', 'pin', 'sender_id', )
class FinalizeGatewaySetupForm(Form): YES = 'Y' NO = 'N' YES_NO_CHOICES = ( (YES, ugettext_lazy("Yes")), (NO, ugettext_lazy("No")), ) name = TrimmedCharField( label=ugettext_lazy("Name"), required=True ) set_as_default = ChoiceField( label=ugettext_lazy("Set as default gateway"), choices=YES_NO_CHOICES, required=True ) def __init__(self, *args, **kwargs): super(FinalizeGatewaySetupForm, self).__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_class = 'form form-horizontal' self.helper.label_class = 'col-sm-3 col-md-2' self.helper.field_class = 'col-sm-3 col-md-2' self.helper.layout = Layout( Div( hqcrispy.B3MultiField( _("Name"), Div( hqcrispy.MultiInlineField( 'name', ng_model='name' ) ), get_rmi_error_placeholder('nameError'), ng_class="{'has-error': nameError}" ), hqcrispy.B3MultiField( _("Set as default gateway"), Div( hqcrispy.MultiInlineField( 'set_as_default', ng_model='setAsDefault', style='margin-left: 0px;' ) ), get_rmi_error_placeholder('setAsDefaultError'), ng_class="{'has-error': setAsDefaultError}" ), FormActions( StrictButton( _("Complete"), id="id_create_backend", css_class='btn-success', ng_click='createBackend();' ) ) ) )
class TelerivetPhoneNumberForm(Form): test_phone_number = TrimmedCharField( required=True, label=ugettext_lazy("+ (Country Code) Phone Number")) def __init__(self, *args, **kwargs): super(TelerivetPhoneNumberForm, self).__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_class = 'form form-horizontal' self.helper.label_class = 'col-sm-2 col-md-1' self.helper.field_class = 'col-sm-4 col-md-3' self.helper.layout = Layout( Div( hqcrispy.B3MultiField( _("Test Phone Number"), Div( hqcrispy.MultiInlineField('test_phone_number', ng_model='testPhoneNumber')), get_rmi_error_placeholder('testPhoneNumberError'), Div( StrictButton(_("Send"), id='id_send_sms_button', css_class='btn btn-success', ng_click='sendTestSMS();')), ng_class="{'has-error': testPhoneNumberError}"))) def clean_test_phone_number(self): value = self.cleaned_data.get('test_phone_number') value = apply_leniency(value) validate_phone_number( value, error_message= _("Please enter digits only, in international format (country code and phone number)." )) return value
class SelfRegistrationForm(forms.Form): def __init__(self, *args, **kwargs): if 'domain' not in kwargs: raise Exception('Expected kwargs: domain') self.domain = kwargs.pop('domain') require_email = kwargs.pop('require_email', False) super(SelfRegistrationForm, self).__init__(*args, **kwargs) if require_email: self.fields['email'].required = True self.helper = FormHelper() self.helper.form_class = 'form-horizontal' self.helper.label_class = 'col-xs-4' self.helper.field_class = 'col-xs-8' layout_fields = [ crispy.Fieldset( _('Register'), crispy.Field('username'), crispy.Field('password'), crispy.Field('password2'), crispy.Field('email'), ), hqcrispy.FormActions( StrictButton( _('Register'), css_class='btn-primary', type='submit', )), ] self.helper.layout = crispy.Layout(*layout_fields) username = TrimmedCharField( required=True, label=ugettext_lazy('Username (create a username)'), ) password = forms.CharField( required=True, label=ugettext_lazy('Password (create a password)'), widget=PasswordInput(), ) password2 = forms.CharField( required=True, label=ugettext_lazy('Re-enter Password'), widget=PasswordInput(), ) email = forms.EmailField( required=False, label=ugettext_lazy('Email address'), ) def clean_username(self): return clean_mobile_worker_username(self.domain, self.cleaned_data.get('username')) def clean_password2(self): if self.cleaned_data.get('password') != self.cleaned_data.get( 'password2'): raise forms.ValidationError(_('Passwords do not match.'))
class FinalizeGatewaySetupForm(Form): YES = 'Y' NO = 'N' YES_NO_CHOICES = ( (YES, ugettext_lazy("Yes")), (NO, ugettext_lazy("No")), ) name = TrimmedCharField(label=ugettext_lazy("Name"), required=True) set_as_default = ChoiceField(label=ugettext_lazy("Set as default gateway"), choices=YES_NO_CHOICES, required=True) def __init__(self, *args, **kwargs): super(FinalizeGatewaySetupForm, self).__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_class = 'form form-horizontal' self.helper.label_class = 'col-sm-3 col-md-2' self.helper.field_class = 'col-sm-3 col-md-2' self.helper.layout = Layout( Div( hqcrispy.B3MultiField( _("Name"), Div( hqcrispy.MultiInlineField( 'name', data_bind='value: name', )), crispy.Div( css_class="help-block", data_bind="visible: nameError, text: nameError", ), data_bind="css: {'has-error': nameError}", ), hqcrispy.B3MultiField( _("Set as default gateway"), Div( hqcrispy.MultiInlineField( 'set_as_default', data_bind='value: setAsDefault', style='margin-left: 0px;')), crispy.Div( css_class="help-block", data_bind= "visible: setAsDefaultError, text: setAsDefaultError", ), data_bind="css: {'has-error': setAsDefaultError}", ), FormActions( StrictButton( "", id="id_create_backend", css_class='btn-primary', data_bind= "text: backendButtonText, click: createBackend, disable: creatingBackend," "css: {'btn-primary': !backendButtonError(), " "'btn-danger': backendButtonError()}", ))))
class TwilioBackendForm(BackendForm, LoadBalancingBackendFormMixin): account_sid = TrimmedCharField(label=_("Account SID"), ) auth_token = TrimmedCharField(label=_("Auth Token"), ) @property def gateway_specific_fields(self): return crispy.Fieldset( _("Twilio Settings"), 'account_sid', 'auth_token', ) def validate_phone_number(self, phone_number: str) -> None: from corehq.messaging.smsbackends.twilio.models import SQLTwilioBackend if not SQLTwilioBackend.phone_number_is_messaging_service_sid( phone_number): super().validate_phone_number(phone_number)
class TropoBackendForm(BackendForm): messaging_token = TrimmedCharField(label=_("Messaging Token")) @property def gateway_specific_fields(self): return crispy.Fieldset( _("Tropo Settings"), 'messaging_token', )
class AppositBackendForm(BackendForm): application_id = TrimmedCharField(label=gettext_lazy("Application Id"), ) application_token = TrimmedCharField( label=gettext_lazy("Application Token"), ) from_number = TrimmedCharField(label=gettext_lazy("From Number"), ) host = TrimmedCharField(label=gettext_lazy("Host"), ) @property def gateway_specific_fields(self): return crispy.Fieldset( _("Apposit Settings"), 'application_id', 'application_token', 'from_number', 'host', ) def clean_host(self): host = self.cleaned_data.get("host") return form_clean_url(host)
class VertexBackendForm(BackendForm): username = TrimmedCharField( label=ugettext_lazy("username"), required=True, ) password = TrimmedCharField( label=ugettext_lazy("password"), required=True, ) senderid = TrimmedCharField( label=ugettext_lazy("senderid"), required=True, ) @property def gateway_specific_fields(self): return crispy.Fieldset( _("Vertex Settings"), 'username', 'password', 'senderid', )
class StartEnterpriseBackendForm(BackendForm): username = TrimmedCharField( label=ugettext_lazy("Username"), required=True, ) password = TrimmedCharField( label=ugettext_lazy("Password"), required=True, ) sender_id = TrimmedCharField( label=ugettext_lazy("Sender Id"), required=True, ) @property def gateway_specific_fields(self): return crispy.Fieldset( _("Start Enterprise Settings"), 'username', 'password', 'sender_id', )
class InfobipBackendForm(BackendForm): account_sid = TrimmedCharField(label=_("Account SID"), required=True) auth_token = TrimmedCharField(label=_("Auth Token"), required=True) personalized_subdomain = TrimmedCharField( label=_("Personalized Subdomain"), required=True) scenario_key = TrimmedCharField( label=_("Scenario Key"), help_text= _("Enables sendimg messages via whatsapp, viber, line and voice channel with or " "without automatic failover to another channel according to the specific scenario." ), required=False) def clean_scenario_key(self): value = self.cleaned_data.get("scenario_key") or "" return value.strip() or None @property def gateway_specific_fields(self): return crispy.Fieldset(_("Infobip Settings"), 'account_sid', 'auth_token', 'personalized_subdomain', 'scenario_key')
class TelerivetPhoneNumberForm(Form): test_phone_number = TrimmedCharField( required=True, label=ugettext_lazy("+ (Country Code) Phone Number")) def __init__(self, *args, **kwargs): super(TelerivetPhoneNumberForm, self).__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_class = 'form form-horizontal' self.helper.label_class = 'col-sm-2 col-md-1' self.helper.field_class = 'col-sm-4 col-md-3' self.helper.layout = Layout( Div( hqcrispy.B3MultiField( _("Test Phone Number"), Div( hqcrispy.MultiInlineField( 'test_phone_number', data_bind='value: testPhoneNumber', )), crispy.Div( css_class="help-block", data_bind= "visible: testPhoneNumberError, text: testPhoneNumberError", ), Div( StrictButton( "", id='id_send_sms_button', css_class='btn', data_bind= "text: sendSmsButtonText, click: sendTestSMS, " "css: {'btn-primary': !sendSmsButtonError(), " "'btn-danger': sendSmsButtonError()}", )), data_bind="css: {'has-error': testPhoneNumberError}", ))) def clean_test_phone_number(self): value = self.cleaned_data.get('test_phone_number') value = apply_leniency(value) validate_phone_number( value, error_message= _("Please enter digits only, in international format (country code and phone number)." )) return value
class CaseUpdateRuleForm(forms.Form): # Prefix to avoid name collisions; this means all input # names in the HTML are prefixed with "rule-" prefix = "rule" name = TrimmedCharField( label=ugettext_lazy("Name"), required=True, ) def compute_initial(self, rule): return { 'name': rule.name, } def __init__(self, domain, *args, **kwargs): if 'initial' in kwargs: raise ValueError("Initial values are set by the form") self.is_system_admin = kwargs.pop('is_system_admin', False) rule = kwargs.pop('rule', None) if rule: kwargs['initial'] = self.compute_initial(rule) super(CaseUpdateRuleForm, self).__init__(*args, **kwargs) self.domain = domain self.helper = FormHelper() self.helper.label_class = 'col-xs-2 col-xs-offset-1' self.helper.field_class = 'col-xs-2' self.helper.form_tag = False self.helper.layout = Layout( Fieldset( _("Basic Information"), Field('name', data_bind='name'), ), )
class AirtelTCLBackendForm(BackendForm): host_and_port = TrimmedCharField( label=gettext_lazy("Host:Port"), required=True, ) user_name = TrimmedCharField( label=gettext_lazy("Username"), required=True, ) password = TrimmedCharField( label=gettext_lazy("Password"), required=True, ) sender_id = TrimmedCharField( label=gettext_lazy("Sender ID"), required=True, ) circle_name = TrimmedCharField( label=gettext_lazy("Circle Name"), required=True, ) campaign_name = TrimmedCharField( label=gettext_lazy("Campaign Name"), required=True, ) @property def gateway_specific_fields(self): return crispy.Fieldset( _("Airtel (through TCL) Settings"), 'host_and_port', 'user_name', 'password', 'sender_id', 'circle_name', 'campaign_name', ) def clean_host_and_port(self): host_and_port = self.cleaned_data.get('host_and_port') return form_clean_url(host_and_port)
class SettingsForm(Form): # General Settings use_default_sms_response = ChoiceField( required=False, label=ugettext_noop("Default SMS Response"), choices=ENABLED_DISABLED_CHOICES, ) default_sms_response = TrimmedCharField( required=False, label="", ) use_restricted_sms_times = ChoiceField( required=False, label=ugettext_noop("Send SMS on..."), choices=( (DISABLED, ugettext_noop("any day, at any time")), (ENABLED, ugettext_noop("only specific days and times")), ), ) restricted_sms_times_json = CharField( required=False, widget=forms.HiddenInput, ) send_to_duplicated_case_numbers = ChoiceField( required=False, label=ugettext_noop("Send Messages to Non-Unique Phone Numbers"), choices=ENABLED_DISABLED_CHOICES, ) # Chat Settings use_custom_case_username = ChoiceField( required=False, choices=DEFAULT_CUSTOM_CHOICES, ) custom_case_username = TrimmedCharField( required=False, label=ugettext_noop("Enter a Case Property"), ) use_custom_message_count_threshold = ChoiceField( required=False, choices=MESSAGE_COUNTER_CHOICES, ) custom_message_count_threshold = IntegerField( required=False, label=ugettext_noop("Enter a Number"), ) use_sms_conversation_times = ChoiceField( required=False, label=ugettext_noop("Delay Automated SMS"), choices=ENABLED_DISABLED_CHOICES, ) sms_conversation_times_json = CharField( required=False, widget=forms.HiddenInput, ) sms_conversation_length = ChoiceField( required=False, label=ugettext_noop("Conversation Duration"), choices=SMS_CONVERSATION_LENGTH_CHOICES, ) survey_traffic_option = ChoiceField( required=False, label=ugettext_noop("Survey Traffic"), choices=( (SHOW_ALL, ugettext_noop("Show all survey traffic")), (SHOW_INVALID, ugettext_noop("Hide all survey traffic except " "invalid responses")), (HIDE_ALL, ugettext_noop("Hide all survey traffic")), ), ) count_messages_as_read_by_anyone = ChoiceField( required=False, label=ugettext_noop("A Message is Read..."), choices=( (ENABLED, ugettext_noop("when it is read by anyone")), (DISABLED, ugettext_noop("only for the user that reads it")), ), ) use_custom_chat_template = ChoiceField( required=False, choices=DEFAULT_CUSTOM_CHOICES, ) custom_chat_template = TrimmedCharField( required=False, label=ugettext_noop("Enter Chat Template Identifier"), ) sms_case_registration_enabled = ChoiceField( required=False, choices=ENABLED_DISABLED_CHOICES, label=ugettext_noop("Case Self-Registration"), ) sms_case_registration_type = TrimmedCharField( required=False, label=ugettext_noop("Default Case Type"), ) sms_case_registration_owner_id = ChoiceField( required=False, label=ugettext_noop("Default Case Owner"), ) sms_case_registration_user_id = ChoiceField( required=False, label=ugettext_noop("Registration Submitter"), ) sms_mobile_worker_registration_enabled = ChoiceField( required=False, choices=ENABLED_DISABLED_CHOICES, label=ugettext_noop("SMS Mobile Worker Registration"), ) @property def section_general(self): fields = [ BootstrapMultiField( _("Default SMS Response"), InlineField( "use_default_sms_response", data_bind="value: use_default_sms_response", ), InlineField( "default_sms_response", css_class="input-xxlarge", placeholder=_("Enter Default Response"), data_bind="visible: showDefaultSMSResponse", ), help_bubble_text=_( "Enable this option to provide a " "default response when a user's incoming SMS does not " "answer an open survey or match a known keyword."), css_id="default-sms-response-group", ), FieldWithHelpBubble( "use_restricted_sms_times", data_bind="value: use_restricted_sms_times", help_bubble_text=_( "Use this option to limit the times " "that SMS messages can be sent to users. Messages that " "are sent outside these windows will remained queued " "and will go out as soon as another window opens up."), ), BootstrapMultiField( "", HiddenFieldWithErrors( "restricted_sms_times_json", data_bind="value: restricted_sms_times_json"), crispy.Div(data_bind="template: {" " name: 'ko-template-restricted-sms-times', " " data: $data" "}", ), data_bind="visible: showRestrictedSMSTimes", ), FieldWithHelpBubble( "send_to_duplicated_case_numbers", help_bubble_text=_( "Enabling this option will send " "outgoing-only messages to phone numbers registered " "with more than one mobile worker or case. SMS surveys " "and keywords will still only work for unique phone " "numbers in your project."), ), ] return crispy.Fieldset(_("General Settings"), *fields) @property def section_registration(self): fields = [ FieldWithHelpBubble( "sms_case_registration_enabled", help_bubble_text=_( "When this option is enabled, a person " "can send an SMS into the system saying 'join " "[project]', where [project] is your project " "space name, and the system will automatically " "create a case tied to that person's phone number."), data_bind="value: sms_case_registration_enabled", ), crispy.Div( FieldWithHelpBubble( "sms_case_registration_type", placeholder=_("Enter a Case Type"), help_bubble_text=_("Cases that self-register over SMS " "will be given this case type."), ), FieldWithHelpBubble( "sms_case_registration_owner_id", help_bubble_text=_( "Cases that self-register over SMS " "will be owned by this user or user group."), ), FieldWithHelpBubble( "sms_case_registration_user_id", help_bubble_text=_( "The form submission for a " "self-registration will belong to this user."), ), data_bind="visible: showRegistrationOptions", ), FieldWithHelpBubble( "sms_mobile_worker_registration_enabled", help_bubble_text=_( "When this option is enabled, a person " "can send an SMS into the system saying 'join " "[project] worker [username]' (where [project] is your " " project space and [username] is an optional username)" ", and the system will add them as a mobile worker."), ), ] return crispy.Fieldset(_("Registration Settings"), *fields) @property def section_chat(self): fields = [ BootstrapMultiField( _("Case Name Display"), InlineField( "use_custom_case_username", data_bind="value: use_custom_case_username", ), InlineField( "custom_case_username", css_class="input-large", data_bind="visible: showCustomCaseUsername", ), help_bubble_text=_( "By default, when chatting with a case, " "the chat window will use the case's \"name\" case " "property when displaying the case's name. To use a " "different case property, specify it here."), css_id="custom-case-username-group", ), BootstrapMultiField( _("Message Counter"), InlineField( "use_custom_message_count_threshold", data_bind="value: use_custom_message_count_threshold", ), InlineField( "custom_message_count_threshold", css_class="input-large", data_bind="visible: showCustomMessageCountThreshold", ), help_bubble_text=_( "The chat window can use a counter to keep " "track of how many messages are being sent and received " "and highlight that number after a certain threshold is " "reached. By default, the counter is disabled. To enable " "it, enter the desired threshold here."), css_id="custom-message-count-threshold-group", ), FieldWithHelpBubble( "use_sms_conversation_times", data_bind="value: use_sms_conversation_times", help_bubble_text=_( "When this option is enabled, the system " "will not send automated SMS to chat recipients when " "those recipients are in the middle of a conversation."), ), BootstrapMultiField( "", HiddenFieldWithErrors( "sms_conversation_times_json", data_bind="value: sms_conversation_times_json"), crispy.Div(data_bind="template: {" " name: 'ko-template-sms-conversation-times', " " data: $data" "}", ), data_bind="visible: showSMSConversationTimes", ), crispy.Div( FieldWithHelpBubble( "sms_conversation_length", help_bubble_text=_( "The number of minutes to wait " "after receiving an incoming SMS from a chat " "recipient before resuming automated SMS to that " "recipient."), ), data_bind="visible: showSMSConversationTimes", ), FieldWithHelpBubble( "survey_traffic_option", help_bubble_text=_( "This option allows you to hide a chat " "recipient's survey questions and responses from chat " "windows. There is also the option to show only invalid " "responses to questions in the chat window, which could " "be attempts to converse."), ), FieldWithHelpBubble( "count_messages_as_read_by_anyone", help_bubble_text=_( "The chat window will mark unread " "messages to the user viewing them. Use this option to " "control whether a message counts as being read if it " "is read by anyone, or if it counts as being read only " "to the user who reads it."), ), ] if self._cchq_is_previewer: fields.append( BootstrapMultiField( _("Chat Template"), InlineField( "use_custom_chat_template", data_bind="value: use_custom_chat_template", ), InlineField( "custom_chat_template", data_bind="visible: showCustomChatTemplate", ), help_bubble_text=_( "To use a custom template to render the " "chat window, enter it here."), css_id="custom-chat-template-group", )) attrs = {} if not self._cchq_is_previewer: attrs["style"] = "display: none;" return crispy.Fieldset(_("Chat Settings"), *fields, **attrs) def __init__(self, data=None, cchq_domain=None, cchq_is_previewer=False, *args, **kwargs): self._cchq_domain = cchq_domain self._cchq_is_previewer = cchq_is_previewer super(SettingsForm, self).__init__(data, *args, **kwargs) self.populate_dynamic_choices() self.helper = FormHelper() self.helper.form_class = "form form-horizontal" self.helper.layout = crispy.Layout( self.section_general, self.section_registration, self.section_chat, FormActions( StrictButton( _("Save"), type="submit", css_class="btn-primary", ), ), ) self.restricted_sms_times_widget_context = { "template_name": "ko-template-restricted-sms-times", "explanation_text": _("SMS will only be sent when any of the following is true:"), "ko_array_name": "restricted_sms_times", "remove_window_method": "$parent.removeRestrictedSMSTime", "add_window_method": "addRestrictedSMSTime", } self.sms_conversation_times_widget_context = { "template_name": "ko-template-sms-conversation-times", "explanation_text": _("Automated SMS will be suppressed during " "chat conversations when any of the following " "is true:"), "ko_array_name": "sms_conversation_times", "remove_window_method": "$parent.removeSMSConversationTime", "add_window_method": "addSMSConversationTime", } @property def current_values(self): current_values = {} for field_name in self.fields.keys(): value = self[field_name].value() if field_name in [ "restricted_sms_times_json", "sms_conversation_times_json" ]: if isinstance(value, basestring): current_values[field_name] = json.loads(value) else: current_values[field_name] = value else: current_values[field_name] = value return current_values def populate_dynamic_choices(self): groups = Group.get_case_sharing_groups(self._cchq_domain) users = CommCareUser.by_domain(self._cchq_domain) domain_group_choices = [(group._id, group.name) for group in groups] domain_user_choices = [(user._id, user.raw_username) for user in users] domain_owner_choices = domain_group_choices + domain_user_choices choose = [("", _("(Choose)"))] self.fields["sms_case_registration_owner_id"].choices = ( choose + domain_owner_choices) self.fields["sms_case_registration_user_id"].choices = ( choose + domain_user_choices) def _clean_dependent_field(self, bool_field, field): if self.cleaned_data.get(bool_field): value = self.cleaned_data.get(field, None) if not value: raise ValidationError(_("This field is required.")) return value else: return None def clean_use_default_sms_response(self): return self.cleaned_data.get("use_default_sms_response") == ENABLED def clean_default_sms_response(self): return self._clean_dependent_field("use_default_sms_response", "default_sms_response") def clean_use_custom_case_username(self): return self.cleaned_data.get("use_custom_case_username") == CUSTOM def clean_custom_case_username(self): return self._clean_dependent_field("use_custom_case_username", "custom_case_username") def clean_use_custom_message_count_threshold(self): return (self.cleaned_data.get("use_custom_message_count_threshold") == CUSTOM) def clean_custom_message_count_threshold(self): value = self._clean_dependent_field( "use_custom_message_count_threshold", "custom_message_count_threshold") if value is not None and value <= 0: raise ValidationError(_("Please enter a positive number")) return value def clean_use_custom_chat_template(self): if not self._cchq_is_previewer: return None return self.cleaned_data.get("use_custom_chat_template") == CUSTOM def clean_custom_chat_template(self): if not self._cchq_is_previewer: return None value = self._clean_dependent_field("use_custom_chat_template", "custom_chat_template") if value is not None and value not in settings.CUSTOM_CHAT_TEMPLATES: raise ValidationError(_("Unknown custom template identifier.")) return value def _clean_time_window_json(self, field_name): try: time_window_json = json.loads(self.cleaned_data.get(field_name)) except ValueError: raise ValidationError( _("An error has occurred. Please try again, " "and if the problem persists, please report an issue.")) result = [] for window in time_window_json: day = window.get("day") start_time = window.get("start_time") end_time = window.get("end_time") time_input_relationship = window.get("time_input_relationship") try: day = int(day) assert day >= -1 and day <= 6 except (ValueError, AssertionError): raise ValidationError(_("Invalid day chosen.")) if time_input_relationship == TIME_BEFORE: end_time = validate_time(end_time) result.append( DayTimeWindow( day=day, start_time=None, end_time=end_time, )) elif time_input_relationship == TIME_AFTER: start_time = validate_time(start_time) result.append( DayTimeWindow( day=day, start_time=start_time, end_time=None, )) else: start_time = validate_time(start_time) end_time = validate_time(end_time) if start_time >= end_time: raise ValidationError( _("End time must come after start " "time.")) result.append( DayTimeWindow( day=day, start_time=start_time, end_time=end_time, )) return result def clean_use_restricted_sms_times(self): return self.cleaned_data.get("use_restricted_sms_times") == ENABLED def clean_restricted_sms_times_json(self): if self.cleaned_data.get("use_restricted_sms_times"): return self._clean_time_window_json("restricted_sms_times_json") else: return [] def clean_use_sms_conversation_times(self): return self.cleaned_data.get("use_sms_conversation_times") == ENABLED def clean_sms_conversation_times_json(self): if self.cleaned_data.get("use_sms_conversation_times"): return self._clean_time_window_json("sms_conversation_times_json") else: return [] def clean_send_to_duplicated_case_numbers(self): return (self.cleaned_data.get("send_to_duplicated_case_numbers") == ENABLED) def clean_count_messages_as_read_by_anyone(self): return (self.cleaned_data.get("count_messages_as_read_by_anyone") == ENABLED) def clean_sms_case_registration_enabled(self): return ( self.cleaned_data.get("sms_case_registration_enabled") == ENABLED) def clean_sms_case_registration_type(self): return self._clean_dependent_field("sms_case_registration_enabled", "sms_case_registration_type") def _clean_registration_id_field(self, field_name): if self.cleaned_data.get("sms_case_registration_enabled"): value = self.cleaned_data.get(field_name) if not value: raise ValidationError(_("This field is required.")) # Otherwise, the ChoiceField automatically validates that it is # in the list that is dynamically populated in __init__ return value else: return None def clean_sms_case_registration_owner_id(self): return self._clean_registration_id_field( "sms_case_registration_owner_id") def clean_sms_case_registration_user_id(self): return self._clean_registration_id_field( "sms_case_registration_user_id") def clean_sms_mobile_worker_registration_enabled(self): return (self.cleaned_data.get("sms_mobile_worker_registration_enabled") == ENABLED) def clean_sms_conversation_length(self): # Just cast to int, the ChoiceField will validate that it is an integer return int(self.cleaned_data.get("sms_conversation_length"))