def frame_faces(request, api, campaign_id, content_id): campaign = get_object_or_404(relational.Campaign, campaign_id=campaign_id) campaign_properties = campaign.campaignproperties.get() if campaign_properties.root_campaign_id != campaign_properties.campaign_id: LOG.warning("Received request for non-root campaign", extra={'request': request}) raise http.Http404 try: campaign_status = campaignstatus.handle_request(request, campaign, campaign_properties) except campaignstatus.DisallowedError as exc: return exc.make_error_response(embedded=True) client = campaign.client content = get_object_or_404(client.clientcontent, content_id=content_id) canvas = bool(re.search(r'\bcanvas\b', request.resolver_match.namespace)) db.bulk_create.delay([ relational.Event( visit_id=request.visit.pk, event_type='faces_page_load', campaign_id=campaign.pk, client_content_id=content.pk, content=api, ), relational.Event( visit_id=request.visit.pk, event_type=('faces_canvas_load' if canvas else 'faces_iframe_load'), campaign_id=campaign.pk, client_content_id=content.pk, content=api, ) ]) # If the visitor passed through FB OAuth and our incoming redirector, we # may be able to retrieve the result of their store_oauth_token job, # determine their FBID & token, and eagerly start their targeting jobs. task_id_oauth = request.session.get(OAUTH_TASK_KEY) if task_id_oauth: task_oauth = celery.current_app.AsyncResult(task_id_oauth) if task_oauth.ready(): del request.session[OAUTH_TASK_KEY] # no need to do this again token = task_oauth.result if task_oauth.successful() else None if token: faces_tasks_key = FACES_TASKS_KEY.format(api=api, campaign_id=campaign_id, content_id=content_id, fbid=token.fbid) if faces_tasks_key not in request.session: # Initiate targeting tasks: targeting_tasks = request_targeting( visit=request.visit, token=token, api=api, campaign=campaign, client_content=content, num_faces=campaign_properties.num_faces, ) request.session[faces_tasks_key] = [ (rank, task.id) for (rank, task) in targeting_tasks ] page_styles = utils.assign_page_styles( request.visit, relational.Page.FRAME_FACES, campaign, content, ) (serialized_properties,) = serialize('python', (campaign_properties,)) properties = serialized_properties['fields'] for override_key, field in [ ('efsuccessurl', 'client_thanks_url'), ('eferrorurl', 'client_error_url'), ]: value = request.REQUEST.get(override_key) or properties[field] properties[field] = "{}?{}".format( reverse('targetshare:outgoing', args=[client.fb_app_id, value]), urllib.urlencode({'campaignid': campaign_id}), ) default_permissions = client.fb_app.permissions.values_list('code', flat=True) return render(request, 'targetshare/frame_faces.html', { 'fb_params': { 'fb_app_name': client.fb_app.name, 'fb_app_id': client.fb_app_id, }, 'api': api, 'default_scope': ','.join(default_permissions.iterator()), 'campaign': campaign, 'content': content, 'properties': properties, 'campaign_css': page_styles, 'canvas': canvas, 'draft_preview': campaign_status.isdraft, # Debug mode currently on for all methods of targeted sharing # However will likely just reflect the canvas var in the future 'debug_mode': True, })
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, })