Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Add]: Movie list and Entry list batch remove endpoints #2545

Merged
merged 3 commits into from Dec 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions flexget/api/app.py
Expand Up @@ -20,7 +20,7 @@

from . import __path__

__version__ = '1.6.0'
__version__ = '1.7.0'

logger = logger.bind(name='api')

Expand Down Expand Up @@ -137,7 +137,7 @@ def response(self, code_or_apierror, description='Success', model=None, **kwargs
code_or_apierror.response_model,
)
},
**kwargs
**kwargs,
)
except TypeError:
# If first argument isn't a class this happens
Expand Down
37 changes: 37 additions & 0 deletions flexget/components/managed_lists/lists/entry_list/api.py
Expand Up @@ -58,6 +58,15 @@ class ObjectsContainer:

entry_lists_entries_return_object = {'type': 'array', 'items': entry_list_entry_base_object}

batch_ids = {'type': 'array', 'items': {'type': 'integer'}, 'uniqueItems': True, 'minItems': 1}

batch_remove_object = {
'type': 'object',
'properties': {'ids': batch_ids},
'required': ['ids'],
'additionalProperties': False,
}


entry_list_object_schema = api.schema_model(
'entry_list_object_schema', ObjectsContainer.entry_list_base_object
Expand All @@ -68,6 +77,9 @@ class ObjectsContainer:
entry_list_return_lists_schema = api.schema_model(
'entry_list_return_lists_schema', ObjectsContainer.entry_list_return_lists
)
entry_list_batch_remove_schema = api.schema_model(
'entry_list.batch_remove_object', ObjectsContainer.batch_remove_object
)

entry_list_parser = api.parser()
entry_list_parser.add_argument('name', help='Filter results by list name')
Expand Down Expand Up @@ -283,3 +295,28 @@ def put(self, list_id, entry_id, session=None):
resp = jsonify(entry.to_dict())
resp.status_code = 201
return resp


@entry_list_api.route('/<int:list_id>/entries/batch')
@api.doc(params={'list_id': 'ID of the list'})
@api.response(NotFoundError)
class EntryListEntriesBatchAPI(APIResource):
@api.response(204)
@api.validate(model=entry_list_batch_remove_schema)
@api.doc(description='Remove multiple entries')
def delete(self, list_id, session=None):
"""Remove multiple entries"""
data = request.json
entry_ids = data.get('ids')
try:
entries = db.get_entries_by_list_id(list_id, entry_ids=entry_ids, session=session)
except NoResultFound:
raise NotFoundError(f'could not find entries in list {list_id}')

for entry in entries:
session.delete(entry)
session.commit()
response = jsonify([])
response.status_code = 204

return response
10 changes: 9 additions & 1 deletion flexget/components/managed_lists/lists/entry_list/db.py
Expand Up @@ -215,10 +215,18 @@ def delete_list_by_id(list_id, session=None):

@with_session
def get_entries_by_list_id(
list_id, start=None, stop=None, order_by='title', descending=False, session=None
list_id,
start=None,
stop=None,
order_by='title',
descending=False,
entry_ids=None,
session=None,
):
logger.debug('querying entries from entry list with id {}', list_id)
query = session.query(EntryListEntry).filter(EntryListEntry.list_id == list_id)
if entry_ids:
query = query.filter(EntryListEntry.id.in_(entry_ids))
if descending:
query = query.order_by(getattr(EntryListEntry, order_by).desc())
else:
Expand Down
38 changes: 38 additions & 0 deletions flexget/components/managed_lists/lists/movie_list/api.py
Expand Up @@ -82,6 +82,15 @@ class ObjectsContainer:

return_identifiers = {'type': 'array', 'items': {'type': 'string'}}

batch_ids = {'type': 'array', 'items': {'type': 'integer'}, 'uniqueItems': True, 'minItems': 1}

batch_remove_object = {
'type': 'object',
'properties': {'ids': batch_ids},
'required': ['ids'],
'additionalProperties': False,
}


input_movie_entry_schema = api.schema_model(
'input_movie_entry', ObjectsContainer.input_movie_entry
Expand All @@ -105,6 +114,10 @@ class ObjectsContainer:
'movie_list.identifiers', ObjectsContainer.return_identifiers
)

movie_list_batch_remove_schema = api.schema_model(
'movie_list.batch_remove_object', ObjectsContainer.batch_remove_object
)

movie_list_parser = api.parser()
movie_list_parser.add_argument('name', help='Filter results by list name')

Expand Down Expand Up @@ -328,3 +341,28 @@ class MovieListIdentifiers(APIResource):
def get(self, session=None):
""" Return a list of supported movie list identifiers """
return jsonify(MovieListBase().supported_ids)


@movie_list_api.route('/<int:list_id>/movies/batch')
@api.doc(params={'list_id': 'ID of the list'})
@api.response(NotFoundError)
class MovieListEntriesBatchAPI(APIResource):
@api.response(204)
@api.validate(model=movie_list_batch_remove_schema)
@api.doc(description='Remove multiple entries')
def delete(self, list_id, session=None):
"""Remove multiple entries"""
data = request.json
movie_ids = data.get('ids')
try:
movies = db.get_movies_by_list_id(list_id, movie_ids=movie_ids, session=session)
except NoResultFound:
raise NotFoundError(f'could not find movies in list {list_id}')

for movie in movies:
session.delete(movie)
session.commit()
response = jsonify([])
response.status_code = 204

return response
10 changes: 9 additions & 1 deletion flexget/components/managed_lists/lists/movie_list/db.py
Expand Up @@ -121,9 +121,17 @@ def to_dict(self):

@with_session
def get_movies_by_list_id(
list_id, start=None, stop=None, order_by='added', descending=False, session=None
list_id,
start=None,
stop=None,
order_by='added',
descending=False,
movie_ids=None,
session=None,
):
query = session.query(MovieListMovie).filter(MovieListMovie.list_id == list_id)
if movie_ids:
query = query.filter(MovieListMovie.id.in_(movie_ids))
if descending:
query = query.order_by(getattr(MovieListMovie, order_by).desc())
else:
Expand Down
34 changes: 34 additions & 0 deletions flexget/tests/api_tests/test_entry_list_api.py
Expand Up @@ -258,6 +258,40 @@ def test_entry_list_entry(self, api_client, schema_match):
errors = schema_match(base_message, data)
assert not errors

def test_entry_list_entries_batch_remove(self, api_client, schema_match):
payload = {'name': 'test_list'}

# Create list
api_client.json_post('/entry_list/', data=json.dumps(payload))

# Add 3 entries to list
for i in range(3):
payload = {'title': f'title {i}', 'original_url': f'http://{i}test.com'}
rsp = api_client.json_post('/entry_list/1/entries/', data=json.dumps(payload))
assert rsp.status_code == 201

# get entries is correct
rsp = api_client.get('/entry_list/1/entries/')
assert rsp.status_code == 200
data = json.loads(rsp.get_data(as_text=True))

errors = schema_match(OC.entry_lists_entries_return_object, data)
assert not errors
assert len(data) == 3

payload = {'ids': [1, 2, 3]}

rsp = api_client.json_delete('entry_list/1/entries/batch', data=json.dumps(payload))
assert rsp.status_code == 204

rsp = api_client.get('/entry_list/1/entries/')
assert rsp.status_code == 200
data = json.loads(rsp.get_data(as_text=True))

errors = schema_match(OC.entry_lists_entries_return_object, data)
assert not errors
assert not data


class TestEntryListPagination:
config = 'tasks: {}'
Expand Down
34 changes: 34 additions & 0 deletions flexget/tests/api_tests/test_movie_list_api.py
Expand Up @@ -322,6 +322,40 @@ def test_identifiers(self, api_client, schema_match):
identifiers = MovieListBase().supported_ids
assert data == identifiers

def test_movie_list_movies_batch_remove(self, api_client, schema_match):
payload = {'name': 'test_list'}

# Create list
api_client.json_post('/movie_list/', data=json.dumps(payload))

# Add 3 entries to list
for i in range(3):
payload = {'movie_name': f'movie {i}', 'movie_year': 2000 + i}
rsp = api_client.json_post('/movie_list/1/movies/', data=json.dumps(payload))
assert rsp.status_code == 201

# get entries is correct
rsp = api_client.get('/movie_list/1/movies/')
assert rsp.status_code == 200
data = json.loads(rsp.get_data(as_text=True))

errors = schema_match(OC.return_movies, data)
assert not errors
assert len(data) == 3

payload = {'ids': [1, 2, 3]}

rsp = api_client.json_delete('movie_list/1/movies/batch', data=json.dumps(payload))
assert rsp.status_code == 204

rsp = api_client.get('/movie_list/1/movies/')
assert rsp.status_code == 200
data = json.loads(rsp.get_data(as_text=True))

errors = schema_match(OC.return_movies, data)
assert not errors
assert not data


class TestMovieListPagination:
config = 'tasks: {}'
Expand Down