implement impression api endpoint for sponsored shelf (#15687)
* implement impression api endpoint for sponsored shelf * update docs for impression endpoint
This commit is contained in:
Родитель
87e82fa110
Коммит
dfcde84625
|
@ -101,4 +101,19 @@ Current implementation relies on Adzerk to determine which addons are returned a
|
|||
:>json string results[].click_url: the url to ping if the sponsored addon's detail page is navigated to.
|
||||
:>json string results[].click_data: the data payload to send to ``click_url`` that identifies the sponsored placement clicked on.
|
||||
:>json string impression_url: the url to ping when the contents of this sponsored shelf is rendered on screen to the user.
|
||||
:>json string impression_data: the data payload to send to ``impression_url`` that identifies the sponsored placements displayed.
|
||||
:>json string impression_data: the signed data payload to send to ``impression_url`` that identifies the sponsored placements displayed.
|
||||
|
||||
|
||||
---------------------------
|
||||
Sponsored Shelf Impressions
|
||||
---------------------------
|
||||
|
||||
.. _sponsored-shelf-impression:
|
||||
|
||||
When the sponsored shelf is displayed for the user this endpoint can be used to record the impressions.
|
||||
The current implemenation forwards these impression pings to Adzerk.
|
||||
|
||||
|
||||
.. http:post:: /api/v4/shelves/sponsored/impression/
|
||||
|
||||
:form string impression_data: the signed data payload that was sent in the :ref:`sponsored shelf <sponsored-shelf>` response.
|
||||
|
|
|
@ -1940,3 +1940,5 @@ ADZERK_TIMEOUT = 5 # seconds
|
|||
ADZERK_NETWORK_ID = env('ADZERK_NETWORK_ID', default=10521)
|
||||
ADZERK_SITE_ID = env('ADZERK_SITE_ID', default=1131244)
|
||||
ADZERK_URL = f'https://e-{ADZERK_NETWORK_ID}.adzerk.net/api/v2'
|
||||
ADZERK_IMPRESSION_URL = f'https://e-{ADZERK_NETWORK_ID}.adzerk.net/i.gif?'
|
||||
ADZERK_IMPRESSION_TIMEOUT = 60 # seconds
|
||||
|
|
|
@ -1,14 +1,26 @@
|
|||
import json
|
||||
import os
|
||||
from unittest import mock
|
||||
from collections import namedtuple
|
||||
from datetime import timedelta
|
||||
from unittest import mock, TestCase
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.signing import TimestampSigner
|
||||
from django.utils.encoding import force_bytes
|
||||
|
||||
from rest_framework.exceptions import APIException
|
||||
|
||||
import responses
|
||||
from freezegun import freeze_time
|
||||
|
||||
from ..utils import (
|
||||
call_adzerk_server, get_addons_from_adzerk, process_adzerk_results)
|
||||
call_adzerk_server,
|
||||
filter_adzerk_results_to_es_results_qs,
|
||||
get_addons_from_adzerk,
|
||||
get_signed_impression_blob_from_results,
|
||||
get_impression_data_from_signed_blob,
|
||||
process_adzerk_results,
|
||||
send_impression_pings)
|
||||
|
||||
|
||||
# This is a copy of a response from adzerk (with click and impressions trimmed)
|
||||
|
@ -26,12 +38,12 @@ def test_get_addons_from_adzerk_full():
|
|||
results = get_addons_from_adzerk(4)
|
||||
assert results == {
|
||||
'415198': {
|
||||
'impression': 'e=eyJ2IjLtgBug',
|
||||
'click': 'e=eyJ2IjoiMS42IiwiYXLCA',
|
||||
'impression': 'e%3DeyJ2IjLtgBug',
|
||||
'click': 'e%3DeyJ2IjoiMS42IiwiYXLCA',
|
||||
'addon_id': '415198'},
|
||||
'566314': {
|
||||
'impression': 'e=eyJ2IjoNtSz8',
|
||||
'click': 'e=eyJ2IjoiMS42IiwiYU5Hw',
|
||||
'impression': 'e%3DeyJ2IjoNtSz8',
|
||||
'click': 'e%3DeyJ2IjoiMS42IiwiYU5Hw',
|
||||
'addon_id': '566314'},
|
||||
}
|
||||
|
||||
|
@ -42,12 +54,10 @@ def test_call_adzerk_server(statsd_mock):
|
|||
responses.POST,
|
||||
settings.ADZERK_URL,
|
||||
json=adzerk_json)
|
||||
placeholders = ['div0', 'div1', 'div2']
|
||||
site_id = settings.ADZERK_SITE_ID
|
||||
network_id = settings.ADZERK_NETWORK_ID
|
||||
results = call_adzerk_server(placeholders)
|
||||
assert responses.calls[0].request.body == force_bytes(json.dumps(
|
||||
{'placements': [
|
||||
data = {
|
||||
'placements': [
|
||||
{
|
||||
"divName": 'div0',
|
||||
"networkId": network_id,
|
||||
|
@ -63,7 +73,10 @@ def test_call_adzerk_server(statsd_mock):
|
|||
"networkId": network_id,
|
||||
"siteId": site_id,
|
||||
"adTypes": [5]},
|
||||
]}))
|
||||
]}
|
||||
results = call_adzerk_server(settings.ADZERK_URL, data)
|
||||
assert responses.calls[0].request.body == force_bytes(json.dumps(
|
||||
data))
|
||||
assert 'div0' in results['decisions']
|
||||
assert 'div1' in results['decisions']
|
||||
# we're using a real response that only contains two divs
|
||||
|
@ -72,12 +85,10 @@ def test_call_adzerk_server(statsd_mock):
|
|||
|
||||
@mock.patch('olympia.shelves.utils.statsd.incr')
|
||||
def test_call_adzerk_server_empty_response(statsd_mock):
|
||||
placeholders = ['div1', 'div2', 'div3']
|
||||
|
||||
responses.add(
|
||||
responses.POST,
|
||||
settings.ADZERK_URL)
|
||||
results = call_adzerk_server(placeholders)
|
||||
results = call_adzerk_server(settings.ADZERK_URL)
|
||||
assert results == {}
|
||||
statsd_mock.assert_called_with('services.adzerk.fail')
|
||||
|
||||
|
@ -87,7 +98,7 @@ def test_call_adzerk_server_empty_response(statsd_mock):
|
|||
settings.ADZERK_URL,
|
||||
status=500,
|
||||
json={})
|
||||
results = call_adzerk_server(placeholders)
|
||||
results = call_adzerk_server(settings.ADZERK_URL)
|
||||
assert results == {}
|
||||
statsd_mock.assert_called_with('services.adzerk.fail')
|
||||
|
||||
|
@ -99,8 +110,8 @@ def test_process_adzerk_results():
|
|||
response = {
|
||||
'decisions': {
|
||||
'foo1': {
|
||||
"clickUrl": "https://e-9999.adzerk.net/r?e=eyJ2IjoiMS42IiwiA",
|
||||
"impressionUrl": "https://e-9999.adzerk.net/i.gif?e=eyJ2IjLg",
|
||||
"clickUrl": "https://e-9999.adzerk.net/r?e=eyJ2IjoiMS42I&iwiA",
|
||||
"impressionUrl": "https://e-9999.adzerk.net/i.gif?e=eyJ2I&jLg",
|
||||
"contents": [{
|
||||
"data": {
|
||||
"customData": {
|
||||
|
@ -160,13 +171,13 @@ def test_process_adzerk_results():
|
|||
results = process_adzerk_results(response, placeholders)
|
||||
assert results == {
|
||||
'1234': {
|
||||
'impression': 'e=eyJ2IjLg',
|
||||
'click': 'e=eyJ2IjoiMS42IiwiA',
|
||||
'impression': 'e%3DeyJ2I%26jLg',
|
||||
'click': 'e%3DeyJ2IjoiMS42I%26iwiA',
|
||||
'addon_id': '1234',
|
||||
},
|
||||
'1': {
|
||||
'impression': 'e=thesfsg',
|
||||
'click': 'e=ey44545',
|
||||
'impression': 'e%3Dthesfsg',
|
||||
'click': 'e%3Dey44545',
|
||||
'addon_id': '1',
|
||||
}
|
||||
}
|
||||
|
@ -175,14 +186,110 @@ def test_process_adzerk_results():
|
|||
@mock.patch('olympia.shelves.utils.process_adzerk_results')
|
||||
@mock.patch('olympia.shelves.utils.call_adzerk_server')
|
||||
def test_get_addons_from_adzerk(call_server_mock, process_mock):
|
||||
site_id = settings.ADZERK_SITE_ID
|
||||
network_id = settings.ADZERK_NETWORK_ID
|
||||
data = {
|
||||
'placements': [
|
||||
{
|
||||
"divName": 'div0',
|
||||
"networkId": network_id,
|
||||
"siteId": site_id,
|
||||
"adTypes": [5]},
|
||||
{
|
||||
"divName": 'div1',
|
||||
"networkId": network_id,
|
||||
"siteId": site_id,
|
||||
"adTypes": [5]},
|
||||
]}
|
||||
|
||||
call_server_mock.return_value = None
|
||||
process_mock.return_value = {'thing': {}}
|
||||
assert get_addons_from_adzerk(2) == {}
|
||||
call_server_mock.assert_called_with(['div0', 'div1'])
|
||||
call_server_mock.assert_called_with(
|
||||
settings.ADZERK_URL, data)
|
||||
process_mock.assert_not_called()
|
||||
|
||||
call_server_mock.return_value = {'something': 'something'}
|
||||
assert get_addons_from_adzerk(3) == {'thing': {}}
|
||||
call_server_mock.assert_called_with(['div0', 'div1', 'div2'])
|
||||
data['placements'].append(
|
||||
{
|
||||
"divName": 'div2',
|
||||
"networkId": network_id,
|
||||
"siteId": site_id,
|
||||
"adTypes": [5]},
|
||||
)
|
||||
call_server_mock.assert_called_with(
|
||||
settings.ADZERK_URL, data)
|
||||
process_mock.assert_called_with(
|
||||
{'something': 'something'}, ['div0', 'div1', 'div2'])
|
||||
|
||||
|
||||
@mock.patch('olympia.shelves.utils.statsd.incr')
|
||||
def test_send_impression_pings(incr_mock):
|
||||
impressions = [
|
||||
'e%3DeyJ2IjLtg%26Bug',
|
||||
'e%3DeyJ2IjoNtSz8',
|
||||
]
|
||||
responses.add(
|
||||
responses.GET,
|
||||
settings.ADZERK_IMPRESSION_URL + 'e=eyJ2IjLtg&Bug')
|
||||
responses.add(
|
||||
responses.GET,
|
||||
settings.ADZERK_IMPRESSION_URL + 'e=eyJ2IjoNtSz8')
|
||||
|
||||
send_impression_pings(impressions)
|
||||
incr_mock.assert_called_with('services.adzerk.impression.success')
|
||||
|
||||
|
||||
def test_filter_adzerk_results_to_es_results_qs():
|
||||
results = {
|
||||
'99': {},
|
||||
'123': {},
|
||||
'33': {},
|
||||
}
|
||||
hit = namedtuple('hit', 'id')
|
||||
es_results = [
|
||||
hit(99),
|
||||
hit(66),
|
||||
hit(33),
|
||||
]
|
||||
filter_adzerk_results_to_es_results_qs(results, es_results)
|
||||
assert results == {
|
||||
'99': {},
|
||||
'33': {}
|
||||
}
|
||||
|
||||
|
||||
@freeze_time('2020-01-01')
|
||||
def test_get_signed_impression_blob_from_results():
|
||||
signer = TimestampSigner()
|
||||
results = {
|
||||
'66': {
|
||||
'addon_id': 66,
|
||||
'impression': '123456',
|
||||
'click': 'abcdef'},
|
||||
'99': {
|
||||
'addon_id': 99,
|
||||
'impression': '012345',
|
||||
'click': 'bcdefg'},
|
||||
}
|
||||
blob = get_signed_impression_blob_from_results(results)
|
||||
assert blob == signer.sign('123456,012345')
|
||||
assert blob == '123456,012345:1imRQe:bYOCLk1ZS18trP34EnE8Ph5ykFI'
|
||||
|
||||
|
||||
def test_get_impression_data_from_signed_blob():
|
||||
blob = '123456,012345:1imRQe:bYOCLk1ZS18trP34EnE8Ph5ykFI'
|
||||
with freeze_time('2020-01-01') as freezer:
|
||||
# bad
|
||||
with TestCase.assertRaises(None, APIException):
|
||||
get_impression_data_from_signed_blob('.' + blob)
|
||||
|
||||
# good
|
||||
impressions = get_impression_data_from_signed_blob(blob)
|
||||
assert impressions == ['123456', '012345']
|
||||
|
||||
# good, but now stale
|
||||
freezer.tick(delta=timedelta(seconds=61))
|
||||
with TestCase.assertRaises(None, APIException):
|
||||
get_impression_data_from_signed_blob(blob)
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import json
|
||||
from datetime import timedelta
|
||||
from unittest import mock
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.signing import TimestampSigner
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from freezegun import freeze_time
|
||||
|
||||
from olympia import amo
|
||||
from olympia.amo.tests import (
|
||||
addon_factory, APITestClient, ESTestCase, reverse_ns)
|
||||
|
@ -168,7 +172,6 @@ class TestSponsoredShelfViewSet(ESTestCase):
|
|||
assert data['page_count'] == 1
|
||||
assert data['impression_url'] == reverse_ns(
|
||||
'sponsored-shelf-impression')
|
||||
assert data['impression_data'] is None
|
||||
return data
|
||||
|
||||
def test_no_adzerk_addons(self):
|
||||
|
@ -178,8 +181,11 @@ class TestSponsoredShelfViewSet(ESTestCase):
|
|||
get.assert_called_with(6)
|
||||
assert data['count'] == 0
|
||||
assert len(data['results']) == 0
|
||||
assert data['impression_data'] is None
|
||||
|
||||
@freeze_time('2020-01-01')
|
||||
def test_basic(self):
|
||||
signer = TimestampSigner()
|
||||
with mock.patch('olympia.shelves.views.get_addons_from_adzerk') as get:
|
||||
get.return_value = {
|
||||
str(self.sponsored_ext.id): {
|
||||
|
@ -195,10 +201,13 @@ class TestSponsoredShelfViewSet(ESTestCase):
|
|||
get.assert_called_with(6), get.call_args
|
||||
assert data['count'] == 2
|
||||
assert len(data['results']) == 2
|
||||
assert data['impression_data'] == signer.sign('123456,012345')
|
||||
assert {itm['id'] for itm in data['results']} == {
|
||||
self.sponsored_ext.pk, self.sponsored_theme.pk}
|
||||
|
||||
@freeze_time('2020-01-01')
|
||||
def test_adzerk_returns_none_sponsored(self):
|
||||
signer = TimestampSigner()
|
||||
with mock.patch('olympia.shelves.views.get_addons_from_adzerk') as get:
|
||||
get.return_value = {
|
||||
str(self.sponsored_ext.id): {
|
||||
|
@ -227,6 +236,7 @@ class TestSponsoredShelfViewSet(ESTestCase):
|
|||
# non sponsored are ignored
|
||||
assert data['count'] == 2
|
||||
assert len(data['results']) == 2
|
||||
assert data['impression_data'] == signer.sign('123456,012345')
|
||||
assert {itm['id'] for itm in data['results']} == {
|
||||
self.sponsored_ext.pk, self.sponsored_theme.pk}
|
||||
|
||||
|
@ -238,15 +248,42 @@ class TestSponsoredShelfViewSet(ESTestCase):
|
|||
get.assert_called_with(4)
|
||||
assert data['count'] == 0
|
||||
assert len(data['results']) == 0
|
||||
assert data['impression_data'] is None
|
||||
|
||||
def test_impression_endpoint(self):
|
||||
@mock.patch('olympia.shelves.views.send_impression_pings')
|
||||
def test_impression_endpoint(self, send_mock):
|
||||
url = reverse_ns('sponsored-shelf-impression')
|
||||
with self.assertNumQueries(0):
|
||||
response = self.client.post(url)
|
||||
assert response.status_code == 200
|
||||
# no data
|
||||
response = self.client.post(url)
|
||||
assert response.status_code == 400
|
||||
send_mock.assert_not_called()
|
||||
|
||||
# bad data
|
||||
response = self.client.post(url, {'impression_data': 'dfdfd:3434'})
|
||||
assert response.status_code == 400
|
||||
send_mock.assert_not_called()
|
||||
|
||||
# good data
|
||||
signer = TimestampSigner()
|
||||
impressions = ['assfsf', 'fwafsf']
|
||||
data = signer.sign(','.join(impressions))
|
||||
response = self.client.post(url, {'impression_data': data})
|
||||
assert response.status_code == 202
|
||||
send_mock.assert_called_with(impressions)
|
||||
assert response.content == b''
|
||||
|
||||
# good data but stale
|
||||
send_mock.reset_mock()
|
||||
with freeze_time('2020-01-01') as freezer:
|
||||
data = signer.sign(','.join(impressions))
|
||||
freezer.tick(delta=timedelta(seconds=61))
|
||||
response = self.client.post(url, {'impression_data': data})
|
||||
assert response.status_code == 400
|
||||
send_mock.assert_not_called()
|
||||
|
||||
def test_click_endpoint(self):
|
||||
url = reverse_ns('sponsored-shelf-click')
|
||||
with self.assertNumQueries(0):
|
||||
response = self.client.post(url)
|
||||
assert response.status_code == 200
|
||||
assert response.content == b''
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
from urllib.parse import urlparse
|
||||
from urllib.parse import quote, unquote, urlparse
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.signing import BadSignature, SignatureExpired, TimestampSigner
|
||||
|
||||
import requests
|
||||
from django_statsd.clients import statsd
|
||||
from rest_framework.exceptions import APIException
|
||||
|
||||
import olympia.core.logger
|
||||
|
||||
|
@ -11,26 +13,15 @@ import olympia.core.logger
|
|||
log = olympia.core.logger.getLogger('z.shelves')
|
||||
|
||||
|
||||
def call_adzerk_server(placeholders):
|
||||
"""Call adzerk server to get sponsored addon results.
|
||||
|
||||
`placeholders` is a list of arbitrary strings that we pass so we can
|
||||
identify the order of the results in the response dict."""
|
||||
site_id = settings.ADZERK_SITE_ID
|
||||
network_id = settings.ADZERK_NETWORK_ID
|
||||
placements = [
|
||||
{"divName": ph,
|
||||
"networkId": network_id,
|
||||
"siteId": site_id,
|
||||
"adTypes": [5]} for ph in placeholders]
|
||||
|
||||
def call_adzerk_server(url, json_data=None):
|
||||
"""Call adzerk server to get sponsored addon results."""
|
||||
json_response = {}
|
||||
try:
|
||||
log.info('Calling adzerk')
|
||||
with statsd.timer('services.adzerk'):
|
||||
response = requests.post(
|
||||
settings.ADZERK_URL,
|
||||
json={'placements': placements},
|
||||
url,
|
||||
json=json_data,
|
||||
timeout=settings.ADZERK_TIMEOUT)
|
||||
if response.status_code != 200:
|
||||
raise requests.exceptions.RequestException()
|
||||
|
@ -46,15 +37,31 @@ def call_adzerk_server(placeholders):
|
|||
return json_response
|
||||
|
||||
|
||||
def ping_adzerk_server(url, type='impression'):
|
||||
"""Ping adzerk server for impression/clicks"""
|
||||
try:
|
||||
log.info('Calling adzerk')
|
||||
with statsd.timer('services.adzerk'):
|
||||
response = requests.get(
|
||||
url,
|
||||
timeout=settings.ADZERK_TIMEOUT)
|
||||
if response.status_code != 200:
|
||||
raise requests.exceptions.RequestException()
|
||||
except requests.exceptions.RequestException as e:
|
||||
log.exception('Calling adzerk failed: %s', e)
|
||||
statsd.incr(f'services.adzerk.{type}.fail')
|
||||
else:
|
||||
statsd.incr(f'services.adzerk.{type}.success')
|
||||
|
||||
|
||||
def process_adzerk_result(decision):
|
||||
contents = (decision.get('contents') or [{}])[0]
|
||||
return {
|
||||
'impression': urlparse(decision.get('impressionUrl', '')).query,
|
||||
'click': urlparse(decision.get('clickUrl', '')).query,
|
||||
'impression': quote(urlparse(decision.get('impressionUrl', '')).query),
|
||||
'click': quote(urlparse(decision.get('clickUrl', '')).query),
|
||||
'addon_id':
|
||||
contents.get('data', {}).get('customData', {}).get('id', None)
|
||||
}
|
||||
return
|
||||
|
||||
|
||||
def process_adzerk_results(response, placeholders):
|
||||
|
@ -72,8 +79,48 @@ def process_adzerk_results(response, placeholders):
|
|||
|
||||
def get_addons_from_adzerk(count):
|
||||
placeholders = [f'div{i}' for i in range(count)]
|
||||
response = call_adzerk_server(placeholders)
|
||||
site_id = settings.ADZERK_SITE_ID
|
||||
network_id = settings.ADZERK_NETWORK_ID
|
||||
placements = [
|
||||
{"divName": ph,
|
||||
"networkId": network_id,
|
||||
"siteId": site_id,
|
||||
"adTypes": [5]} for ph in placeholders]
|
||||
url = settings.ADZERK_URL
|
||||
response = call_adzerk_server(url, {'placements': placements})
|
||||
results_dict = (
|
||||
process_adzerk_results(response, placeholders) if response else {})
|
||||
log.debug(f'{results_dict=}')
|
||||
return results_dict
|
||||
|
||||
|
||||
def send_impression_pings(impressions):
|
||||
base_url = settings.ADZERK_IMPRESSION_URL
|
||||
urls = [f'{base_url}{unquote(impression)}' for impression in impressions]
|
||||
for url in urls:
|
||||
ping_adzerk_server(url, type='impression')
|
||||
|
||||
|
||||
def filter_adzerk_results_to_es_results_qs(results, es_results_qs):
|
||||
results_ids = [str(hit.id) for hit in es_results_qs]
|
||||
for key in tuple(results.keys()):
|
||||
if key not in results_ids:
|
||||
results.pop(key)
|
||||
|
||||
|
||||
def get_signed_impression_blob_from_results(adzerk_results):
|
||||
impressions = [
|
||||
result.get('impression') for result in adzerk_results.values()
|
||||
if result.get('impression')]
|
||||
if not impressions:
|
||||
return None
|
||||
signer = TimestampSigner()
|
||||
return signer.sign(','.join(impressions))
|
||||
|
||||
|
||||
def get_impression_data_from_signed_blob(blob):
|
||||
signer = TimestampSigner()
|
||||
try:
|
||||
return signer.unsign(
|
||||
blob, settings.ADZERK_IMPRESSION_TIMEOUT).split(',')
|
||||
except (BadSignature, SignatureExpired) as e:
|
||||
raise APIException(e)
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
from django.db.transaction import non_atomic_requests
|
||||
|
||||
from elasticsearch_dsl import Q, query
|
||||
from rest_framework import mixins, viewsets
|
||||
from rest_framework import mixins, status, viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import APIException
|
||||
from rest_framework.response import Response
|
||||
|
||||
from olympia.addons.views import AddonSearchView
|
||||
|
@ -12,7 +13,12 @@ from olympia.search.filters import ReviewedContentFilter
|
|||
|
||||
from .models import Shelf
|
||||
from .serializers import ESSponsoredAddonSerializer, ShelfSerializer
|
||||
from .utils import get_addons_from_adzerk
|
||||
from .utils import (
|
||||
get_addons_from_adzerk,
|
||||
get_impression_data_from_signed_blob,
|
||||
get_signed_impression_blob_from_results,
|
||||
filter_adzerk_results_to_es_results_qs,
|
||||
send_impression_pings)
|
||||
|
||||
|
||||
class ShelfViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
|
||||
|
@ -35,28 +41,37 @@ class SponsoredShelfViewSet(viewsets.ViewSetMixin, AddonSearchView):
|
|||
def as_view(cls, actions, **initkwargs):
|
||||
return non_atomic_requests(super().as_view(actions, **initkwargs))
|
||||
|
||||
def get_impression_data(self):
|
||||
return None
|
||||
|
||||
def get_paginated_response(self, data):
|
||||
response = super().get_paginated_response(data)
|
||||
response.data['impression_url'] = self.reverse_action('impression')
|
||||
response.data['impression_data'] = self.get_impression_data()
|
||||
response.data['impression_data'] = (
|
||||
get_signed_impression_blob_from_results(self.adzerk_results))
|
||||
return response
|
||||
|
||||
def filter_queryset(self, qs):
|
||||
qs = super().filter_queryset(qs)
|
||||
count = self.paginator.get_page_size(self.request)
|
||||
results = get_addons_from_adzerk(count)
|
||||
ids = list(results.keys())
|
||||
self.adzerk_results = get_addons_from_adzerk(count)
|
||||
ids = list(self.adzerk_results.keys())
|
||||
results_qs = qs.query(query.Bool(must=[
|
||||
Q('terms', id=ids),
|
||||
Q('term', **{'promoted.group_id': VERIFIED_ONE.id})]))
|
||||
results_qs.execute() # To cache the results.
|
||||
filter_adzerk_results_to_es_results_qs(
|
||||
self.adzerk_results, results_qs)
|
||||
return results_qs
|
||||
|
||||
@action(detail=False, methods=['post'])
|
||||
def impression(self, request):
|
||||
return Response()
|
||||
signed_impressions = request.data.get('impression_data', '')
|
||||
try:
|
||||
send_impression_pings(
|
||||
get_impression_data_from_signed_blob(signed_impressions))
|
||||
except APIException as e:
|
||||
return Response(
|
||||
f'Bad impression_data: {e}',
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(status=status.HTTP_202_ACCEPTED)
|
||||
|
||||
@action(detail=False, methods=['post'])
|
||||
def click(self, request):
|
||||
|
|
Загрузка…
Ссылка в новой задаче