def normalize_urn(self, value): if self.request.user.get_org().is_anon: raise InvalidQueryError("URN lookups not allowed for anonymous organizations") try: return URN.identity(URN.normalize(value)) except ValueError: raise InvalidQueryError("Invalid URN: %s" % value)
def get_object(self, value): # try to normalize as URN but don't blow up if it's a UUID try: as_urn = URN.normalize(value) except ValueError: as_urn = value return self.get_queryset().filter(Q(uuid=value) | Q(urns__urn=as_urn)).first()
def get_object(self, value): # try to normalize as URN but don't blow up if it's a UUID try: as_urn = URN.identity(URN.normalize(value)) except ValueError: as_urn = value return self.get_queryset().filter(Q(uuid=value) | Q(urns__identity=as_urn)).first()
def validate_urn(value, strict=True): try: normalized = URN.normalize(value) if strict and not URN.validate(normalized): raise ValueError() except ValueError: raise serializers.ValidationError("Invalid URN: %s. Ensure phone numbers contain country codes." % value) return normalized
def to_internal_value(self, data): try: normalized = URN.normalize(data) if not URN.validate(normalized): raise ValueError() except ValueError: raise serializers.ValidationError("Invalid URN: %s. Ensure phone numbers contain country codes." % data) return normalized
def validate_urn(value, strict=True): try: normalized = URN.normalize(value) if strict and not URN.validate(normalized): raise ValueError() except ValueError: raise serializers.ValidationError("Invalid URN: %s. Ensure phone numbers contain country codes." % value) return normalized
def to_internal_value(self, data): try: country_code = self.context['org'].get_country_code() normalized = URN.normalize(data, country_code=country_code) if not URN.validate(normalized): raise ValueError() except ValueError: raise serializers.ValidationError("Invalid URN: %s" % data) return normalized
def get_object(self, value): # try to normalize as URN but don't blow up if it's a UUID try: as_urn = URN.identity(URN.normalize(value)) except ValueError: as_urn = value contact_ids_with_urn = list(ContactURN.objects.filter(identity=as_urn).values_list("contact_id", flat=True)) return self.get_queryset().filter(Q(uuid=value) | Q(id__in=contact_ids_with_urn)).first()
def get_object(self, value): # try to normalize as URN but don't blow up if it's a UUID try: as_urn = URN.identity(URN.normalize(value)) except ValueError: as_urn = value contact_ids_with_urn = list(ContactURN.objects.filter(identity=as_urn).values_list('contact_id', flat=True)) return self.get_queryset().filter(Q(uuid=value) | Q(id__in=contact_ids_with_urn)).first()
def to_internal_value(self, data): try: normalized = URN.normalize(data) if not URN.validate(normalized): raise ValueError() except ValueError: raise serializers.ValidationError( "Invalid URN: %s. Ensure phone numbers contain country codes." % data) return normalized
def validate(self, data): if data.get('urns') is not None and data.get('phone') is not None: raise serializers.ValidationError( "Cannot provide both urns and phone parameters together") if data.get('group_uuids') is not None and data.get( 'groups') is not None: raise serializers.ValidationError( "Parameter groups is deprecated and can't be used together with group_uuids" ) if self.org.is_anon and self.instance and self.parsed_urns is not None: raise serializers.ValidationError( "Cannot update contact URNs on anonymous organizations") if self.parsed_urns is not None: # look up these URNs, keeping track of the contacts that are connected to them urn_contacts = set() country = self.org.get_country_code() for parsed_urn in self.parsed_urns: normalized_urn = URN.identity( URN.normalize(parsed_urn, country)) urn = ContactURN.objects.filter( org=self.org, identity__exact=normalized_urn).first() if urn and urn.contact: urn_contacts.add(urn.contact) if len(urn_contacts) > 1: raise serializers.ValidationError( _("URNs are used by multiple contacts")) contact_by_urns = urn_contacts.pop( ) if len(urn_contacts) > 0 else None if self.instance and contact_by_urns and contact_by_urns != self.instance: # pragma: no cover raise serializers.ValidationError( _("URNs are used by other contacts")) else: contact_by_urns = None contact = self.instance or contact_by_urns # if contact is blocked, they can't be added to groups if contact and contact.is_blocked and self.group_objs: raise serializers.ValidationError( "Cannot add blocked contact to groups") return data
def validate_urns(self, value): if value is not None: self.parsed_urns = [] for urn in value: try: normalized = URN.normalize(urn) scheme, path, display = URN.to_parts(normalized) # for backwards compatibility we don't validate phone numbers here if scheme != TEL_SCHEME and not URN.validate(normalized): # pragma: needs cover raise ValueError() except ValueError: raise serializers.ValidationError("Invalid URN: '%s'" % urn) self.parsed_urns.append(normalized) return value
def validate_urns(self, value): if value is not None: self.parsed_urns = [] for urn in value: try: normalized = URN.normalize(urn) scheme, path, query, display = URN.to_parts(normalized) # for backwards compatibility we don't validate phone numbers here if scheme != TEL_SCHEME and not URN.validate(normalized): # pragma: needs cover raise ValueError() except ValueError: raise serializers.ValidationError("Invalid URN: '%s'" % urn) self.parsed_urns.append(normalized) return value
def validate_urn(self, value): urns = [] if value: # if we have tel URNs, we may need a country to normalize by country = self.org.get_country_code() for urn in value: try: normalized = URN.normalize(urn, country) except ValueError as e: # pragma: needs cover raise serializers.ValidationError(str(e)) if not URN.validate(normalized, country): # pragma: needs cover raise serializers.ValidationError("Invalid URN: '%s'" % urn) urns.append(normalized) return urns
def validate_urn(self, value): urns = [] if value: # if we have tel URNs, we may need a country to normalize by country = self.org.get_country_code() for urn in value: try: normalized = URN.normalize(urn, country) except ValueError as e: # pragma: needs cover raise serializers.ValidationError(six.text_type(e)) if not URN.validate(normalized, country): # pragma: needs cover raise serializers.ValidationError("Invalid URN: '%s'" % urn) urns.append(normalized) return urns
def update_urns_locally(contact, urns: list[str]): country = contact.org.default_country_code priority = ContactURN.PRIORITY_HIGHEST urns_created = [] # new URNs created urns_attached = [] # existing orphan URNs attached urns_retained = [] # existing URNs retained for urn_as_string in urns: normalized = URN.normalize(urn_as_string, country) urn = ContactURN.lookup(contact.org, normalized) if not urn: urn = ContactURN.create(contact.org, contact, normalized, priority=priority) urns_created.append(urn) # unassigned URN or different contact elif not urn.contact or urn.contact != contact: urn.contact = contact urn.priority = priority urn.save() urns_attached.append(urn) else: if urn.priority != priority: urn.priority = priority urn.save() urns_retained.append(urn) # step down our priority priority -= 1 # detach any existing URNs that weren't included urn_ids = [u.pk for u in (urns_created + urns_attached + urns_retained)] urns_detached = ContactURN.objects.filter(contact=contact).exclude( id__in=urn_ids) urns_detached.update(contact=None)
def validate(self, data): if data.get("urns") is not None and data.get("phone") is not None: raise serializers.ValidationError("Cannot provide both urns and phone parameters together") if data.get("group_uuids") is not None and data.get("groups") is not None: raise serializers.ValidationError( "Parameter groups is deprecated and can't be used together with group_uuids" ) if self.org.is_anon and self.instance and self.parsed_urns is not None: raise serializers.ValidationError("Cannot update contact URNs on anonymous organizations") if self.parsed_urns is not None: # look up these URNs, keeping track of the contacts that are connected to them urn_contacts = set() country = self.org.get_country_code() for parsed_urn in self.parsed_urns: normalized_urn = URN.identity(URN.normalize(parsed_urn, country)) urn = ContactURN.objects.filter(org=self.org, identity__exact=normalized_urn).first() if urn and urn.contact: urn_contacts.add(urn.contact) if len(urn_contacts) > 1: raise serializers.ValidationError(_("URNs are used by multiple contacts")) contact_by_urns = urn_contacts.pop() if len(urn_contacts) > 0 else None if self.instance and contact_by_urns and contact_by_urns != self.instance: # pragma: no cover raise serializers.ValidationError(_("URNs are used by other contacts")) else: contact_by_urns = None contact = self.instance or contact_by_urns # if contact is blocked, they can't be added to groups if contact and contact.is_blocked and self.group_objs: raise serializers.ValidationError("Cannot add blocked contact to groups") return data
def contact_resolve(self, org_id: int, channel_id: int, urn: str): org = Org.objects.get(id=org_id) user = get_anonymous_user() try: urn = URN.normalize(urn, org.default_country_code) if not URN.validate(urn, org.default_country_code): raise ValueError() except ValueError: raise MailroomException("contact/resolve", None, {"error": "invalid URN"}) contact_urn = ContactURN.lookup(org, urn) if contact_urn: contact = contact_urn.contact else: contact = create_contact_locally(org, user, name="", language="", urns=[urn], fields={}, group_uuids=[]) contact_urn = ContactURN.lookup(org, urn) return { "contact": { "id": contact.id, "uuid": str(contact.uuid), "name": contact.name }, "urn": { "id": contact_urn.id, "identity": contact_urn.identity }, }
def to_internal_value(self, data): if not URN.validate(data): raise ValidationError("Invalid URN: %s" % data) return URN.normalize(data)
def resolve_twitter_ids(): r = get_redis_connection() # TODO: we can't use our non-overlapping task decorator as it creates a loop in the celery resolver when registering if r.get("resolve_twitter_ids_task"): # pragma: no cover return with r.lock("resolve_twitter_ids_task", 1800): # look up all 'twitter' URNs, limiting to 30k since that's the most our API would allow anyways twitter_urns = ContactURN.objects.filter( scheme=TWITTER_SCHEME, contact__is_stopped=False, contact__is_blocked=False ).exclude(contact=None) twitter_urns = twitter_urns[:30000].only("id", "org", "contact", "path") api_key = settings.TWITTER_API_KEY api_secret = settings.TWITTER_API_SECRET client = Twython(api_key, api_secret) updated = 0 print("found %d twitter urns to resolve" % len(twitter_urns)) # contacts we will stop stop_contacts = [] # we try to look these up 100 at a time for urn_batch in chunk_list(twitter_urns, 100): screen_names = [u.path for u in urn_batch] screen_map = {u.path: u for u in urn_batch} # try to fetch our users by screen name try: resp = client.lookup_user(screen_name=",".join(screen_names)) for twitter_user in resp: screen_name = twitter_user["screen_name"].lower() twitter_id = twitter_user["id"] if screen_name in screen_map and twitter_user["id"]: twitterid_urn = URN.normalize(URN.from_twitterid(twitter_id, screen_name)) old_urn = screen_map[screen_name] # create our new contact URN new_urn = ContactURN.get_or_create(old_urn.org, old_urn.contact, twitterid_urn) # if our new URN already existed for another contact and it is newer # than our old contact, reassign it to the old contact if ( new_urn.contact != old_urn.contact and new_urn.contact.created_on > old_urn.contact.created_on ): new_urn.contact = old_urn.contact new_urn.save(update_fields=["contact"]) # get rid of our old URN ContactURN.objects.filter(id=old_urn.id).update(contact=None) del screen_map[screen_name] updated += 1 except Exception as e: # if this wasn't an exception caused by not finding any of the users, then break if str(e).find("No user matches") < 0: # exit, we'll try again later print("exiting resolve_twitter_ids due to exception: %s" % e) break # add all remaining contacts to the contacts we will stop for contact in screen_map.values(): stop_contacts.append(contact) # stop all the contacts we couldn't resolve that have only a twitter URN stopped = 0 for contact_urn in stop_contacts: contact = contact_urn.contact if len(contact.urns.all()) == 1: contact.stop(contact.created_by) stopped += 1 if len(twitter_urns) > 0: print("updated %d twitter urns, %d stopped" % (updated, len(stop_contacts)))
def to_internal_value(self, data): if not URN.validate(data): raise ValidationError("Invalid URN: %s" % data) return URN.normalize(data)