Repository: fossasia/open-event-server

Issue:
Add filter by location in Event GET list API
Add it in both, normal get list and paginated get list. 



Discussion:

Resolving patch:
diff --git a/open_event/api/events.py b/open_event/api/events.py
--- a/open_event/api/events.py
+++ b/open_event/api/events.py
@@ -1,4 +1,4 @@
-from flask.ext.restplus import Resource, Namespace
+from flask.ext.restplus import Resource, Namespace, reqparse
 from flask import g
 
 from open_event.models.event import Event as EventModel
@@ -7,7 +7,7 @@
 from open_event.models.user import ORGANIZER
 from open_event.helpers.data import save_to_db, update_version
 
-from .helpers.helpers import get_paginated_list, requires_auth
+from .helpers.helpers import get_paginated_list, requires_auth, parse_args
 from .helpers.utils import PAGINATED_MODEL, PaginatedResourceBase, \
     PAGE_PARAMS, POST_RESPONSES, PUT_RESPONSES, BaseDAO
 from .helpers import custom_fields as fields
@@ -99,6 +99,26 @@ def update(self, event_id, data):
 DAO = EventDAO(EventModel, EVENT_POST)
 
 
+# DEFINE PARAMS
+
+EVENT_PARAMS = {
+    'location_name': {
+        'description': 'Filter by location_name',
+        'type': str
+    }
+}
+
+# DEFINE RESOURCES
+
+
+class EventResource():
+    """
+    Event Resource Base class
+    """
+    event_parser = reqparse.RequestParser()
+    event_parser.add_argument('location_name', type=str)
+
+
 @api.route('/<int:event_id>')
 @api.param('event_id')
 @api.response(404, 'Event not found')
@@ -126,12 +146,12 @@ def put(self, event_id):
 
 
 @api.route('')
-class EventList(Resource):
-    @api.doc('list_events')
+class EventList(Resource, EventResource):
+    @api.doc('list_events', params=EVENT_PARAMS)
     @api.marshal_list_with(EVENT)
     def get(self):
         """List all events"""
-        return DAO.list()
+        return DAO.list(**parse_args(self.event_parser))
 
     @requires_auth
     @api.doc('create_event', responses=POST_RESPONSES)
@@ -143,11 +163,15 @@ def post(self):
 
 
 @api.route('/page')
-class EventListPaginated(Resource, PaginatedResourceBase):
+class EventListPaginated(Resource, PaginatedResourceBase, EventResource):
     @api.doc('list_events_paginated', params=PAGE_PARAMS)
+    @api.doc(params=EVENT_PARAMS)
     @api.marshal_with(EVENT_PAGINATED)
     def get(self):
         """List events in a paginated manner"""
         args = self.parser.parse_args()
         url = self.api.url_for(self)  # WARN: undocumented way
-        return get_paginated_list(EventModel, url, args=args)
+        return get_paginated_list(
+            EventModel, url, args=args,
+            **parse_args(self.event_parser)
+        )
diff --git a/open_event/api/helpers/helpers.py b/open_event/api/helpers/helpers.py
--- a/open_event/api/helpers/helpers.py
+++ b/open_event/api/helpers/helpers.py
@@ -317,3 +317,15 @@ def no_cache(*args, **kwargs):
         response.headers['Expires'] = '-1'
         return response
     return update_wrapper(no_cache, view)
+
+
+def parse_args(parser, keep_none=False):
+    """
+    Abstraction over flask_restplus.reqparse.parser.parse_args
+    It returned None value if a value was not set
+    This completely removes that value from the returned dict
+    """
+    args = parser.parse_args()
+    if not keep_none:
+        args = {k: v for k, v in args.items() if v is not None}
+    return args
diff --git a/open_event/api/helpers/utils.py b/open_event/api/helpers/utils.py
--- a/open_event/api/helpers/utils.py
+++ b/open_event/api/helpers/utils.py
@@ -68,8 +68,8 @@ def __init__(self, model, post_api_model=None, put_api_model=None):
     def get(self, id_):
         return get_object_or_404(self.model, id_)
 
-    def list(self):
-        return get_object_list(self.model)
+    def list(self, **kwargs):
+        return get_object_list(self.model, **kwargs)
 
     def create(self, data, validate=True):
         if validate:
@@ -105,10 +105,10 @@ class ServiceDAO(BaseDAO):
     def get(self, event_id, sid):
         return get_object_in_event(self.model, sid, event_id)
 
-    def list(self, event_id):
+    def list(self, event_id, **kwargs):
         # Check if an event with `event_id` exists
         get_object_or_404(EventModel, event_id)
-        return get_object_list(self.model, event_id=event_id)
+        return get_object_list(self.model, event_id=event_id, **kwargs)
 
     def create(self, event_id, data, url, validate=True):
         if validate:


Test patch:
diff --git a/tests/api/test_get_api_listed_200.py b/tests/api/test_get_api_listed_200.py
--- a/tests/api/test_get_api_listed_200.py
+++ b/tests/api/test_get_api_listed_200.py
@@ -65,6 +65,14 @@ def test_sponsor_types_api(self):
         path = get_path(1, 'sponsors', 'types')
         self._test_path(path, 'TestSponsorType1_1', 'TestSponsorType2_1')
 
+    # special tests
+
+    def test_event_api_filters(self):
+        path = get_path() + '?location_name=r@nd0m'
+        resp = self.app.get(path)
+        self.assertTrue(len(resp.data) < 4, resp.data)
+        self.assertNotIn('TestEvent', resp.data)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/api/test_get_api_paginated.py b/tests/api/test_get_api_paginated.py
--- a/tests/api/test_get_api_paginated.py
+++ b/tests/api/test_get_api_paginated.py
@@ -127,6 +127,13 @@ def test_api(self):
         self.assertEqual(response.status_code, 200)
         self.assertIn('TestEvent', response.data)
 
+    def test_api_filters(self):
+        with app.test_request_context():
+            create_event()
+        path = get_path('page') + '?location_name=r@nd0m'
+        resp = self.app.get(path)
+        self.assertEqual(resp.status_code, 404)
+
 
 if __name__ == '__main__':
     unittest.main()