Skip to content

Commit

Permalink
Merge pull request #61 from gpodder/limit-actions
Browse files Browse the repository at this point in the history
Limit number of episode actions returned by API
  • Loading branch information
stefankoegl authored Aug 15, 2018
2 parents 52fbfe7 + e0d8967 commit 77c5314
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 1 deletion.
5 changes: 5 additions & 0 deletions doc/dev/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,8 @@ Social Login

* ``GOOGLE_CLIENT_ID`` - Google Client ID
* ``GOOGLE_CLIENT_SECRET`` - Google Client Secret


API
---
* ``MAX_EPISODE_ACTIONS`` - maximum number of episode actions that the API will return in one `GET` request.
17 changes: 16 additions & 1 deletion mygpo/api/advanced/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ def get_episode_changes(user, podcast, device, since, until, aggregated, version
history = EpisodeHistoryEntry.objects.filter(user=user,
timestamp__lt=until)

# return the earlier entries first
history = history.order_by('timestamp')

if since:
history = history.filter(timestamp__gte=since)

Expand All @@ -156,12 +159,24 @@ def get_episode_changes(user, podcast, device, since, until, aggregated, version
if version == 1:
history = map(convert_position, history)

# Limit number of returned episode actions
max_actions = dsettings.MAX_EPISODE_ACTIONS
history = history[:max_actions]

# evaluate query and turn into list, for negative indexing
history = list(history)

actions = [episode_action_json(a, user) for a in history]

if aggregated:
actions = list(dict( (a['episode'], a) for a in actions ).values())

return {'actions': actions, 'timestamp': get_timestamp(until)}
if history:
ts = get_timestamp(history[-1].timestamp)
else:
ts = get_timestamp(until)

return {'actions': actions, 'timestamp': ts}


def episode_action_json(history, user):
Expand Down
106 changes: 106 additions & 0 deletions mygpo/api/tests.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import json
import uuid
import copy
import unittest
from datetime import datetime, timedelta
from urllib.parse import urlencode

from django.test.client import Client
from django.test import TestCase
from django.urls import reverse
from django.contrib.auth import get_user_model
from django.test.utils import override_settings

from mygpo.podcasts.models import Podcast, Episode
from mygpo.api.advanced import episodes
from mygpo.history.models import EpisodeHistoryEntry
from mygpo.test import create_auth_string, anon_request
from mygpo.utils import get_timestamp


class AdvancedAPITests(unittest.TestCase):
Expand Down Expand Up @@ -186,3 +191,104 @@ def test_episode_info(self):
resp = self.client.get(url)

self.assertEqual(resp.status_code, 200)


class EpisodeActionTests(TestCase):

def setUp(self):
self.podcast = Podcast.objects.get_or_create_for_url(
'http://example.com/directory-podcast.xml',
defaults = {
'title': 'My Podcast',
},
).object
self.episode = Episode.objects.get_or_create_for_url(
self.podcast,
'http://example.com/directory-podcast/1.mp3',
defaults = {
'title': 'My Episode',
},
).object
User = get_user_model()
self.password = 'asdf'
self.username = 'adv-api-user'
self.user = User(username=self.username, email='user@example.com')
self.user.set_password(self.password)
self.user.save()
self.user.is_active = True
self.client = Client()
self.extra = {
'HTTP_AUTHORIZATION': create_auth_string(self.username,
self.password)
}

def tearDown(self):
self.episode.delete()
self.podcast.delete()
self.user.delete()

@override_settings(MAX_EPISODE_ACTIONS=10)
def test_limit_actions(self):
""" Test that max MAX_EPISODE_ACTIONS episodes are returned """

timestamps = []
t = datetime.utcnow()
for n in range(15):
timestamp = t - timedelta(seconds=n)
EpisodeHistoryEntry.objects.create(
timestamp = timestamp,
episode = self.episode,
user = self.user,
action = EpisodeHistoryEntry.DOWNLOAD,
)
timestamps.append(timestamp)

url = reverse(episodes, kwargs={
'version': '2',
'username': self.user.username,
})
response = self.client.get(url, {'since': '0'}, **self.extra)
self.assertEqual(response.status_code, 200, response.content)
response_obj = json.loads(response.content.decode('utf-8'))
actions = response_obj['actions']

# 10 actions should be returned
self.assertEqual(len(actions), 10)

timestamps = sorted(timestamps)

# the first 10 actions, according to their timestamp should be returned
for action, timestamp in zip(actions, timestamps):
self.assertEqual(timestamp.isoformat(), action['timestamp'])

# the `timestamp` field in the response should be the timestamp of the
# last returned action
self.assertEqual(
get_timestamp(timestamps[9]),
response_obj['timestamp']
)


def test_no_actions(self):
""" Test when there are no actions to return """

t1 = get_timestamp(datetime.utcnow())

url = reverse(episodes, kwargs={
'version': '2',
'username': self.user.username,
})
response = self.client.get(url, {'since': '0'}, **self.extra)
self.assertEqual(response.status_code, 200, response.content)
response_obj = json.loads(response.content.decode('utf-8'))
actions = response_obj['actions']

# 10 actions should be returned
self.assertEqual(len(actions), 0)

returned = response_obj['timestamp']
t2 = get_timestamp(datetime.utcnow())
# the `timestamp` field in the response should be the timestamp of the
# last returned action
self.assertGreaterEqual(returned, t1)
self.assertGreaterEqual(t2, returned)
2 changes: 2 additions & 0 deletions mygpo/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,4 +374,6 @@ def get_intOrNone(name, default):
PODCAST_AD_ID = os.getenv('PODCAST_AD_ID')


MAX_EPISODE_ACTIONS = int(os.getenv('MAX_EPISODE_ACTIONS', 1000))

SEARCH_CUTOFF = float(os.getenv('SEARCH_CUTOFF', 0.3))

0 comments on commit 77c5314

Please sign in to comment.