Example #1
0
    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"}
Example #2
0
    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
Example #3
0
    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
Example #4
0
 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)
Example #5
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
Example #6
0
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)
Example #7
0
    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"),
            ),
        )
Example #8
0
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),
        }
Example #11
0
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
Example #14
0
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)
Example #15
0
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,
        ))
Example #16
0
    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"),
            ),
        )
Example #17
0
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]))
Example #18
0
    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
Example #19
0
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]))
Example #20
0
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)
Example #21
0
    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',
                }
Example #22
0
    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
Example #23
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
Example #24
0
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"))
Example #25
0
def _sms_backend_is_global(sms_backend_id):
    return SMSBackend.get(sms_backend_id).is_global