Skip to content

Commit 21d44d7

Browse files
feat: TT-43 return related items to projects (#293)
* feat: TT-43 create decorators to add new custom attributes to response * feat: TT-43 add unit test to extend_model * fix: TT-43 Fixing decorators errors on tests * fix: TT-43 Fixing code smells * fix: TT-43 Remove unnecesary commas Co-authored-by: jcalarcon98 <[email protected]>
1 parent 844b24c commit 21d44d7

File tree

5 files changed

+147
-6
lines changed

5 files changed

+147
-6
lines changed

.gitignore

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,4 @@ migration_status.csv
3838
.DS_Store
3939

4040
# windows env variables
41-
.env.bat
42-
# mac / linux env variables
43-
.env
44-
#PowerShell env variables
45-
.env.ps1
41+
env.*

tests/utils/extend_model_test.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from unittest.mock import patch
2+
from utils.extend_model import add_custom_attribute
3+
4+
5+
@patch('time_tracker_api.project_types.project_types_model.create_dao')
6+
@patch('time_tracker_api.customers.customers_model.create_dao')
7+
def test_add_custom_attribute(customers_create_dao, projects_create_dao):
8+
@add_custom_attribute('project_type', projects_create_dao)
9+
@add_custom_attribute('customer', customers_create_dao)
10+
@patch('time_tracker_api.projects.projects_model.ProjectCosmosDBModel')
11+
def fn(project):
12+
project.return_value.name = "Franklin, Mcdonald and Morrison"
13+
project.return_value.description = "Include speech feeling court almost country smile economy. True quality mention key. Similar provide yard."
14+
project.return_value.customer_id = (
15+
"9afbfa3a-9de4-4b90-a1b7-a53d2c17a178"
16+
)
17+
project.return_value.project_type_id = (
18+
"208aadb7-1ec1-4a67-a0b0-e0308d27045b"
19+
)
20+
project.return_value.technologies = "['python', 'restplus', 'openapi']"
21+
project.return_value.status = "active"
22+
project.return_value.customer_name = "Tucker Inc"
23+
project.return_value.id = "768c924e-4501-457f-99c5-7198440d3c60"
24+
project.return_value.tenant_id = "e2953984-03e7-4730-be29-1753d24df3b0"
25+
project.return_value.deleted = None
26+
27+
return project.return_value
28+
29+
project = fn().__dict__
30+
31+
assert 'customer' in project
32+
assert 'project_type' in project

time_tracker_api/projects/projects_model.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,16 @@
1111
from time_tracker_api.customers.customers_model import (
1212
create_dao as customers_create_dao,
1313
)
14+
from time_tracker_api.project_types.project_types_model import (
15+
create_dao as project_types_create_dao,
16+
)
1417
from time_tracker_api.customers.customers_model import CustomerCosmosDBModel
1518
from utils.query_builder import CosmosDBQueryBuilder
16-
from utils.extend_model import add_customer_name_to_projects
19+
from utils.extend_model import (
20+
add_customer_name_to_projects,
21+
add_custom_attribute_in_list,
22+
add_custom_attribute,
23+
)
1724

1825

1926
class ProjectDao(CRUDDao):
@@ -101,6 +108,17 @@ class ProjectCosmosDBDao(APICosmosDBDao, ProjectDao):
101108
def __init__(self, repository):
102109
CosmosDBDao.__init__(self, repository)
103110

111+
@add_custom_attribute('customer', customers_create_dao)
112+
@add_custom_attribute('project_type', project_types_create_dao)
113+
def get(self, id) -> ProjectCosmosDBModel:
114+
"""
115+
Get one project an active client
116+
:param (str) id: project's id
117+
"""
118+
return super().get(id)
119+
120+
@add_custom_attribute_in_list('customer', customers_create_dao)
121+
@add_custom_attribute_in_list('project_type', project_types_create_dao)
104122
def get_all(
105123
self, conditions: dict = None, project_ids: List = None, **kwargs
106124
) -> list:

time_tracker_api/projects/projects_namespace.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,51 @@
7070
},
7171
)
7272

73+
project_type_nested_field = ns.model('ProjectType', {
74+
'name': fields.String(
75+
title='Name',
76+
required=True,
77+
max_length=50,
78+
description='Name of the project type',
79+
example=faker.random_element(["Customer", "Training", "Internal"]),
80+
),
81+
'description': NullableString(
82+
title='Description',
83+
required=False,
84+
max_length=250,
85+
description='Comments about the project type',
86+
example=faker.paragraph(),
87+
)
88+
})
89+
90+
customer_nested_field = ns.model('Customer', {
91+
'name': fields.String(
92+
title='Name',
93+
required=True,
94+
max_length=50,
95+
description='Name of the customer',
96+
example=faker.company(),
97+
),
98+
'description': NullableString(
99+
title='Description',
100+
required=False,
101+
max_length=250,
102+
description='Description about the customer',
103+
example=faker.paragraph(),
104+
)
105+
})
106+
73107
project_response_fields = {
108+
# TODO: Remove this DEAD CODE
74109
'customer_name': fields.String(
75110
required=True,
76111
title='Customer Name',
77112
max_length=50,
78113
description='Name of the customer of the project',
79114
example=faker.company(),
80115
),
116+
'customer': fields.Nested(customer_nested_field),
117+
'project_type': fields.Nested(project_type_nested_field),
81118
}
82119
project_response_fields.update(common_fields)
83120

utils/extend_model.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,64 @@
1+
from functools import wraps
12
import re
23

34

5+
def add_custom_attribute(attr, dao):
6+
"""
7+
Decorator to add an custom attribute in model, based on entity's id
8+
:param (attr) attribute: name of the new attribute
9+
:param (dao) dao: related entity to the model
10+
"""
11+
12+
def decorator_for_single_item(func):
13+
@wraps(func)
14+
def wrapper(*args, **kwargs):
15+
current_dao = dao()
16+
entity_model = func(*args, **kwargs)
17+
attribute_id = f"{attr}_id"
18+
19+
if entity_model and attribute_id in entity_model.__dict__:
20+
value_id = entity_model.__dict__[attribute_id]
21+
if value_id:
22+
related_entity = current_dao.get(value_id)
23+
setattr(entity_model, attr, related_entity)
24+
25+
return entity_model
26+
27+
return wrapper
28+
29+
return decorator_for_single_item
30+
31+
32+
def add_custom_attribute_in_list(attr, dao):
33+
"""
34+
Decorator to add an custom attribute in model_list, based on entity's id
35+
:param (attr) attribute: name of the new attribute
36+
:param (dao) dao: related entity to the model
37+
"""
38+
39+
def decorator_for_list_item(func):
40+
@wraps(func)
41+
def wrapper(*args, **kwargs):
42+
current_dao = dao()
43+
entity_model_list = func(*args, **kwargs)
44+
attribute_id = f"{attr}_id"
45+
46+
related_entity_list = current_dao.get_all()
47+
related_entities_ids_dict = {x.id: x for x in related_entity_list}
48+
49+
for entity_model in entity_model_list:
50+
value_id = entity_model.__dict__[attribute_id]
51+
setattr(
52+
entity_model, attr, related_entities_ids_dict.get(value_id)
53+
)
54+
55+
return entity_model_list
56+
57+
return wrapper
58+
59+
return decorator_for_list_item
60+
61+
462
def add_customer_name_to_projects(projects, customers):
563
"""
664
Add attribute customer_name in project model, based on customer_id of the

0 commit comments

Comments
 (0)