def get_rsvps(self, event): """ :param event: event in getTalent database :type event: common.models.event.Event :return: rsvps of given event :rtype: list - We get RSVPs of given event by API of Meetup. - We use this method while importing RSVPs through social network manager. :Example: - Create RSVP class object as sn_rsvp_obj = sn_rsvp_class(social_network=self.social_network, headers=self.headers, user_credentials=user_credentials) - Then call get_all_rsvps() on sn_rsvp_obj by passing events in parameters as follow self.rsvps = sn_rsvp_obj.get_all_rsvps(self.events) - Inside get_all_rsvps(), we call get_rsvps() on class object. - It appends rsvps of an events in a list and returns it **See Also** .. seealso:: get_all_rsvps() method in RSVPBase class inside social_network_service/rsvp/base.py for more insight. :return: list of RSVPs """ rsvps = [] social_network_id = event.social_network_id assert social_network_id is not None rsvps_url = get_url(self, Urls.RSVPS) params = {'event_id': event.social_network_event_id} response = http_request('GET', rsvps_url, params=params, headers=self.headers, user_id=self.user.id) if response.ok: data = response.json() rsvps.extend(data['results']) # next_url determines the pagination, this variable keeps appearing in response if there are more pages # and stops showing when there are no more. We have almost the same code for events' pagination, # might consolidate it. next_url = data['meta']['next'] or None while next_url: # Sleep for 10 / 30 seconds to avoid throttling time.sleep(0.34) # attach the key before sending the request response = http_request('GET', next_url, headers=self.headers, user_id=self.user.id) if response.ok: data = response.json() rsvps.extend(data['results']) next_url = data['meta']['next'] or None if not next_url: break return rsvps
def get_rsvps(self, event): """ This method is used to get all attendees of an event :param event: Eventbrite event :type event: Event :return: List of RSVPs :rtype: list """ rsvps_url = get_url(self, Urls.RSVPS).format(event.social_network_event_id) response = http_request('GET', url=rsvps_url, headers=self.headers, user_id=self.user.id) all_rsvps = [] data = response.json() page_size = data['pagination']['page_size'] total_records = data['pagination']['object_count'] all_rsvps.extend(data['attendees']) current_page = 1 total_pages = total_records / page_size for page in xrange(1, total_pages): params = {'page': current_page} current_page += 1 # get data for every page response = http_request('GET', rsvps_url, params=params, headers=self.headers, user_id=self.user.id) if response.ok: data = response.json() all_rsvps.extend(data['attendees']) return all_rsvps
def get_events(self): """ We send GET requests to API URL and get data. We also have to handle pagination because Meetup's API does that too. :return: all_events: Events of getTalent user on Meetup.com :rtype all_events: list """ all_events = [] # contains all events of gt-users # page size is 100 so if we have 500 records we will make # 5 requests (using pagination where each response will contain # 100 records). events_url = get_url(self, Urls.EVENTS) # we can specify status=upcoming,past,draft,cancelled etc. By default we # have status=upcoming, so we are not explicitly specifying in fields. meetup_groups = MeetupGroup.filter_by_keywords(user_id=self.user.id) if not meetup_groups: logger.warn( '''No MeetupGroup is associated with this user subscription for Meetup. UserId: %s MemberId: %s ''' % (self.user.id, self.user_credentials.member_id)) return all_events params = { 'group_id': ','.join([str(group.group_id) for group in meetup_groups]), 'status': "upcoming,proposed,suggested", 'fields': 'timezone' } response = http_request('GET', events_url, params=params, headers=self.headers, user_id=self.user.id) if response.ok: data = response.json() all_events = [] all_events.extend(data['results']) # next_url determines the pagination, this variable keeps # appearing in response if there are more pages and stops # showing up when there are no more. next_url = data['meta']['next'] or None while next_url: url = next_url # Sleep for 10 / 30 seconds to avoid throttling time.sleep(0.34) response = http_request('GET', url, headers=self.headers, user_id=self.user.id) if response.ok: data = response.json() all_events.extend(data['results']) next_url = data['meta']['next'] or None if not next_url: break return all_events
def get_events(self, status='live', order_by='start_asc'): """ We send GET requests to Eventbrite API and get already created events by this user on eventbrite.com. We also have to handle pagination because Eventbrite's API does that too. :return all_events: a collection of eventbrite events for specific user :rtype all_events: list """ # create url to fetch events from eventbrite.com search = str(self.member_id) + '/owned_events' events_url = get_url(self, Urls.USER).format(search) params = {'status': status} # initialize event list to empty all_events = [] try: # send a GET request to eventbrite's api to get events for given # user and after start_date response = http_request('GET', events_url, params=params, headers=self.headers, user_id=self.user.id) except: logger.exception('get_events: user_id: %s' % self.user.id) raise if response.ok: # if response is ok, get json data data = response.json() all_events.extend(data['events']) current_page = 1 total_pages = data['pagination']['page_count'] for page in range(1, total_pages): params_copy = params.copy() current_page += 1 params_copy['page'] = current_page try: # get data for every page response = http_request('GET', events_url, params=params_copy, headers=self.headers, user_id=self.user.id) except Exception as e: logger.exception('get_events: user_id: %s' % self.user.id) if response.ok: data = response.json() all_events.extend(data['events']) return all_events
def get_groups(self): """ - This function fetches the groups of user from Meetup website for which the user is an organizer. These groups are shown in drop down while creating event on Meetup through Event Creation Form. - This function is called from GET method of class MeetupGroups() inside social_network_service/app/restful/social_network.py :Example: from social_network_service.meetup import Meetup sn = Meetup(user_id=1) sn.get_groups() :return: Meetup groups for which gt-user is an organizer :rtype: list (list of dicts where each dict is likely the response from Meetup API) **See Also** .. seealso:: GET method of class MeetupGroups() inside social_network_service/app/restful/social_network.py """ url = get_url(self, SocialNetworkUrls.GROUPS) params = {'organizer_id': self.user_credentials.member_id} # Sleep for 10 / 30 seconds to avoid throttling time.sleep(0.34) response = http_request('GET', url, params=params, headers=self.headers, user_id=self.user.id) if response.ok: return response.json()['results'] else: return []
def unpublish_event(self, event_id, method=HttpMethods.DELETE): """ This function is used while running unit tests. It deletes the Event from database that were created during the lifetime of a unit test. :param int | long event_id: id of newly created event :param string method: http standard method , default is DELETE :return: True if event is deleted from vendor, False otherwise. :rtype: bool """ # create url to unpublish event url = get_url(self, Urls.EVENT).format(event_id) # Sleep for 10 / 30 seconds to avoid throttling time.sleep(0.34) # params are None. Access token is present in self.headers response = http_request(method, url, headers=self.headers, user_id=self.user.id) if response.ok: logger.info('| Event has been unpublished (deleted) |') else: error_message = "Event was not unpublished (deleted):%s" % response.text log_error({'user_id': self.user.id, 'error': error_message}) raise EventNotUnpublished( 'ApiError: Unable to remove event from %s' % self.social_network.name)
def application_based_auth(self): """ This function does application based authentication with Twitter. This do not need any user interaction to connect with Twitter account because it makes request on behalf of App. It raises InternalServerError in case authentication fails. Here are the detailed docs https://dev.twitter.com/oauth/application-only. :return: Access token for getTalent app to access Twitter's API. :rtype: str """ combined_key = self.consumer_key + ':' + self.consumer_secret combined_key = base64.b64encode(combined_key) headers = { 'Authorization': 'Basic %s' % combined_key, 'Content-Type': 'application/x-www-form-urlencoded' } data = {'grant_type': 'client_credentials'} result = http_request('post', APPLICATION_BASED_AUTH_URL, headers=headers, data=data, app=app) if result.ok: logger.info( 'Successfully authenticated from Twitter API. User(id:%s).', self.user.id) access_token = result.json()['access_token'] return access_token raise InternalServerError( 'Error Occurred while authenticating from Twitter')
def publish_event(self, social_network_event_id): """ This function publishes the Event on Eventbrite. This event is public. :param social_network_event_id: id for event on eventbrite.com :type social_network_event_id: int :exception EventNotPublished: raises this exception when unable to publish event on Eventbrite.com """ # create url to publish event url = get_url(self, Urls.PUBLISH_EVENT).format(str(social_network_event_id)) # params are None. Access token is present in self.headers response = http_request('POST', url, headers=self.headers, user_id=self.user.id) if response.ok: logger.info('| Event has been published |') else: error_message = "Event was not Published. There are some " \ "errors: Details: %s |" % response.text log_error({ 'user_id': self.user.id, 'error': error_message, }) raise EventNotPublished('ApiError: Unable to publish event ' 'on Eventbrite')
def add_venue_to_sn(self, venue_data): """ This function sends a POST request to Eventbrite api to create a venue for event. :param dict venue_data: a dictionary containing data required to create a venue """ payload = { 'venue.name': venue_data['address_line_1'], 'venue.address.address_1': venue_data['address_line_1'], 'venue.address.address_2': venue_data.get('address_line_2'), 'venue.address.region': venue_data['state'], 'venue.address.city': venue_data['city'], 'venue.address.postal_code': venue_data.get('zip_code'), 'venue.address.latitude': venue_data.get('latitude'), 'venue.address.longitude': venue_data.get('longitude') } # create url to send post request to create venue url = get_url(self, Urls.VENUES) response = http_request('POST', url, params=payload, headers=self.headers, user_id=self.user.id) json_resp = response.json() if response.ok: logger.info('| Venue has been created |') venue_id = json_resp.get('id') else: raise InternalServerError( 'ApiError: Unable to create venue for Eventbrite', additional_error_info=dict(venue_error=json_resp)) venue_data['user_id'] = self.user.id venue_data['social_network_venue_id'] = venue_id return SocialNetworkBase.save_venue(venue_data)
def update_event(self): """ It first creates/ updates a venue on Meetup.com and then passes that venue's id in event's payload to update event location along with event data. :exception EventNotCreated: raises exception if unable to update event on Meetup.com :return: id of event :rtype: int """ # create url to update event url = get_url(self, Urls.EVENT).format(self.social_network_event_id) # create or update venue for event venue_id = self.add_location() # add venue id in event payload to update event venue on Meetup.com self.payload.update({'venue_id': venue_id}) # Sleep for 10 / 30 seconds to avoid throttling time.sleep(0.34) response = http_request('POST', url, params=self.payload, headers=self.headers, user_id=self.user.id) if response.ok: event_id = response.json().get('id') logger.info('| Event %s updated Successfully |' % self.payload['name']) self.data['social_network_event_id'] = event_id return self.save_event() else: error_message = 'Event was not created. Error occurred during ' \ 'event update on Meetup' log_error({'user_id': self.user.id, 'error': error_message}) raise EventNotCreated('ApiError: Unable to update event on Meetup')
def get_member_id(self): """ - If getTalent user has an account on some social network, like Meetup.com, it will have a "member id" for that social network. This "member id" is used to make API subsequent calls to fetch events or RSVPs and relevant data for getTalent user from social network website. :Example: from social_network_service.meetup import Meetup sn = Meetup(user_id=1) sn.get_member_id() - We call this method from connect() of SocialNetworkBase class so that we don't need to get 'member id' of getTalent user while making object of some social network class at different places. (e.g. creating object of Meetup() in when importing events) **See Also** .. seealso:: connect() method defined in SocialNetworkBase class inside social_network_service/base.py. """ logger.info('Getting "member id" of %s(user id:%s) using API of %s.' % (self.user.name, self.user.id, self.social_network.name)) url = get_url(self, SocialNetworkUrls.VALIDATE_TOKEN) # Now we have the URL, access token, and header is set too, response = http_request('POST', url, headers=self.headers, user_id=self.user.id, app=app) if response.ok: return response.json().get('id') else: logger.error('get_member_id: Error:%s.' % response.text)
def _filter_event(self, event): """ This method returns True if given event's group is owned by current user. :param event: event to be tested :type event: dict :return True or False :rtype Boolean """ social_network_group_id = event['group'].get('id') # check if event's group id exists if social_network_group_id: if social_network_group_id in self.social_network_group_ids: return True url = get_url(self, Urls.GROUPS) + '/?sign=true' # Sleep for 10 / 30 seconds to avoid throttling time.sleep(0.34) response = http_request( 'GET', url, params={'group_id': social_network_group_id}, headers=self.headers, user_id=self.user.id) if response.ok: group = response.json() group_organizer = group['results'][0]['organizer'] # group_organizer contains a dict that has member_id and name if str(group_organizer['member_id']) == self.member_id: # save this group id as user's owned groups, so no need to # fetch it again self.social_network_group_ids.append( social_network_group_id) return True return False
def delete_webhooks(cls, user_credentials): """ This method deletes webhook for current user from eventbrite :param type(t) user_credentials: user credentials for eventbrite for this user """ url = user_credentials.social_network.api_url + "/webhooks/" + user_credentials.webhook headers = {'Authorization': 'Bearer ' + user_credentials.access_token} response = http_request('GET', url, headers=headers, user_id=user_credentials.user.id) if response.ok: webhook = response.json() # Deleting existing webhook for this user because we don't know about their action types. # Need to register a webhook with `event.published` and `event.unpublished` actions and correct callback url http_request('DELETE', webhook['resource_uri'], headers=headers, user_id=user_credentials.user.id)
def get_access_and_refresh_token(cls, user_id, social_network, code_to_get_access_token=None, method_type=None, payload=None, params=None, api_relative_url=None): """ This function is used by Social Network API to save 'access_token' and 'refresh_token' for specific social network against a user (current user) by sending an POST call to respective social network API. :param user_id: current user id :param social_network: social_network in getTalent database :param code_to_get_access_token: Code which is exchanged for an access token. :param method_type: In case of Eventbrite, need to make a 'POST' call to get access token. :param payload: is set inside this method and is passed in super constructor. This is sent in body of HTTP request. :param params: dictionary of data to send in the url params. :param api_relative_url: This variable is set in this function and is passed in super constructor to make HTTP request. :type user_id: int :type social_network: common.models.social_network.SocialNetwork :type code_to_get_access_token: str :type method_type: str :type payload: dict :type payload: dict :type api_relative_url: str :return: returns access token and refresh token :rtype: tuple (str, str) """ logger.info('Getting "access_token and refresh_token" of user_id:%s using API of %s.' % (user_id, social_network.name)) url = social_network.auth_url + api_relative_url get_token_response = http_request(method_type, url, data=payload, user_id=user_id, params=params) try: if get_token_response.ok: # access token is used to make API calls, this is what we need # to make subsequent calls try: response = get_token_response.json() access_token = response.get('access_token') # refresh token is used to refresh the access token refresh_token = response.get('refresh_token') except ValueError: if 'facebook' in social_network.api_url: # In case of Facebook, access_token is retrieved as follows access_token = \ get_token_response.content.split('=')[1].split('&')[0] refresh_token = '' else: raise return access_token, refresh_token else: log_error({'user_id': user_id, 'error': get_token_response.json().get('error')}) raise SNServerException('Unable to to get access token for current user') except: logger.exception('get_access_and_refresh_token: user_id: %s, social network: %s(id: %s)' % (user_id, social_network.name, social_network.id)) raise SNServerException('Unable to create user credentials for current user')
def add_venue_to_sn(self, venue_data): """ This function sends a POST request to Meetup api to create a venue for event. :param dict venue_data: a dictionary containing data required to create a venue """ if not venue_data.get('group_url_name'): raise InvalidUsage("Mandatory Input Missing: group_url_name", error_code=custom_codes.MISSING_REQUIRED_FIELDS) url = get_url(self, SocialNetworkUrls.VENUES, custom_url=MEETUP_VENUE).format( venue_data['group_url_name']) payload = { 'address_1': venue_data['address_line_1'], 'address_2': venue_data.get('address_line_2'), 'city': venue_data['city'], 'country': venue_data['country'], 'state': venue_data['state'], 'name': venue_data['address_line_1'] } # Sleep for 10 / 30 seconds to avoid throttling time.sleep(0.34) response = http_request('POST', url, params=payload, headers=self.headers, user_id=self.user.id) json_resp = response.json() if response.ok: venue_id = json_resp['id'] logger.info('Venue has been Added. Venue Id: %s', venue_id) elif response.status_code == codes.CONFLICT: # 409 is returned when our venue is matching existing # venue/venues. try: match_id = json_resp['errors'][0]['potential_matches'][0]['id'] except: raise InternalServerError('Invalid response from Meetup', error_code=INVALID_MEETUP_RESPONSE) venue = Venue.get_by_user_id_and_social_network_venue_id( self.user.id, match_id) if venue: raise InvalidUsage( 'Venue already exists in getTalent database', additional_error_info=dict(id=venue.id), error_code=VENUE_EXISTS_IN_GT_DATABASE) venue_id = json_resp['errors'][0]['potential_matches'][0]['id'] else: raise InternalServerError( 'ApiError: Unable to create venue for Meetup', additional_error_info=dict(venue_error=json_resp)) venue_data['user_id'] = self.user.id venue_data['social_network_venue_id'] = venue_id return SocialNetworkBase.save_venue(venue_data)
def get_event(self, event_url): """ This method takes event resource uri, sends a GET call to respective social network API and then saves retrieved event in db. :param event_url: event resource rui :return: event model object :rtype type(t) """ # Sleep for 10 / 30 seconds to avoid throttling time.sleep(0.34) response = http_request('get', event_url, headers=self.headers) if response.ok: event = response.json() return self.import_event(event)
def save_attendee_source(self, attendee): """ :param attendee: attendees is a utility object we share in calls that contains pertinent data. :type attendee: object of class Attendee defined in utilities.py - This method checks if the event is present in candidate_source db table. If does not exist, it adds record, otherwise updates the record. It then appends id of source in attendee object to be saved in candidate table. - This method is called from process_rsvps() defined in RSVPBase class inside social_network_service/rsvp/base.py. - We use this method while importing RSVPs through social network :Example: attendee = self.save_attendee_source(attendee) **See Also** .. seealso:: process_rsvps() method in EventBase class :return attendee: :rtype: Attendee """ attendee.event.description = attendee.event.description[:1000].encode('utf-8') if attendee.event.description \ else 'Not given' candidate_source = { "source": { "description": attendee.event.description, "notes": attendee.event.title } } access_token = User.generate_jw_token(user_id=self.user.id) header = { 'Content-Type': 'application/json', 'Authorization': access_token } response = http_request('POST', UserServiceApiUrl.DOMAIN_SOURCES, headers=header, data=json.dumps(candidate_source)) # Source already exists if response.status_code == requests.codes.bad: attendee.candidate_source_id = response.json()['error']['source_id'] elif response.status_code != requests.codes.created: logger.exception(response.text) raise InternalServerError(error_message="Error while creating candidate source") else: attendee.candidate_source_id = response.json()['source']['id'] return attendee
def test_meetup_rsvp_importer_with_valid_token(self, user_first, talent_pool_session_scope, token_first, meetup_event_dict_second): """ - We post an RSVP on this event. We assert on response of RSVP POST. If it is in 2xx, then we run rsvp importer to import RSVP (that we just posted) in database. After importing RSVPs, we pick the imported record using social_network_rsvp_id and finally assert on the status of RSVP. It should be same as given in POST request's payload. - We add 'id' of newly created event to delete it from social network website in the finalizer of meetup_event_dict. """ event = meetup_event_dict_second['event'] with app.app_context(): social_network_event_id = event.social_network_event_id user_credentials = UserSocialNetworkCredential.get_by_user_and_social_network_id( user_first['id'], event.social_network.id) # create object of respective social network to run RSVP importer social_network = SocialNetwork.get_by_name( user_credentials.social_network.name) social_network_class = get_class(social_network.name.lower(), 'social_network', user_credentials=user_credentials) # we call social network class here for auth purpose, If token is expired # access token is refreshed and we use fresh token to make HTTP calls sn = social_network_class(user_id=user_credentials.user_id) url = '{}/rsvp/'.format(sn.api_url) payload = {'event_id': social_network_event_id, 'rsvp': 'no'} response = http_request('POST', url, params=payload, headers=sn.headers) assert response.ok is True logger.info('RSVP has been posted successfully') social_network_rsvp_id = response.json()['rsvp_id'] sn.process('rsvp', user_credentials=user_credentials) # get the imported RSVP by social_network_rsvp_id and social_network_id rsvp_in_db = RSVP.get_by_social_network_rsvp_id_and_social_network_id( social_network_rsvp_id, social_network.id) assert isinstance(rsvp_in_db, RSVP) assert rsvp_in_db.status == payload['rsvp'] response = send_request('delete', url=SocialNetworkApiUrl.EVENT % event.id, access_token=token_first) assert response.status_code == codes.OK
def fetch_event(self, event_id): """ This method creates api url to fetch an event and then sends a GET request to fetch that event. :param str | int | long event_id: social network event id :return: event dict or None """ event_url = get_url(self, SocialNetworkUrls.EVENT).format(event_id) try: response = http_request('get', event_url, headers=self.headers) if response.ok: return response.json() except ResourceNotFound: logger.info('Event not found for url: %s' % event_url) return None
def create_webhook(cls, user_credentials): """ :param user_credentials: User's social network credentials for which we need to create webhook. Webhook is created to be updated about any RSVP on an event of Eventbrite. :type user_credentials: common.models.user.UserSocialNetworkCredential - This method creates a webhook to stream the live feed of RSVPs of Eventbrite events to the getTalent app. Once we have the webhook id for given user, we update user credentials in db. - It also performs a check which ensures that webhook is not generated every time code passes through this flow once a webhook has been created for a user (since webhook don't expire and are unique for every user). - This method is called from save_user_credentials_in_db() defined in Eventbrite class inside social_network_service/eventbrite.py. **See Also** .. seealso:: save_user_credentials_in_db() function defined in Eventbrite class inside social_network_service/eventbrite.py. .. seealso:: get_access_and_refresh_token() function defined in Eventbrite class inside social_network_service/eventbrite.py. """ url = user_credentials.social_network.api_url + "/webhooks/" payload = { 'endpoint_url': SocialNetworkApiUrl.WEBHOOK % user_credentials.user_id, 'actions': ','.join([ ACTIONS['published'], ACTIONS['unpublished'], ACTIONS['rsvp'] ]) } headers = {'Authorization': 'Bearer ' + user_credentials.access_token} response = http_request('POST', url, params=payload, headers=headers, user_id=user_credentials.user.id) try: webhook_id = response.json()['id'] user_credentials.update(webhook=webhook_id) except Exception: logger.exception('create_webhook: user_id: %s' % user_credentials.user.id) raise InternalServerError( "Eventbrite Webhook wasn't created successfully")
def test_meetup_rsvp_importer_with_invalid_token(self, user_first, token_first, meetup_event_dict_second): """ - Here we post an RSVP on newly created event and assert on response of RSVP POST. It should be 2xx, then we run rsvp importer with invalid token. RSVP for this event should not be imported. - We add 'id' of newly created event to delete it from social network website in the finalizer of meetup_event_dict. """ with app.app_context(): event = meetup_event_dict_second['event'] social_network_event_id = event.social_network_event_id user_credentials = UserSocialNetworkCredential.get_by_user_and_social_network_id( user_first['id'], event.social_network.id) # create object of respective social network to run RSVP importer social_network = SocialNetwork.get_by_name( user_credentials.social_network.name) social_network_class = get_class(social_network.name.lower(), 'social_network', user_credentials=user_credentials) # we call social network class here for auth purpose, If token is expired # access token is refreshed and we use fresh token to make HTTP calls sn = social_network_class(user_id=user_credentials.user_id) url = sn.api_url + '/rsvp/' payload = {'event_id': social_network_event_id, 'rsvp': 'no'} response = http_request('POST', url, params=payload, headers=sn.headers) assert response.ok is True, "Response: {}".format(response.text) logger.info('RSVP has been posted successfully') social_network_rsvp_id = response.json()['rsvp_id'] sn.headers = {'Authorization': 'Bearer invalid_token'} logger.info('Access Token has been malformed.') # Call process method of social network class to start importing RSVPs sn.process('rsvp', user_credentials=user_credentials) # get the imported RSVP by social_network_rsvp_id and social_network_id rsvp_in_db = RSVP.get_by_social_network_rsvp_id_and_social_network_id( social_network_rsvp_id, social_network.id) assert rsvp_in_db is None
def archive_email_campaigns_for_deleted_event(self, event, deleted_from_vendor): """ Whenever an event is deleted from social-network, we update field `is_deleted_from_vendor` to 1. We then check if it was promoted via email-campaign to getTalent candidates and mark all linked email-campaigns as archived. :param Event event: Event object :param bool deleted_from_vendor: flag to mark 'is_deleted_from_vendor' field true or false """ # if deleted_from_vendor is True, it means we are directly deleting event from our api call not from third party # social network that's why we need to un-publish # event from social network and will also mark is_hidden=1. if deleted_from_vendor: event.update(is_deleted_from_vendor=1, is_hidden=1) else: event.update(is_deleted_from_vendor=1) base_campaign_events = event.base_campaign_event for base_campaign_event in base_campaign_events: base_campaign = base_campaign_event.base_campaign email_campaigns = base_campaign.email_campaigns.all() for email_campaign in email_campaigns: data = {'is_hidden': 1} try: response = http_request('patch', EmailCampaignApiUrl.CAMPAIGN % email_campaign.id, headers=self.gt_headers, data=json.dumps(data)) if response.ok: logger.info( 'Email campaign(id:%s) has been archived successfully.' % email_campaign.id) else: logger.info( 'Email campaign(id:%s) could not be archived.' % email_campaign.id) except Exception: logger.exception( 'Email campaign(id:%s) could not be archived.' % email_campaign.id)
def create_event(self): """ This function is used to create Meetup event using vendor's API. It first creates a venue for event. Then venue_id is passed to event_payload. Then a POST request to Meetup API creates event on Meetup.com :exception EventNotCreated: raises exception if unable to publish/create event on Meetup.com. :return: id of event in db :rtype: int """ venue_id = self.add_location() url = get_url(self, Urls.EVENT).format('') self.payload.update({ 'venue_id': venue_id, 'publish_status': 'published' }) logger.info( 'Creating event for %s(user id:%s) using url:%s of API of %s.' % (self.user.name, self.user.id, url, self.social_network.name)) # Sleep for 10 / 30 seconds to avoid throttling time.sleep(0.34) response = http_request('POST', url, params=self.payload, headers=self.headers, user_id=self.user.id) if response.ok: event = response.json() event_id = event['id'] logger.info('| Event %s created Successfully |' % self.payload['name']) self.data['social_network_event_id'] = event_id self.data['url'] = event.get('event_url', '') return self.save_event() else: error_message = 'Event was not Created. Error occurred during draft creation' log_error({'user_id': self.user.id, 'error': error_message}) raise EventNotCreated('ApiError: Unable to create event on Meetup')
def import_meetup_groups(self): """ This method is to fetch user's meetup groups and save them in gt database. """ url = get_url(self, SocialNetworkUrls.GROUPS) params = {'organizer_id': self.user_credentials.member_id} # Sleep for 10 / 30 seconds to avoid throttling time.sleep(0.34) response = http_request('GET', url, params=params, headers=self.headers, user_id=self.user.id) meetup_groups = [] if response.ok: groups = response.json()['results'] for group in groups: meetup_group = MeetupGroup.get_by_group_id(group['id']) if meetup_group: if meetup_group.user_id == self.user.id: meetup_groups.append(meetup_group.to_json()) continue meetup_group = MeetupGroup( group_id=group['id'], user_id=self.user.id, name=group['name'], url_name=group['urlname'], description=group.get('description'), created_datetime=group.get('created'), visibility=group.get('visibility'), country=group.get('country'), state=group.get('state'), city=group.get('city'), timezone=group.get('timezone')) MeetupGroup.save(meetup_group) meetup_groups.append(meetup_group.to_json()) return meetup_groups
def create_event_organizer(self, data): """ This method sends a POST request to Eventbrite API to create an event organizer. :param dict[str, T] data: organizer data :return: organizer id on Eventbrite :rtype string """ mandatory_input_data = ['name', 'about'] # gets fields which are missing missing_items = find_missing_items(data, mandatory_input_data) if missing_items: raise InvalidUsage("Mandatory Input Missing: %s" % missing_items) payload = { 'organizer.name': data['name'], 'organizer.description.html': data['about'] } # create url to send post request to create organizer url = get_url(self, Urls.ORGANIZERS) response = http_request('POST', url, params=payload, headers=self.headers, user_id=self.user.id) json_response = response.json() if response.ok: return json_response['id'] elif response.status_code == requests.codes.BAD_REQUEST and json_response.get( 'error') == "ARGUMENTS_ERROR": raise InvalidUsage( 'Organizer name `{}` already exists on Eventbrite'.format( data['name']), error_code=ORGANIZER_ALREADY_EXISTS) raise InternalServerError( 'Error occurred while creating organizer.', additional_error_info=dict(error=json_response))
def manage_event_tickets(self, tickets_url): """ Here tickets are created for event on Eventbrite. This method sends a POST request to Eventbrite.com API to create or update tickets. Call this method from create_tickets or update_tickets with respective tickets_url. It returns tickets id if successful otherwise raises "TicketsNotCreated" exception :param tickets_url (API url to create or update event tickets) :type tickets_url: str :exception TicketsNotCreated (throws exception if unable to create or update tickets) :return: tickets_id (an id which refers to tickets for event on eventbrite.com) :rtype: str """ # send POST request to create or update tickets for event response = http_request('POST', tickets_url, params=self.ticket_payload, headers=self.headers, user_id=self.user.id) if response.ok: logger.info( '| %s Ticket(s) have been created |' % str(self.ticket_payload['ticket_class.quantity_total'])) tickets_id = response.json().get('id') return tickets_id else: error_message = 'Event tickets were not created successfully' log_error({ 'user_id': self.user.id, 'error': error_message, }) raise TicketsNotCreated('ApiError: Unable to create event tickets ' 'on Eventbrite')
def save_attendee_as_candidate(self, attendee): """ :param attendee: attendees is a utility object we share in calls that contains pertinent data. :type attendee: object of class Attendee defined in utilities.py - This method adds the attendee as a new candidate if it is not present in the database already, otherwise it updates the previous record. It then appends the candidate_id to the attendee object and add/updates record in candidate_social_network db table. It then returns attendee object. - This method is called from process_rsvps() defined in RSVPBase class inside social_network_service/rsvp/base.py. - We use this method while importing RSVPs through social network :Example: attendee = self.save_attendee_as_candidate(attendee) **See Also** .. seealso:: process_rsvps() method in EventBase class :return attendee: :rtype: Attendee """ candidate_in_db = None request_method = 'post' domain_id = User.get_domain_id(attendee.gt_user_id) candidate_by_email = CandidateEmail.get_email_in_users_domain(domain_id, attendee.email) if candidate_by_email: candidate_in_db = candidate_by_email.candidate if not candidate_in_db: # TODO: Need to handle multiple sources per candidate candidate_in_db = Candidate.filter_by_keywords(first_name=attendee.first_name, last_name=attendee.last_name, source_id=attendee.candidate_source_id, source_product_id=attendee.source_product_id) if candidate_in_db: candidate_in_db = candidate_in_db[0] # Check if candidate's domain is same as user's domain if not candidate_in_db.user.domain_id == domain_id: candidate_in_db = None # To create candidates, user must have be associated with talent_pool talent_pools = TalentPool.filter_by_keywords(user_id=attendee.gt_user_id) talent_pool_ids = map(lambda talent_pool: talent_pool.id, talent_pools) if not talent_pool_ids: raise InternalServerError("save_attendee_as_candidate: user doesn't have any talent_pool") # Create data dictionary data = {'first_name': attendee.first_name, 'last_name': attendee.last_name, 'source_id': attendee.candidate_source_id, 'source_product_id': attendee.source_product_id, 'talent_pool_ids': dict(add=talent_pool_ids) } social_network_data = { 'name': attendee.event.social_network.name, 'profile_url': attendee.social_profile_url } # Update if already exist if candidate_in_db: candidate_id = candidate_in_db.id data.update({'id': candidate_id}) request_method = 'patch' # Get candidate's social network if already exist candidate_social_network_in_db = \ CandidateSocialNetwork.get_by_candidate_id_and_sn_id(candidate_id, attendee.social_network_id) if candidate_social_network_in_db: social_network_data.update({'id': candidate_social_network_in_db.id}) if attendee.email: data.update({'emails': [{'address': attendee.email, 'label': EmailLabel.PRIMARY_DESCRIPTION, 'is_default': True }] }) # Update social network data to be sent with candidate data.update({'social_networks': [social_network_data]}) access_token = User.generate_jw_token(user_id=self.user.id) header = { 'Content-Type': 'application/json', 'Authorization': access_token } resp = http_request(request_method, url=CandidateApiUrl.CANDIDATES, headers=header, data=json.dumps(dict(candidates=[data])), app=app) data_resp = resp.json() if resp.status_code not in [codes.CREATED, codes.OK]: error_message = 'save_attendee_as_candidate: candidate creation failed. Error:%s' % data_resp logger.error(error_message) raise InternalServerError(error_message) attendee.candidate_id = data_resp['candidates'][0]['id'] return attendee
def add_location(self): """ This generates a venue object for the event and returns the id of venue. :exception EventLocationNotCreated: throws exception if unable to create or update venue on Eventbrite :exception VenueNotFound: raises exception if venue does not exist in database :return venue_id: id for venue created on eventbrite.com :rtype venue_id: int :Example: This method is used to create venue or location for event on Eventbrite. It requires a venue already created in getTalent database otherwise it will raise VenueNotFound exception. Given venue id it first gets venue from database and uses its data to create Eventbrite object >> eventbrite = Eventbrite(user=gt_user, headers=authentication_headers) Then we call add location from create event To get a better understanding see *create_event()* method. """ # get venue from db which will be created on Eventbrite venue = Venue.get_by_user_id_social_network_id_venue_id( self.user.id, self.social_network.id, self.venue_id) if venue: if venue.social_network_venue_id: # there is already a venue on Eventbrite with this info. return venue.social_network_venue_id # This dict is used to create venue payload = { 'venue.name': venue.address_line_1, 'venue.address.address_1': venue.address_line_1, 'venue.address.address_2': venue.address_line_2, 'venue.address.region': venue.state, 'venue.address.city': venue.city, # 'venue.address.country': venue.country, 'venue.address.postal_code': venue.zip_code, 'venue.address.latitude': venue.latitude, 'venue.address.longitude': venue.longitude, } # create url to send post request to create venue url = get_url(self, Urls.VENUES) response = http_request('POST', url, params=payload, headers=self.headers, user_id=self.user.id) if response.ok: logger.info('| Venue has been created |') venue_id = response.json().get('id') venue.update(social_network_venue_id=venue_id) return venue_id else: error_message = "Venue was not Created. There are some " \ "errors: Details: %s " % response message = '\nErrors from the Social Network:\n' message += \ ''.join(response.json().get('error') + ',' + response.json().get('error_description')) error_message += message log_error({'error': error_message, 'user_id': self.user.id}) raise EventLocationNotCreated( 'ApiError: Unable to create venue for event\n %s' % message) else: error_message = 'Venue with ID = %s does not exist in db.' \ % self.venue_id log_error({ 'user_id': self.user.id, 'error': error_message, }) raise VenueNotFound('Venue not found in database. Kindly create' ' venue first.')
def get_attendee(self, rsvp): """ :param rsvp: rsvp is likely the response of social network API. :type rsvp: dict :return: attendee :rtype: object - This function is used to get the data of candidate related to given rsvp. It attaches all the information in attendee object. attendees is a utility object we share in calls that contains pertinent data. - This method is called from process_rsvps() defined in RSVPBase class. :Example: attendee = self.get_attendee(rsvp) - RSVP data return from Meetup looks like In case of RSVPs importer: { 'group': { 'group_lat': 24.860000610351562, 'created': 1439953915212, 'join_mode': 'open', 'group_lon': 67.01000213623047, 'urlname': 'Meteor-Karachi', 'id': 17900002 }, 'created': 1438040123000, 'rsvp_id': 1562651661, 'mtime': 1438040194000, 'event': { 'name': 'Welcome to Karachi - Meteor', 'id': '223588917' 'event_url': 'http://www.meetup.com/Meteor-Karachi/events/223588917/', 'time': 1440252000000, }, 'member': { 'name': 'kamran', 'member_id': 190405794 }, 'guests': 1, 'member_photo': { 'thumb_link': 'http://photos3.meetupstatic.com/photos/member/c/b/1/0/thumb_248211984.jpeg', 'photo_id': 248211984, 'highres_link': 'http://photos3.meetupstatic.com/photos/member/c/b/1/0/highres_248211984.jpeg', 'photo_link': 'http://photos3.meetupstatic.com/photos/member/c/b/1/0/member_248211984.jpeg' }, 'response': 'yes' } From streaming API: { u'group': { u'group_city': u'Denver', u'group_lat': 39.68, u'group_urlname': u'denver-metro-chadd-support', u'group_name': u'Denver-Metro CHADD (Children and Adults with ADHD) Meetup', u'group_lon': -104.92, u'group_topics': [ {u'topic_name': u'ADHD', u'urlkey': u'adhd'}, {u'topic_name': u'ADHD Support', u'urlkey': u'adhd-support'}, {u'topic_name': u'Adults with ADD', u'urlkey': u'adults-with-add'}, {u'topic_name': u'Families of Children who have ADD/ADHD', u'urlkey': u'families-of-children-who-have-add-adhd'}, {u'topic_name': u'ADHD, ADD', u'urlkey': u'adhd-add'}, {u'topic_name': u'ADHD Parents with ADHD Children', u'urlkey': u'adhd-parents-with-adhd-children'}, {u'topic_name': u'Resources for ADHD', u'urlkey': u'resources-for-adhd'}, {u'topic_name': u'Parents of Children with ADHD', u'urlkey': u'parents-of-children-with-adhd'}, {u'topic_name': u'Support Groups for Parents with ADHD Children', u'urlkey': u'support-groups-for-parents-with-adhd-children'}, {u'topic_name': u'Educators Training on AD/HD', u'urlkey': u'educators-training-on-ad-hd'}, {u'topic_name': u'Adults with ADHD', u'urlkey': u'adults-with-adhd'} ], u'group_state': u'CO', u'group_id': 1632579, u'group_country': u'us' }, u'rsvp_id': 1639776896, u'venue': {u'lat': 39.674759, u'venue_id': 3407262, u'lon': -104.936317, u'venue_name': u'Denver Academy-Richardson Hall'}, u'visibility': u'public', u'event': { u'event_name': u'Manage the Impact of Technology on Your Child and Family with Lana Gollyhorn', u'event_id': u'235574682', u'event_url': u'https://www.meetup.com/denver-metro-chadd-support/events/235574682/', u'time': 1479778200000 }, u'member': { u'member_name': u'Valerie Brown', u'member_id': 195674019 }, u'guests': 0, u'mtime': 1479312043215, u'response': u'yes' } In both cases, we get info of attendee from Meetup API and response looks like { u'status': u'active', u'city': u'Horsham', u'name': u'Katalin Nimmerfroh', u'other_services': {}, u'country': u'gb', u'topics': [ {u'name': u'Musicians', u'urlkey': u'musicians', u'id': 173}, {u'name': u'Acting', u'urlkey': u'acting', u'id': 226} ], u'lon': -0.33, u'joined': 1456995423000, u'id': 200820968, u'state': u'P6', u'link': u'http://www.meetup.com/members/200820968', u'photo': { u'thumb_link': u'http://photos2.meetupstatic.com/photos/member/4/f/f/9/thumb_254420473.jpeg', u'photo_id': 254420473, u'highres_link': u'http://photos2.meetupstatic.com/photos/member/4/f/f/9/highres_254420473.jpeg', u'base_url': u'http://photos2.meetupstatic.com', u'type': u'member', u'photo_link': u'http://photos4.meetupstatic.com/photos/member/4/f/f/9/member_254420473.jpeg' }, u'lat': 51.07, u'visited': 1472805623000, u'self': {u'common': {}}} - So we will get the member data and issue a member call to get more info about member so we can later save him as a candidate. **See Also** .. seealso:: process_rsvps() method in RSVPBase class inside social_network_service/rsvp/base.py for more insight. """ if self.rsvp_via_importer: social_network_event_id = rsvp['event']['id'] else: social_network_event_id = rsvp['event']['event_id'] event = Event.get_by_user_id_social_network_id_vendor_event_id(self.user.id, self.social_network.id, social_network_event_id) if not event: raise EventNotFound('Event is not present in db, social_network_event_id is %s. ' 'User Id: %s' % (social_network_event_id, self.user.id)) member_url = self.api_url + '/member/' + str(rsvp['member']['member_id']) # Sleep for 10 / 30 seconds to avoid throttling time.sleep(0.34) response = http_request('GET', member_url, headers=self.headers, user_id=self.user.id) if response.ok: data = response.json() attendee = Attendee() attendee.first_name = data['name'].split(" ")[0] if len(data['name'].split(" ")) > 1: attendee.last_name = data['name'].split(" ")[1] else: attendee.last_name = ' ' attendee.full_name = data['name'] attendee.city = data['city'] attendee.email = '' # Meetup API does not expose this attendee.country = data['country'] attendee.social_profile_url = data['link'] # attendee.picture_url = data['photo']['photo_link'] attendee.gt_user_id = self.user.id attendee.social_network_id = self.social_network.id attendee.rsvp_status = rsvp['response'] attendee.vendor_rsvp_id = rsvp['rsvp_id'] # TODO: This won't work now, need to figure out a way attendee.vendor_img_link = "<img class='pull-right' " \ "style='width:60px;height:30px'" \ " src='/web/static/images" \ "/activities/meetup_logo.png'/>" # get event from database epoch_time = rsvp['mtime'] dt = milliseconds_since_epoch_to_dt(epoch_time) attendee.added_time = dt attendee.event = event return attendee
def refresh_access_token(self): """ - When user authorizes Meetup account, we get a refresh token and access token. Access token expires in one hour. Here we refresh the access_token using refresh_token without user involvement and save in user_credentials db table. - This function is called from validate_and_refresh_access_token() defined in SocialNetworkBase class inside social_network_service/base.py :Example: from social_network_service.meetup import Meetup sn = Meetup(user_id=1) sn.refresh_access_token() **See Also** .. seealso:: validate_and_refresh_token() function defined in SocialNetworkBase class inside social_network_service/base.py. :return True if token has been refreshed successfully and False otherwise. :rtype: bool """ status = False user_refresh_token = self.user_credentials.refresh_token auth_url = get_url(self, SocialNetworkUrls.REFRESH_TOKEN, is_auth=True) client_id = self.social_network.client_key client_secret = self.social_network.secret_key payload_data = { 'client_id': client_id, 'client_secret': client_secret, 'grant_type': u'refresh_token', 'refresh_token': user_refresh_token } # Sleep for 10 / 30 seconds to avoid throttling time.sleep(0.34) response = http_request('POST', url=auth_url, data=payload_data, user_id=self.user.id, app=app) if response.ok: try: # access token has been refreshed successfully, need to update # self.access_token and self.headers response = response.json() self.access_token = response.get('access_token') self.headers.update( {'Authorization': 'Bearer ' + self.access_token}) refresh_token = response.get('refresh_token') UserSocialNetworkCredential.query.filter_by(social_network_id=self.social_network.id, member_id=self.user_credentials.member_id).\ update({'access_token': self.access_token, 'refresh_token': refresh_token}) db.session.commit() logger.info("Access token has been refreshed.") status = True except Exception: logger.exception('refresh_access_token: user_id: %s' % self.user.id) else: # Error has been logged inside http_request() pass return status