def record_event(request): """Endpoint to record user events.""" user_id = request.POST.get('userid') app_id = request.POST.get('appid') campaign_id = request.POST.get('campaignid') content_id = request.POST.get('contentid') content = request.POST.get('content', '') action_id = request.POST.get('actionid') event_type = request.POST.get('eventType') extend = request.POST.get('extend_token', False) (friends, friend_fbids) = ([], []) for uid in request.POST.getlist('friends[]'): if uid.isdigit(): uid = int(uid) friend_fbids.append(uid) friends.append(uid) if campaign_id: try: campaign = relational.Campaign.objects.get(campaign_id=campaign_id) except (relational.Campaign.DoesNotExist, ValueError): return http.HttpResponseBadRequest("No such campaign: {}".format(campaign_id)) else: campaign = None if event_type not in ALL_EVENTS: return http.HttpResponseForbidden("Ah, ah, ah. You didn't say the magic word") # Create event(s) according to record type # if event_type in SINGULAR_EVENTS: request.visit.events.first_or_create( event_type=event_type, defaults={ 'campaign_id': campaign_id, 'client_content_id': content_id, 'content': content, 'activity_id': action_id, } ) elif event_type in UPDATED_EVENTS: # This is (currently) just the 'heartbeat' event with transaction.atomic(): (event, created) = relational.Event.objects.select_for_update().first_or_create( event_type=event_type, visit=request.visit, defaults={ 'campaign_id': campaign_id, 'client_content_id': content_id, 'content': '1', } ) if not created: if campaign_id and not event.campaign_id: event.campaign_id = campaign_id if content_id and not event.client_content_id: event.client_content_id = content_id event.content = F('content') + 1 event.save() elif friends: db.bulk_create.delay([ relational.Event( visit_id=request.visit.visit_id, campaign_id=campaign_id, client_content_id=content_id, friend_fbid=friend if isinstance(friend, int) else None, content=content, activity_id=action_id, event_type=event_type, ) for friend in friends ]) else: db.delayed_save.delay( relational.Event( visit_id=request.visit.visit_id, campaign_id=campaign_id, client_content_id=content_id, content=content[:1028], activity_id=action_id, event_type=event_type, ) ) # Additional, event_type-specific handling # if event_type == 'authorized': try: fbid = int(user_id) appid = int(app_id) token_string = request.POST['token'] api = request.POST['api'] except (KeyError, ValueError, TypeError): fbid = appid = token_string = api = None if not all([fbid, appid, token_string, campaign, api]): msg = ("Cannot write authorization for fbid %r, appid %r, api %r " "and token %r under campaign %r") args = (user_id, app_id, request.POST.get('api'), request.POST.get('token'), campaign_id) LOG.warning(msg, *args, extra={'request': request}) return http.HttpResponseBadRequest(msg % args) campaign.client.userclients.get_or_create(fbid=fbid) if extend: extend_token.delay(fbid, appid, token_string, api) elif event_type == 'shared': root_campaign = relational.Campaign.objects.get(rootcampaign_properties__campaign=campaign_id) # Write friends with whom we shared to the exclusions table, # so we don't show them for the same content/campaign again. # Rather than wait on as many as 10 get+inserts, background the task. exclusions = [ { 'fbid': user_id, 'campaign_id': root_campaign.campaign_id, 'content_id': content_id, 'friend_fbid': friend, 'defaults': { 'reason': 'shared', } } for friend in friend_fbids ] if exclusions: db.get_or_create.delay(relational.FaceExclusion, *exclusions) # Attempt to avoid race condition between subsequent reload of faces # page and above record of exclusions by storing these in session: faces_exclusions_key = PENDING_EXCLUSIONS_KEY.format(campaign_id=root_campaign.campaign_id, content_id=content_id, fbid=user_id) request.session[faces_exclusions_key] = friend_fbids # Additional handling # error_msg = request.POST.get('errorMsg[message]') if error_msg: # may want to push these to the DB at some point, but at least for now, # dump them to the logs to ensure we keep the data. LOG.warning( 'Front-end error encountered for user %s in session %s: %s', user_id, request.session.session_key, error_msg, extra={'request': request} ) share_msg = request.POST.get('shareMsg') if share_msg: db.delayed_save.delay( relational.ShareMessage( activity_id=action_id, fbid=user_id, campaign_id=campaign_id, content_id=content_id, message=share_msg, ) ) return http.HttpResponse()
def faces(request): faces_form = forms.FacesForm(request.POST) if not faces_form.is_valid(): return JsonHttpResponse(faces_form.errors, status=400) data = faces_form.cleaned_data campaign = root_campaign = data['campaign'] content = data['content'] client = campaign.client if not request.session.get('sessionverified', False): # Avoid spamming the workers with an agent who can't hold onto its session if data['last_call']: LOG.fatal("User agent failed cookie test. (Will return error to user.)", extra={'request': request}) return http.HttpResponseForbidden("Cookies are required.") # Agent failed test, but give it another shot LOG.warning("Suspicious session missing cookie test", extra={'request': request}) request.session.set_test_cookie() return JsonHttpResponse({ 'status': 'waiting', 'reason': "Cookies are required. Please try again.", 'campaignid': campaign.pk, 'contentid': content.pk, }, status=202) faces_tasks_key = FACES_TASKS_KEY.format(api=data['api'], campaign_id=campaign.pk, content_id=content.pk, fbid=data['fbid']) targeting_task_ids = request.session.get(faces_tasks_key) if targeting_task_ids: # Retrieve statuses of active targeting tasks # ranked_tasks = [(rank, celery.current_app.AsyncResult(task_id)) for (rank, task_id) in targeting_task_ids] else: # First request # token = datastructs.ShortToken( fbid=data['fbid'], appid=client.fb_app_id, token=data['token'], api=data['api'], ) # Extend & store Token and record authorized UserClient: extend_token.delay(*token) db.get_or_create.delay( relational.UserClient, client_id=client.pk, fbid=data['fbid'], ) # Initiate targeting tasks: ranked_tasks = request_targeting( visit=request.visit, token=token, api=data['api'], campaign=campaign, client_content=content, num_faces=data['num_face'], ) targeting_task_ids = [(rank, task.id) for (rank, task) in ranked_tasks] request.session[faces_tasks_key] = targeting_task_ids (targeting_ranks, targeting_tasks) = zip(*ranked_tasks) # Check status # (primary_rank, primary_task) = ranked_tasks[0] if not all(task.ready() for task in targeting_tasks) and not primary_task.failed() and not data['last_call']: return JsonHttpResponse({ 'status': 'waiting', 'reason': "Identifying friends.", 'campaignid': campaign.pk, 'contentid': content.pk, }, status=202) # Select results # ranked_results = [ (rank, task.result if task.successful() else targeting.empty_filtering_result) for (rank, task) in ranked_tasks ] # Choose the filtered results from the highest-ranked results: for (result_rank, targeting_result) in reversed(ranked_results): if targeting_result.filtered: # Let's use this result set; # but, gather ranking data from the others as well. for (complement_rank, complement_result) in ranked_results: if complement_result is not targeting_result and complement_result.ranked: if complement_rank > result_rank: # Trust higher-ranked complement's results ranking: targeting_result = targeting_result._replace( ranked=complement_result.ranked, filtered=targeting_result.filtered.reranked(complement_result.ranked, result_rank), ) else: # Our ranking is best; # just include lower-ranked complement's scores (for reporting): targeting_result = targeting_result._replace( filtered=targeting_result.filtered.rescored(complement_result.ranked, complement_rank), ) # We're done break if not targeting_result.ranked or not targeting_result.filtered: if ( primary_task.failed() and isinstance(primary_task.result, facebook.utils.OAuthPermissionDenied) and primary_task.result.requires_review ): return JsonHttpResponse({ 'status': 'failed', 'reason': "This app has not been approved by Facebook, and is only accessible " "to admins, developers and the app's test users.\n\n" "If you are a Facebook App reviewer, please log in as " '"Open Graph Test User" and try again.', }, status=403) elif primary_task.ready(): return http.HttpResponseServerError('No friends were identified for you.') else: LOG.fatal("primary targeting task (px%s) failed to complete in the time allotted (%s)", primary_rank or 0, primary_task.task_id, extra={'request': request}) return http.HttpResponse('Response has taken too long, giving up', status=503) if targeting_result.campaign_id and targeting_result.campaign_id != campaign.pk: campaign = relational.Campaign.objects.get(pk=targeting_result.campaign_id) if targeting_result.content_id and targeting_result.content_id != content.pk: content = relational.ClientContent.objects.get(pk=targeting_result.content_id) # Apply campaign if data['efobjsrc']: fb_object = facebook.third_party.source_campaign_fbobject(campaign, data['efobjsrc']) db.delayed_save.delay( relational.Assignment.make_managed( visit_id=request.visit.pk, campaign_id=campaign.pk, content_id=content.pk, feature_row=fb_object, chosen_from_rows=None, manager=campaign.campaignfbobjects, random_assign=False, ) ) else: fb_object = campaign.campaignfbobjects.for_datetime().random_assign() db.delayed_save.delay( relational.Assignment.make_managed( visit_id=request.visit.pk, campaign_id=campaign.pk, content_id=content.pk, feature_row=fb_object, chosen_from_rows=campaign.campaignfbobjects.for_datetime(), ) ) fb_attrs = fb_object.fbobjectattribute_set.for_datetime().get() fb_object_url = 'https://%s%s?%s' % ( request.get_host(), reverse('targetshare:objects', kwargs={ 'fb_object_id': fb_object.pk, 'content_id': content.pk, }), urllib.urlencode({ 'cssslug': targeting_result.choice_set_slug, 'campaign_id': campaign.pk, }), ) fb_params = { 'fb_action_type': fb_attrs.og_action, 'fb_object_type': fb_attrs.og_type, 'fb_object_url': fb_object_url, 'fb_app_name': client.fb_app.name, 'fb_app_id': client.fb_app_id, 'fb_object_title': fb_attrs.og_title, 'fb_object_image': fb_attrs.og_image, 'fb_object_description': fb_attrs.og_description } # Record generation of suggestions (but only once per set of tasks): generated = [] gen_count = 0 for tier in targeting_result.filtered: for edge in itertools.islice(tier['edges'], MAX_FACES - gen_count): ranked_scores = ( # ((pretty rank), (pretty score)) (('' if rank is None else rank), ('N/A' if score is None else score)) for (rank, score) in ((rank, edge.get_rank_score(rank)) for rank in targeting_ranks) ) px_content = ', '.join( "px{}_score: {} ({})".format(rank, score, task.task_id) for ((rank, score), task) in zip(ranked_scores, targeting_tasks) ) generated.append({ 'visit_id': request.visit.visit_id, 'campaign_id': tier['campaign_id'], 'client_content_id': tier['content_id'], 'friend_fbid': edge.secondary.fbid, 'event_type': 'generated', 'content': u"{} : {}".format(px_content, edge.secondary.name), 'defaults': { 'event_datetime': timezone.now(), }, }) gen_count = len(generated) if gen_count >= MAX_FACES: break db.get_or_create.delay(relational.Event, *generated) # Re-apply exclusions to pick up any shares and suppressions since results first generated: faces_exclusions_key = PENDING_EXCLUSIONS_KEY.format(campaign_id=root_campaign.pk, content_id=content.pk, fbid=data['fbid']) enqueued_exclusions = request.session.get(faces_exclusions_key, ()) existing_exclusions = root_campaign.faceexclusion_set.filter( fbid=data['fbid'], content=content, ).values_list('friend_fbid', flat=True) all_exclusions = set(itertools.chain(enqueued_exclusions, existing_exclusions.iterator())) # Determine faces that can be shown (and record these): (face_friends, show_faces, shown) = ([], [], []) mapped_task_ids = dict(targeting_task_ids) eligible_edges = (edge for edge in targeting_result.filtered.iteredges() if edge.secondary.fbid is None or edge.secondary.fbid not in all_exclusions) for (edge_index, edge) in enumerate(itertools.islice(eligible_edges, MAX_FACES)): if edge_index >= data['num_face']: face_friends.append(edge.secondary) else: show_faces.append(edge.secondary) try: (score_rank, score) = edge.get_score() except LookupError: score = 'N/A' score_rank = primary_rank px_content = "px{}_score: {} ({})".format( '' if score_rank is None else score_rank, score, mapped_task_ids[score_rank], ) shown.append( relational.Event( visit_id=request.visit.visit_id, campaign_id=campaign.campaign_id, client_content_id=content.content_id, friend_fbid=edge.secondary.fbid, content=u"{} : {}".format(px_content, edge.secondary.name), event_type='shown', ) ) if not show_faces: LOG.fatal("No faces to show, (all suppressed). (Will return error to user.)", extra={'request': request}) return http.HttpResponseServerError("No friends remaining.") db.bulk_create.delay(shown) rendered_table = render_to_string('targetshare/faces_table.html', { 'msg_params': { 'sharing_prompt': fb_attrs.sharing_prompt, 'sharing_sub_header': fb_attrs.sharing_sub_header, 'sharing_button': fb_attrs.sharing_button, 'msg1_pre': fb_attrs.msg1_pre, 'msg1_post': fb_attrs.msg1_post, 'msg2_pre': fb_attrs.msg2_pre, 'msg2_post': fb_attrs.msg2_post, }, 'fb_params': fb_params, 'all_friends': [edge.secondary for edge in targeting_result.ranked], 'face_friends': face_friends, 'show_faces': show_faces, 'num_face': data['num_face'], }, context_instance=RequestContext(request)) return JsonHttpResponse({ 'status': 'success', 'campaignid': campaign.pk, 'contentid': content.pk, 'html': rendered_table, })