Skip to content

Commit 7f8b00d

Browse files
author
EliuX
committed
feat: Close #93 Add filter to projects, project-types and time-entries
2 parents afad986 + beb8c84 commit 7f8b00d

File tree

14 files changed

+93
-22
lines changed

14 files changed

+93
-22
lines changed

commons/data_access_layer/cosmos_db.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,9 @@ class CosmosDBDao(CRUDDao):
219219
def __init__(self, repository: CosmosDBRepository):
220220
self.repository = repository
221221

222-
def get_all(self) -> list:
223-
return self.repository.find_all(partition_key_value=self.partition_key_value)
222+
def get_all(self, conditions: dict = {}) -> list:
223+
return self.repository.find_all(partition_key_value=self.partition_key_value,
224+
conditions=conditions)
224225

225226
def get(self, id):
226227
return self.repository.find(id, partition_key_value=self.partition_key_value)

commons/data_access_layer/database.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
class CRUDDao(abc.ABC):
1717
@abc.abstractmethod
18-
def get_all(self):
18+
def get_all(self, conditions: dict):
1919
raise NotImplementedError # pragma: no cover
2020

2121
@abc.abstractmethod

tests/time_tracker_api/activities/activities_namespace_test.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ def test_list_all_activities(client: FlaskClient,
6767
assert HTTPStatus.OK == response.status_code
6868
json_data = json.loads(response.data)
6969
assert [] == json_data
70-
repository_find_all_mock.assert_called_once_with(partition_key_value=tenant_id)
70+
repository_find_all_mock.assert_called_once_with(partition_key_value=tenant_id,
71+
conditions={})
7172

7273

7374
def test_get_activity_should_succeed_with_valid_id(client: FlaskClient,
@@ -258,4 +259,4 @@ def test_delete_activity_should_return_422_for_invalid_id_format(client: FlaskCl
258259

259260
assert HTTPStatus.UNPROCESSABLE_ENTITY == response.status_code
260261
repository_remove_mock.assert_called_once_with(str(invalid_id),
261-
partition_key_value=tenant_id)
262+
partition_key_value=tenant_id)

tests/time_tracker_api/api_test.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from flask_restplus.reqparse import RequestParser
2+
from pytest import fail
3+
4+
5+
def test_create_attributes_filter_with_invalid_attribute_should_fail():
6+
from time_tracker_api.api import create_attributes_filter
7+
from time_tracker_api.projects.projects_namespace import project, ns
8+
9+
try:
10+
create_attributes_filter(ns, project, ['invalid_attribute'])
11+
12+
fail("It was expected to fail")
13+
except Exception as e:
14+
assert type(e) is ValueError
15+
16+
17+
def test_create_attributes_filter_with_valid_attribute_should_succeed():
18+
from time_tracker_api.api import create_attributes_filter
19+
from time_tracker_api.projects.projects_namespace import project, ns
20+
21+
filter = create_attributes_filter(ns, project, ['name'])
22+
23+
assert filter is not None
24+
assert type(filter) is RequestParser

tests/time_tracker_api/project_types/project_types_namespace_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,4 +269,4 @@ def test_delete_project_should_return_unprocessable_entity_for_invalid_id_format
269269

270270
assert HTTPStatus.UNPROCESSABLE_ENTITY == response.status_code
271271
repository_remove_mock.assert_called_once_with(str(invalid_id),
272-
partition_key_value=tenant_id)
272+
partition_key_value=tenant_id)

tests/time_tracker_api/projects/projects_namespace_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,4 +269,4 @@ def test_delete_project_should_return_unprocessable_entity_for_invalid_id_format
269269

270270
assert HTTPStatus.UNPROCESSABLE_ENTITY == response.status_code
271271
repository_remove_mock.assert_called_once_with(str(invalid_id),
272-
partition_key_value=tenant_id)
272+
partition_key_value=tenant_id)

time_tracker_api/activities/activities_namespace.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from faker import Faker
2+
from flask import request
23
from flask_restplus import fields, Resource, Namespace
34
from flask_restplus._http import HTTPStatus
45

time_tracker_api/api.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from azure.cosmos.exceptions import CosmosResourceExistsError, CosmosResourceNotFoundError, CosmosHttpResponseError
22
from faker import Faker
33
from flask import current_app as app
4-
from flask_restplus import Api, fields
4+
from flask_restplus import Api, fields, Model
5+
from flask_restplus import namespace
56
from flask_restplus._http import HTTPStatus
7+
from flask_restplus.reqparse import RequestParser
68

79
from commons.data_access_layer.cosmos_db import CustomError
810
from time_tracker_api import security
11+
from time_tracker_api.security import UUID_REGEX
912
from time_tracker_api.version import __version__
1013

1114
faker = Faker()
@@ -18,8 +21,22 @@
1821
security="TimeTracker JWT",
1922
)
2023

21-
# For matching UUIDs
22-
UUID_REGEX = '[0-9a-f]{8}\-[0-9a-f]{4}\-4[0-9a-f]{3}\-[89ab][0-9a-f]{3}\-[0-9a-f]{12}'
24+
25+
# Filters
26+
def create_attributes_filter(ns: namespace, model: Model, filter_attrib_names: dict) -> RequestParser:
27+
attribs_parser = ns.parser()
28+
model_attributes = model.resolved
29+
for attrib in filter_attrib_names:
30+
if attrib not in model_attributes:
31+
raise ValueError(f"{attrib} is not a valid filter attribute for {model.name}")
32+
33+
attribs_parser.add_argument(attrib, required=False,
34+
store_missing=False,
35+
help="(Filter) %s " % model_attributes[attrib].description,
36+
location='args')
37+
38+
return attribs_parser
39+
2340

2441
# Common models structure
2542
common_fields = {

time_tracker_api/customers/customers_namespace.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from faker import Faker
22
from flask_restplus import Namespace, Resource, fields
33
from flask_restplus._http import HTTPStatus
4+
from flask import request
45

56
from time_tracker_api.api import common_fields
67
from time_tracker_api.customers.customers_model import create_dao

time_tracker_api/project_types/project_types_namespace.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from faker import Faker
2+
from flask import request
23
from flask_restplus import Namespace, Resource, fields
34
from flask_restplus._http import HTTPStatus
45

5-
from time_tracker_api.api import common_fields, UUID_REGEX
6+
from time_tracker_api.api import common_fields, create_attributes_filter
67
from time_tracker_api.project_types.project_types_model import create_dao
8+
from time_tracker_api.security import UUID_REGEX
79

810
faker = Faker()
911

@@ -16,7 +18,7 @@
1618
required=True,
1719
max_length=50,
1820
description='Name of the project type',
19-
example=faker.random_element(["Customer","Training","Internal"]),
21+
example=faker.random_element(["Customer", "Training", "Internal"]),
2022
),
2123
'description': fields.String(
2224
title='Description',
@@ -53,14 +55,21 @@
5355

5456
project_type_dao = create_dao()
5557

58+
attributes_filter = create_attributes_filter(ns, project_type, [
59+
"customer_id",
60+
"parent_id",
61+
])
62+
5663

5764
@ns.route('')
5865
class ProjectTypes(Resource):
5966
@ns.doc('list_project_types')
67+
@ns.expect(attributes_filter)
6068
@ns.marshal_list_with(project_type)
6169
def get(self):
6270
"""List all project types"""
63-
return project_type_dao.get_all()
71+
conditions = attributes_filter.parse_args()
72+
return project_type_dao.get_all(conditions=conditions)
6473

6574
@ns.doc('create_project_type')
6675
@ns.response(HTTPStatus.CONFLICT, 'This project type already exists')

0 commit comments

Comments
 (0)