Enhanced filtering abilities in the API

Introduce new operators and let user combine filters.
This commit is contained in:
Lucas Verney 2018-10-18 09:01:47 +02:00
parent 3f86fc21af
commit 0f03700f71
2 changed files with 85 additions and 3 deletions

View File

@ -8,3 +8,70 @@ API token (see the doc about deployment for more infos).
A helper script is available under `scripts/api_doc.py` to export a
documentation of the available API endpoints and usage in the current version
of the code.
### Pagination
All the API endpoints support pagination. By default, no pagination is done.
You can force pagination specifying a `page[size]` GET parameter to specify a
number of items per page and a `page[number]` GET parameter to specify the
page to return. Pages are numbered starting from zero.
For instance,
```
> GET /api/v1/reports?page[size]=10&page[number]=0
```
will return the ten first reports of the first page, which are the ten first
reports from the database.
### Filtering
#### Basic filtering
Filtering is possible in the API through the use of a `filter` GET parameter.
You can filter on a given field value using the parameter value
`filter[FIELD]=VALUE`. For instance,
```
> GET /api/v1/reports?filter[id]=1
```
will return the reports with `id` 1.
#### Combining filters
All provided filters must be filled for an item to be returned. That is, if
you want to return all the reports of type `interrupt` with no `upvotes`, you
can use
```
> GET /api/v1/reports?filter[type]=interrupt&filter[upvotes]=0
```
If you want to make an `OR` condition, satisfying either one of the filters,
you should make two different API calls.
#### Commplex filtering
You can also use more complex elaborations through the syntax
`filter[FIELD][OPERATION]=VALUE`. The available operations are `eq` (equal,
default operation), `ne` (not equal), `gt` (greater than), `ge` (greater or
equal), `lt` (lower than) and `le` (lower or equal). For instance,
```
> GET /api/v1/reports?filter[id][ne]=1
```
will return all the reports except the one with `id` 1.
With the filters combination capability, you can use this to get all the
reports in a geographical bounding box:
```
> GET /api/v1/reports?filter[lat][gt]=LAT_MIN&filter[lat][lt]=LAT_MAX&filter[lng][gt]=LNG_MIN&filter[lng][lt]=LNG_MAX
```

View File

@ -10,7 +10,7 @@ import re
import bottle
FILTER_RE = re.compile(r"filter\[([A-z0-9_]+)\]")
FILTER_RE = re.compile(r"filter\[([A-z0-9_]+?)\](\[([A-z0-9_]+)\])?")
class DateAwareJSONEncoder(json.JSONEncoder):
@ -73,8 +73,23 @@ def JsonApiParseQuery(query, model, default_sorting=None):
if not filter_match:
continue
field = getattr(model, filter_match.group(1))
value = query[filter_match.group(0)]
filters.append(field == value)
for value in query.getall(param):
# Handle operation
operation = filter_match.group(3)
if operation == 'eq' or operation is None:
filters.append(field == value)
elif operation == 'ne':
filters.append(field != value)
elif operation == 'gt':
filters.append(field > value)
elif operation == 'ge':
filters.append(field >= value)
elif operation == 'lt':
filters.append(field < value)
elif operation == 'le':
filters.append(field <= value)
else:
raise ValueError("Invalid filtering operator provided.")
# Handle pagination according to JSON API spec
page_number, page_size = 0, None