def create_sms_send_activity(self, candidate, source): """ - Here we set "params" and "type" of activity to be stored in db table "Activity" for each send. - Activity will appear as "SMS Campaign 'Hiring at Microsoft' has been sent to 'Borko Jendras'." - This method is called from send_sms_campaign_to_candidates() method of class SmsCampaignBase inside sms_campaign_service/sms_campaign_base.py. :param candidate: Candidate obj :param source: sms_campaign_send obj :type candidate: Candidate :type source: SmsCampaignSend :exception: InvalidUsage **See Also** .. see also:: send_sms_campaign_to_candidates() method in SmsCampaignBase class. """ raise_if_not_instance_of(candidate, Candidate) raise_if_not_instance_of(source, SmsCampaignSend) params = { 'candidate_name': candidate.first_name + ' ' + candidate.last_name, 'campaign_name': self.campaign.name } self.create_activity(self.user.id, _type=Activity.MessageIds.CAMPAIGN_SMS_SEND, source=source, params=params)
def send_sms(self, candidate_phone_value, message_body): """ - This uses Twilio API to send SMS to a given phone number of candidate. - This method is called from send_sms_campaign_to_candidates() method of class SmsCampaignBase inside sms_campaign_service/sms_campaign_base.py. :param candidate_phone_value: Candidate mobile phone number. :type candidate_phone_value: str :exception: InvalidUsage :return: sent message time :rtype: datetime **See Also** .. see also:: send_sms_campaign_to_candidates() method in SmsCampaignBase class. """ raise_if_not_instance_of(candidate_phone_value, basestring) logger.debug("candidate's phone value passed is %s" % candidate_phone_value) if CampaignUtils.IS_DEV: # send SMS using Twilio Test Credentials sender_phone = TWILIO_TEST_NUMBER candidate_phone_value = TWILIO_TEST_NUMBER else: sender_phone = self.user_phone.value logger.debug("user's phone value in self.user_phone is %s" % self.user_phone.value) logger.debug("user's phone value in sender_phone is %s" % sender_phone) logger.debug("candidate's phone value in receiver phone is %s" % candidate_phone_value) twilio_obj = TwilioSMS() message_response = twilio_obj.send_sms(message_body, str(sender_phone), str(candidate_phone_value)) return message_response.date_created
def get_user_phone(self): """ Here we check if current user has Twilio number in "user_phone" table. If user has no Twilio number associated, we buy a new number for this user, saves it in database and returns it. - This method is called from __int__() method of class SmsCampaignBase inside sms_campaign_service/sms_campaign_base.py. :exception: MultipleTwilioNumbersFoundForUser :return: UserPhone obj :rtype: UserPhone """ raise_if_not_instance_of(self.user, User) # TWILIO is a name defined in config phone_label_id = PhoneLabel.phone_label_id_from_phone_label(TWILIO) user_phone = UserPhone.get_by_user_id_and_phone_label_id( self.user.id, phone_label_id) if len(user_phone) == 1: # check if Twilio number is assigned to only one user user_phone_value = user_phone[0].value _get_valid_user_phone(user_phone_value) if user_phone_value: return user_phone[0] elif len(user_phone) > 1: raise MultipleTwilioNumbersFoundForUser( 'User(id:%s) has multiple phone numbers for phone label: %s' % (self.user.id, TWILIO)) else: # User has no associated Twilio number, need to buy one logger.debug( 'get_user_phone: User(id:%s) has no Twilio number associated.' % self.user.id) return self.buy_twilio_mobile_number(phone_label_id)
def _get_valid_candidate_phone(candidate_phone_value, current_user): """ - This ensures that given phone number is associated with only one candidate. - This function is called from class method process_candidate_reply() of SmsCampaignBase class to get candidate_phone db record. :param (basestring) candidate_phone_value: Phone number by which we want to get user. :param (User) current_user: Logged-in user's object :exception: If Multiple Candidates found, it raises "MultipleCandidatesFound". :exception: If no Candidate is found, it raises "CandidateNotFoundInUserDomain". :return: candidate_phone obj :rtype: CandidatePhone """ raise_if_not_instance_of(candidate_phone_value, basestring) raise_if_not_instance_of(current_user, User) candidate_phone_records = \ CandidatePhone.search_phone_number_in_user_domain(candidate_phone_value, current_user) if len(candidate_phone_records) == 1: candidate_phone = candidate_phone_records[0] elif len(candidate_phone_records) > 1: raise MultipleCandidatesFound( '%s phone number is associated with %s candidates. Candidate ids are %s' % (candidate_phone_value, len(candidate_phone_records), [ candidate_phone.candidate_id for candidate_phone in candidate_phone_records ])) else: raise CandidateNotFoundInUserDomain( "Candidate(phone=%s) does not belong to user's domain." % candidate_phone_value) return candidate_phone
def _get_valid_user_phone(user_phone_value): """ - This ensures that given phone number is associated with only one user (i.e. recruiter). - This function is called from class method process_candidate_reply() of SmsCampaignBase class to get user_phone db record. :param user_phone_value: Phone number by which we want to get user. :type user_phone_value: str :exception: If Multiple users found, it raises "MultipleUsersFound". :exception: If no user is found, it raises "ResourceNotFound". :return: user_phone obj :rtype: UserPhone """ raise_if_not_instance_of(user_phone_value, basestring) user_phones_obj = UserPhone.get_by_phone_value(user_phone_value) if len(user_phones_obj) == 1: user_phone = user_phones_obj[0] return user_phone elif len(user_phones_obj) > 1: if not user_phone_value == TWILIO_TEST_NUMBER: raise MultipleUsersFound( '%s phone number is associated with %s users. ' 'User ids are %s' % (user_phone_value, len(user_phones_obj), [user_phone.user_id for user_phone in user_phones_obj])) else: raise NoUserFoundForPhoneNumber('No User is associated with ' '%s phone number' % user_phone_value)
def search_urls_in_text(text): """ This checks if given text has any URL link present in it and returns all urls in a list. This checks for URLs starting with either http or https or www. :param text: string in which we want to search URL :type text: str :return: list of all URLs present in given text | [] :rtype: list """ raise_if_not_instance_of(text, basestring) return re.findall(r'(?:http|ftp)s?://[^\s<>"]+|www\.[^\s<>"]+', text)
def validate_a_number(self, phone_number): """ This method is used to validate a given phone number. :param phone_number: phone number :type phone_number: basestring """ raise_if_not_instance_of(phone_number, basestring) try: # This does not work with Test Credentials response = self.client.caller_ids.validate(phone_number) return response except twilio.TwilioRestException as error: logger.error('Cannot validate given number. Error is "%s"' % error.msg if hasattr(error, 'msg') else error.message) return False
def validate_url_by_http_request(url): """ This function makes HTTP GET call to given URL, and return True if we get OK response, It returns False otherwise :param url: :return: True or False :rtype: bool """ raise_if_not_instance_of(url, basestring) try: with app.app_context(): http_request('GET', url) except Exception: return False return True
def does_candidate_have_unique_mobile_phone(self, candidate): """ Here we validate that if candidate has one unique mobile number associated. If candidate has only one unique mobile number associated, we return that candidate and its phone value. Otherwise we log the error. - This method is used in send_campaign_to_candidate() method. :param candidate: candidates obj :type candidate: Candidate :exception: InvalidUsage :exception: MultipleCandidatesFound :exception: CandidateNotFoundInUserDomain :return: Candidate obj and Candidate's mobile phone :rtype: tuple **See Also** .. see also:: send_campaign_to_candidate() method in SmsCampaignBase class. """ raise_if_not_instance_of(candidate, Candidate) candidate_phones = candidate.phones mobile_label_id = PhoneLabel.phone_label_id_from_phone_label( MOBILE_PHONE_LABEL) # filter only mobile numbers candidate_mobile_phone = filter( lambda candidate_phone: candidate_phone.phone_label_id == mobile_label_id, candidate_phones) if len(candidate_mobile_phone) == 1: # If this number is associated with multiple candidates, raise exception phone_number = candidate_mobile_phone[0].value _get_valid_candidate_phone(phone_number, self.user) return candidate, get_formatted_phone_number(phone_number) elif len(candidate_mobile_phone) > 1: logger.error( 'filter_candidates_for_valid_phone: SMS cannot be sent as ' 'Candidate(id:%s) has multiple mobile numbers. ' 'Campaign(id:%s). (User(id:%s))' % (candidate.id, self.campaign.id, self.user.id)) else: logger.error( 'filter_candidates_for_valid_phone: SMS cannot be sent as ' 'Candidate(id:%s) has no mobile number associated. Campaign(id:%s). ' '(User(id:%s))' % (candidate.id, self.campaign.id, self.user.id))
def validate_urls_in_body_text(text): """ This function validates the URLs present in SMS body text. It first check if they are in valid format, then it makes HTTP GET call to that URL to verify the URL is live. :param text: :return: """ raise_if_not_instance_of(text, basestring) urls = search_urls_in_text(text) invalid_urls = [] for url in urls: try: validate_url_format(url) if not validate_url_by_http_request(url): invalid_urls.append(url) except InvalidUsage: invalid_urls.append(url) return invalid_urls
def get_formatted_phone_number(phone_number): """ This function formats the given phone number using format_phone_number() defined in common/utils/validators.py :param phone_number: :return: """ raise_if_not_instance_of(phone_number, basestring) try: formatted_phone_number = format_phone_number(phone_number)['formatted_number'] except InvalidUsage: # Adding this condition here so that in tests, fake.phone_number() # does not always generate American/Canadian number, so format_phone_number() # throws Invalid usage error. In that case we assign Twilio's Test phone number. if not CampaignUtils.IS_DEV: logger('get_formatted_phone_number: Given phone %s number is invalid.' % phone_number) raise formatted_phone_number = TWILIO_TEST_NUMBER return formatted_phone_number
def create_campaign_reply_activity(cls, sms_campaign_reply, campaign_blast, candidate, user_id): """ - Here we set "params" and "type" of activity to be stored in db table "Activity" for Campaign reply. - Activity will appear as Smith replied "Got it" on SMS campaign "abc". - This method is called from process_candidate_reply() method of class SmsCampaignBase inside sms_campaign_service/sms_campaign_base.py. :param sms_campaign_reply: "sms_campaign_reply" obj :param campaign_blast: "sms_campaign_blast" obj :param candidate: Candidate obj :type sms_campaign_reply: SmsCampaignReply :type campaign_blast: SmsCampaignBlast :type candidate: Candidate :exception: ResourceNotFound **See Also** .. see also:: process_candidate_reply() method in SmsCampaignBase class. """ # get Candidate raise_if_not_instance_of(candidate, Candidate) campaign = campaign_blast.campaign raise_if_not_instance_of(campaign, SmsCampaign) params = { 'candidate_name': candidate.first_name + ' ' + candidate.last_name, 'reply_text': sms_campaign_reply.body_text, 'campaign_name': campaign.name } auth_header = cls.get_authorization_header(user_id) cls.create_activity(user_id, _type=Activity.MessageIds.CAMPAIGN_SMS_REPLY, source=sms_campaign_reply, params=params)