def paginated_list(self): all_backends = [] all_backends += SMSBackend.view( "sms/backend_by_domain", classes=self.backend_classes, startkey=[self.domain], endkey=[self.domain, {}], reduce=False, include_docs=True, ).all() all_backends += SMSBackend.view( "sms/global_backends", classes=self.backend_classes, reduce=False, include_docs=True ).all() if len(all_backends) > 0 and not self.domain_object.default_sms_backend_id: yield { "itemData": {"id": "nodefault", "name": "Automatic Choose", "status": "DEFAULT"}, "template": "gateway-automatic-template", } elif self.domain_object.default_sms_backend_id: default_backend = SMSBackend.get(self.domain_object.default_sms_backend_id) yield {"itemData": self._fmt_backend_data(default_backend), "template": "gateway-default-template"} for backend in all_backends: if not backend._id == self.domain_object.default_sms_backend_id: yield {"itemData": self._fmt_backend_data(backend), "template": "gateway-template"}
def clean_name(self): value = self.cleaned_data.get("name") if value is not None: value = value.strip().upper() if value is None or value == "": raise ValidationError(_("This field is required.")) if re.compile("\s").search(value) is not None: raise ValidationError(_("Name may not contain any spaces.")) backend_classes = get_available_backends() if self._cchq_domain is None: # Ensure name is not duplicated among other global backends backend = SMSBackend.view( "sms/global_backends", classes=backend_classes, key=[value], include_docs=True, reduce=False ).one() else: # Ensure name is not duplicated among other backends owned by this domain backend = SMSBackend.view("sms/backend_by_owner_domain", classes=backend_classes, key=[self._cchq_domain, value], include_docs=True).one() if backend is not None and backend._id != self._cchq_backend_id: raise ValidationError(_("Name is already in use.")) return value
def clean_name(self): value = self.cleaned_data.get("name") if value is not None: value = value.strip().upper() if value is None or value == "": raise ValidationError(_("This field is required.")) if re.compile("\s").search(value) is not None: raise ValidationError(_("Name may not contain any spaces.")) backend_classes = get_available_backends() if self._cchq_domain is None: # Ensure name is not duplicated among other global backends backend = SMSBackend.view("sms/global_backends", classes=backend_classes, key=[value], include_docs=True, reduce=False).one() else: # Ensure name is not duplicated among other backends owned by this domain backend = SMSBackend.view("sms/backend_by_owner_domain", classes=backend_classes, key=[self._cchq_domain, value], include_docs=True).one() if backend is not None and backend._id != self._cchq_backend_id: raise ValidationError(_("Name is already in use.")) return value
def total(self): domain_backends = ( SMSBackend.get_db() .view("sms/backend_by_domain", startkey=[self.domain], endkey=[self.domain, {}], reduce=True) .first() or {} ) global_backends = SMSBackend.get_db().view("sms/global_backends", reduce=True).first() or {} return domain_backends.get("value", 0) + global_backends.get("value", 0)
def arbitrary_backend_ids(): backend_ids = {} for backend in get_available_backends().values(): backend_instance = data_gen.arbitrary_unique_name("back") backend_ids[backend.get_api_id()] = backend_instance sms_backend = SMSBackend() sms_backend._id = backend_instance sms_backend.is_global = True sms_backend.save() return backend_ids
def _list_backends(request, show_global=False, domain=None): backend_classes = get_available_backends() backends = [] editable_backend_ids = [] default_sms_backend_id = None if not show_global: domain_obj = Domain.get_by_name(domain, strict=True) raw_backends = [] if not show_global: raw_backends += SMSBackend.view( "sms/backend_by_domain", reduce=False, classes=backend_classes, startkey=[domain], endkey=[domain, {}], include_docs=True, ).all() if len(raw_backends) > 0 and domain_obj.default_sms_backend_id in [None, ""]: messages.error( request, _( "WARNING: You have not specified a default SMS connection. By default, the system will automatically select one of the SMS connections owned by the system when sending sms." ), ) raw_backends += SMSBackend.view( "sms/global_backends", classes=backend_classes, include_docs=True, reduce=False ).all() for backend in raw_backends: backends.append(backend_classes[backend.doc_type].wrap(backend.to_json())) if show_global or (not backend.is_global and backend.domain == domain): editable_backend_ids.append(backend._id) if not show_global and domain_obj.default_sms_backend_id == backend._id: default_sms_backend_id = backend._id instantiable_backends = [] for name, klass in backend_classes.items(): try: assert ( request.couch_user.is_superuser or show_global or name == "TelerivetBackend" ) # TODO: Remove this once domain-specific billing is sorted out klass.get_generic_name() klass.get_form_class() instantiable_backends.append((name, klass)) except Exception: pass instantiable_backends.sort(key=lambda t: t[0]) context = { "show_global": show_global, "domain": domain, "backends": backends, "editable_backend_ids": editable_backend_ids, "default_sms_backend_id": default_sms_backend_id, "instantiable_backends": instantiable_backends, } return render(request, "sms/list_backends.html", context)
def __init__(self, domain, *args, **kwargs): super(SMSRateCalculatorForm, self).__init__(*args, **kwargs) backends = SMSBackend.view( "sms/backend_by_domain", startkey=[domain], endkey=[domain, {}], reduce=False, include_docs=True, ).all() backends.extend( SMSBackend.view( 'sms/global_backends', reduce=False, include_docs=True, ).all()) def _get_backend_info(backend): try: api_id = " (%s)" % get_backend_by_class_name( backend.doc_type).get_api_id() except AttributeError: api_id = "" return backend._id, "%s%s" % (backend.name, api_id) backends = [_get_backend_info(g) for g in backends] self.fields['gateway'].choices = backends self.helper = FormHelper() self.helper.form_class = "form-horizontal" self.helper.layout = crispy.Layout( crispy.Field( 'gateway', data_bind="value: gateway, events: {change: clearSelect2}", css_class="input-xxlarge", ), crispy.Field( 'direction', data_bind="value: direction, " "event: {change: clearSelect2}", ), crispy.Field( 'country_code', css_class="input-xxlarge", data_bind="value: select2CountryCode.value, " "event: {change: updateRate}", placeholder=_("Please Select a Country Code"), ), )
def get_active_dimagi_owned_gateway_projects(domains, datespan, interval, datefield='date'): """ Returns list of timestamps and how many domains used a Dimagi owned gateway in the past thrity days before each timestamp """ dimagi_owned_backend = SMSBackend.view( "sms/global_backends", reduce=False ).all() dimagi_owned_backend_ids = [x['id'] for x in dimagi_owned_backend] backend_filter = {'terms': {'backend_id': dimagi_owned_backend_ids}} histo_data = [] for timestamp in daterange(interval, datespan.startdate, datespan.enddate): t = timestamp f = timestamp - relativedelta(days=30) sms_query = get_sms_query(f, t, 'domains', 'domain', domains, DOMAIN_COUNT_UPPER_BOUND) d = sms_query.filter(backend_filter).run() c = len(d.facet('domains', 'terms')) if c > 0: histo_data.append(get_data_point(c, timestamp)) return format_return_data(histo_data, 0, datespan)
def country_code_response(self): gateway = self.data.get('gateway') try: backend = SMSBackend.get(gateway) backend_api_id = get_backend_by_class_name( backend.doc_type).get_api_id() except Exception: return [] direction = self.data.get('direction') criteria_query = SmsGatewayFeeCriteria.objects.filter( direction=direction, backend_api_id=backend_api_id) country_codes = criteria_query.exclude( country_code__exact=None).values_list('country_code', flat=True).distinct() final_codes = [] countries = dict(COUNTRIES) for code in country_codes: cc = COUNTRY_CODE_TO_REGION_CODE.get(code) country_name = force_unicode(countries.get(cc[0])) if cc else '' final_codes.append((code, country_name)) search_term = self.data.get('searchString') if search_term: search_term = search_term.lower().replace('+', '') final_codes = filter( lambda x: (str(x[0]).startswith(search_term) or x[1].lower(). startswith(search_term)), final_codes) final_codes = [(c[0], "+%s%s" % (c[0], " (%s)" % c[1] if c[1] else '')) for c in final_codes] if criteria_query.filter(country_code__exact=None).exists(): final_codes.append( (NONMATCHING_COUNTRY, _('Any Country (Delivery not guaranteed via connection)'))) return final_codes
def get_rate_response(self): gateway = self.data.get('gateway') try: backend = SMSBackend.get(gateway) backend_api_id = get_backend_by_class_name( backend.doc_type).get_api_id() except Exception as e: logger.error("Failed to get backend for calculating an sms rate " "due to: %s" % e) raise SMSRateCalculatorError( "Could not obtain connection information.") country_code = self.data.get('country_code') if country_code == NONMATCHING_COUNTRY: country_code = None direction = self.data.get('direction') gateway_fee = SmsGatewayFee.get_by_criteria( backend_api_id, direction, backend_instance=gateway, country_code=country_code, ) usage_fee = SmsUsageFee.get_by_criteria(direction, self.request.domain) usd_gateway_fee = gateway_fee.amount * gateway_fee.currency.rate_to_default usd_total = usage_fee.amount + usd_gateway_fee return { 'rate': _("%s per 160 character SMS") % fmt_dollar_amount(usd_total), }
def get_global_backends_by_class(backend_class): return filter(lambda bk: bk.doc_type == backend_class.__name__, SMSBackend.view( 'sms/global_backends', reduce=False, include_docs=True, ))
def get_rate_response(self): gateway = self.data.get('gateway') try: backend = SMSBackend.get(gateway) backend_api_id = get_backend_by_class_name(backend.doc_type).get_api_id() except Exception as e: logger.error("Failed to get backend for calculating an sms rate " "due to: %s" % e) raise SMSRateCalculatorError("Could not obtain connection information.") country_code = self.data.get('country_code') if country_code == NONMATCHING_COUNTRY: country_code = None direction = self.data.get('direction') gateway_fee = SmsGatewayFee.get_by_criteria( backend_api_id, direction, backend_instance=gateway, country_code=country_code, ) usage_fee = SmsUsageFee.get_by_criteria(direction, self.request.domain) usd_gateway_fee = gateway_fee.amount * gateway_fee.currency.rate_to_default usd_total = usage_fee.amount + usd_gateway_fee return { 'rate': _("%s per 160 character SMS") % fmt_dollar_amount(usd_total), }
def country_code_response(self): gateway = self.data.get('gateway') try: backend = SMSBackend.get(gateway) backend_api_id = get_backend_by_class_name(backend.doc_type).get_api_id() except Exception: return [] direction = self.data.get('direction') criteria_query = SmsGatewayFeeCriteria.objects.filter( direction=direction, backend_api_id=backend_api_id ) country_codes = criteria_query.exclude( country_code__exact=None ).values_list('country_code', flat=True).distinct() final_codes = [] for code in country_codes: country_name = country_name_from_isd_code_or_empty(code) final_codes.append((code, country_name)) search_term = self.data.get('searchString') if search_term: search_term = search_term.lower().replace('+', '') final_codes = filter( lambda x: (str(x[0]).startswith(search_term) or x[1].lower().startswith(search_term)), final_codes ) final_codes = [(c[0], "+%s%s" % (c[0], " (%s)" % c[1] if c[1] else '')) for c in final_codes] if criteria_query.filter(country_code__exact=None).exists(): final_codes.append(( NONMATCHING_COUNTRY, _('Any Country (Delivery not guaranteed via connection)') )) return final_codes
def get_active_dimagi_owned_gateway_projects(domains, datespan, interval, datefield='date'): """ Returns list of timestamps and how many domains used a Dimagi owned gateway in the past thrity days before each timestamp """ dimagi_owned_backend = SMSBackend.view("sms/global_backends", reduce=False).all() dimagi_owned_backend_ids = [x['id'] for x in dimagi_owned_backend] backend_filter = {'terms': {'backend_id': dimagi_owned_backend_ids}} histo_data = [] for timestamp in daterange(interval, datespan.startdate, datespan.enddate): t = timestamp f = timestamp - relativedelta(days=30) sms_query = get_sms_query(f, t, 'domains', 'domain', domains) d = sms_query.filter(backend_filter).run() c = len(d.facet('domains', 'terms')) if c > 0: histo_data.append(get_data_point(c, timestamp)) return format_return_data(histo_data, 0, datespan)
def get_global_backends_by_class(backend_class): return filter( lambda bk: bk.doc_type == backend_class.__name__, SMSBackend.view( 'sms/global_backends', reduce=False, include_docs=True, ))
def __init__(self, domain, *args, **kwargs): super(SMSRateCalculatorForm, self).__init__(*args, **kwargs) backends = SMSBackend.view( "sms/backend_by_domain", startkey=[domain], endkey=[domain, {}], reduce=False, include_docs=True, ).all() backends.extend(SMSBackend.view( 'sms/global_backends', reduce=False, include_docs=True, ).all()) def _get_backend_info(backend): try: api_id = " (%s)" % get_backend_by_class_name(backend.doc_type).get_api_id() except AttributeError: api_id = "" return backend._id, "%s%s" % (backend.name, api_id) backends = [_get_backend_info(g) for g in backends] self.fields['gateway'].choices = backends self.helper = FormHelper() self.helper.form_class = "form-horizontal" self.helper.layout = crispy.Layout( crispy.Field( 'gateway', data_bind="value: gateway, events: {change: clearSelect2}", css_class="input-xxlarge", ), crispy.Field( 'direction', data_bind="value: direction, " "event: {change: clearSelect2}", ), crispy.Field( 'country_code', css_class="input-xxlarge", data_bind="value: select2CountryCode.value, " "event: {change: updateRate}", placeholder=_("Please Select a Country Code"), ), )
def _set_default_domain_backend(request, domain, backend_id, unset=False): backend = SMSBackend.get(backend_id) if not backend.domain_is_authorized(domain): raise Http404 domain_obj = Domain.get_by_name(domain, strict=True) domain_obj.default_sms_backend_id = None if unset else backend._id domain_obj.save() return HttpResponseRedirect(reverse("list_domain_backends", args=[domain]))
def create(cls, message_log, api_response=None): phone_number = clean_phone_number(message_log.phone_number) direction = message_log.direction billable = cls( log_id=message_log._id, phone_number=phone_number, direction=direction, date_sent=message_log.date, domain=message_log.domain, ) # Fetch gateway_fee backend_api_id = message_log.backend_api backend_instance = message_log.backend_id country_code, national_number = get_country_code_and_national_number(phone_number) if backend_instance is None or SMSBackend.get(backend_instance).is_global: billable.gateway_fee = SmsGatewayFee.get_by_criteria( backend_api_id, direction, backend_instance=backend_instance, country_code=country_code, national_number=national_number, ) if billable.gateway_fee is not None: conversion_rate = billable.gateway_fee.currency.rate_to_default if conversion_rate != 0: billable.gateway_fee_conversion_rate = conversion_rate else: smsbillables_logging.error("Gateway fee conversion rate for currency %s is 0", billable.gateway_fee.currency.code) else: smsbillables_logging.error( "No matching gateway fee criteria for SMSLog %s" % message_log._id ) # Fetch usage_fee todo domain = message_log.domain billable.usage_fee = SmsUsageFee.get_by_criteria( direction, domain=domain ) if billable.usage_fee is None: smsbillables_logging.error("Did not find usage fee for direction %s and domain %s" % (direction, domain)) if api_response is not None: billable.api_response = api_response if backend_api_id == TestSMSBackend.get_api_id(): billable.is_valid = False billable.save() return billable
def delete_domain_backend(request, domain, backend_id): backend = SMSBackend.get(backend_id) if backend.domain != domain or backend.base_doc != "MobileBackend": raise Http404 domain_obj = Domain.get_by_name(domain, strict=True) if domain_obj.default_sms_backend_id == backend._id: domain_obj.default_sms_backend_id = None domain_obj.save() backend.retire() # Do not actually delete so that linkage always exists between SMSLog and MobileBackend return HttpResponseRedirect(reverse("list_domain_backends", args=[domain]))
def global_backend_map(request): backend_classes = get_available_backends() global_backends = SMSBackend.view( "sms/global_backends", classes=backend_classes, include_docs=True, reduce=False ).all() current_map = {} catchall_entry = None for entry in BackendMapping.view("sms/backend_map", startkey=["*"], endkey=["*", {}], include_docs=True).all(): if entry.prefix == "*": catchall_entry = entry else: current_map[entry.prefix] = entry if request.method == "POST": form = BackendMapForm(request.POST) if form.is_valid(): new_backend_map = form.cleaned_data.get("backend_map") new_catchall_backend_id = form.cleaned_data.get("catchall_backend_id") for prefix, entry in current_map.items(): if prefix not in new_backend_map: current_map[prefix].delete() del current_map[prefix] for prefix, backend_id in new_backend_map.items(): if prefix in current_map: current_map[prefix].backend_id = backend_id current_map[prefix].save() else: current_map[prefix] = BackendMapping(is_global=True, prefix=prefix, backend_id=backend_id) current_map[prefix].save() if new_catchall_backend_id is None: if catchall_entry is not None: catchall_entry.delete() catchall_entry = None else: if catchall_entry is None: catchall_entry = BackendMapping(is_global=True, prefix="*", backend_id=new_catchall_backend_id) else: catchall_entry.backend_id = new_catchall_backend_id catchall_entry.save() messages.success(request, _("Changes Saved.")) else: initial = { "catchall_backend_id" : catchall_entry.backend_id if catchall_entry is not None else None, "backend_map" : [{"prefix" : prefix, "backend_id" : entry.backend_id} for prefix, entry in current_map.items()], } form = BackendMapForm(initial=initial) context = { "backends" : global_backends, "form" : form, } return render(request, "sms/backend_map.html", context)
def paginated_list(self): all_backends = [] all_backends += SMSBackend.view( "sms/backend_by_domain", classes=self.backend_classes, startkey=[self.domain], endkey=[self.domain, {}], reduce=False, include_docs=True ).all() all_backends += SMSBackend.view( 'sms/global_backends', classes=self.backend_classes, reduce=False, include_docs=True ).all() if len(all_backends) > 0 and not self.domain_object.default_sms_backend_id: yield { 'itemData': { 'id': 'nodefault', 'name': "Automatic Choose", 'status': 'DEFAULT', }, 'template': 'gateway-automatic-template', } elif self.domain_object.default_sms_backend_id: default_backend = SMSBackend.get(self.domain_object.default_sms_backend_id) yield { 'itemData': self._fmt_backend_data(default_backend), 'template': 'gateway-default-template', } for backend in all_backends: if not backend._id == self.domain_object.default_sms_backend_id: yield { 'itemData': self._fmt_backend_data(backend), 'template': 'gateway-template', }
def get_rate_table(self, country_code): backends = SMSBackend.view( 'sms/global_backends', reduce=False, include_docs=True, ).all() def _directed_fee(direction, backend_api_id, backend_instance_id): gateway_fee = SmsGatewayFee.get_by_criteria( backend_api_id, direction, backend_instance=backend_instance_id, country_code=country_code ) if not gateway_fee: return None usd_gateway_fee = gateway_fee.amount / gateway_fee.currency.rate_to_default usage_fee = SmsUsageFee.get_by_criteria(direction) return fmt_dollar_amount(usage_fee.amount + usd_gateway_fee) rate_table = [] from corehq.apps.sms.test_backend import TestSMSBackend for backend_instance in backends: backend_instance = backend_instance.wrap_correctly() # Skip Testing backends if isinstance(backend_instance, TestSMSBackend): continue # skip if country is not in supported countries if backend_instance.supported_countries: if ('*' not in backend_instance.supported_countries and str(country_code) not in backend_instance.supported_countries): continue gateway_fee_incoming = _directed_fee( INCOMING, backend_instance.incoming_api_id or backend_instance.get_api_id(), backend_instance._id ) gateway_fee_outgoing = _directed_fee(OUTGOING, backend_instance.get_api_id(), backend_instance._id) if gateway_fee_outgoing or gateway_fee_incoming: rate_table.append({ 'gateway': backend_instance.display_name, 'inn': gateway_fee_incoming or 'NA', # 'in' is reserved 'out': gateway_fee_outgoing or 'NA' }) return rate_table
def delete_backend(request, backend_id): backend = SMSBackend.get(backend_id) if not backend.is_global or backend.base_doc != "MobileBackend": raise Http404 backend.retire() # Do not actually delete so that linkage always exists between SMSLog and MobileBackend return HttpResponseRedirect(reverse("list_backends"))
def _sms_backend_is_global(sms_backend_id): return SMSBackend.get(sms_backend_id).is_global