Skip to content

Commit 8286e7e

Browse files
author
littlewillow
committed
Work on product's functions
TODO : frontend
1 parent 8bbff2c commit 8286e7e

File tree

14 files changed

+588
-115
lines changed

14 files changed

+588
-115
lines changed

api_server/src/entity/product.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# coding=utf-8
22
from dataclasses import dataclass
3+
from typing import Optional
34

45

56
@dataclass()
67
class Product:
78
name: str
8-
buying_price: float
9-
selling_price: float
10-
9+
buying_price: int
10+
selling_price: int
11+
product_id: Optional[int]

api_server/src/exceptions.py

+5
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ def __init__(self, v=None):
153153
super().__init__('account_type', v)
154154

155155

156+
class ProductNotFoundError(NotFoundError):
157+
def __init__(self, v=None):
158+
super().__init__('product', v)
159+
160+
156161
# ALREADY EXIST ERRORS.
157162
class AlreadyExistsError(UserInputError):
158163
"""
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,125 @@
11
# coding=utf-8
2+
from dataclasses import asdict
3+
4+
from connexion import NoContent
5+
26
from src.constants import DEFAULT_LIMIT, DEFAULT_OFFSET
7+
from src.entity.product import Product
8+
from src.exceptions import ProductNotFoundError, UserInputError
9+
from src.interface_adapter.http_api.decorator.with_context import with_context
10+
from src.interface_adapter.http_api.util.error import bad_request
311
from src.interface_adapter.sql.decorator.auth import auth_regular_admin
412
from src.interface_adapter.sql.decorator.sql_session import require_sql
5-
6-
from src.entity.product import Product
7-
from src.use_case.product_manager import ProductManager
13+
from src.use_case.product_manager import PartialMutationRequest, ProductManager, FullMutationRequest
14+
from src.util.context import log_extra
15+
from src.util.log import LOG
816

917

1018
class ProductHandler:
1119
def __init__(self, product_manager: ProductManager):
1220
self.product_manager = product_manager
1321

22+
@with_context
1423
@require_sql
1524
@auth_regular_admin
16-
def search(self, limit=DEFAULT_LIMIT, offset=DEFAULT_OFFSET, terms=None):
17-
# TODO: LOG.debug
25+
def search(self, ctx, limit=DEFAULT_LIMIT, offset=DEFAULT_OFFSET, terms=None):
26+
27+
LOG.debug("http_product_search_called", extra=log_extra(ctx,
28+
limit=limit,
29+
offset=offset,
30+
terms=terms))
1831
try:
19-
result, count = self.product_manager.get_by_name(ctx, limit, offset, name, terms)
20-
except Exception:
21-
pass
22-
# TODO: except NameInputError
32+
result, count = self.product_manager.search(ctx, product_id=None, terms=terms)
33+
headers = {
34+
"X-Total-Count": count,
35+
'access-control-expose-headers': 'X-Total-Count'
36+
}
37+
return list(map(_map_product_to_http_response, result)), 200, headers
2338

24-
headers = {
25-
"X-Total-Count": count,
26-
"access-control-expose-headers": "X-Total-Count"
27-
}
28-
return list(map(_map_product_to_http_response, result)), 200, headers
39+
except UserInputError as e:
40+
return bad_request(e), 400 # 400 Bad Request
2941

42+
@with_context
3043
@require_sql
3144
@auth_regular_admin
32-
def post(self, body):
33-
pass
45+
def post(self, ctx, body):
46+
""" Add a product record in the database """
47+
LOG.debug("http_product_post_called", extra=log_extra(ctx, request=body))
3448

49+
try:
50+
created = self.product_manager.update_or_create(ctx, req=FullMutationRequest(
51+
name=body.get('name'),
52+
buying_price=body.get('buying_price'),
53+
selling_price=body.get('selling_price')),
54+
product_id=body.get('product_id'))
55+
if created:
56+
return NoContent, 201 # 201 Created
57+
else:
58+
return NoContent, 204 # 204 No Content
59+
60+
except UserInputError as e:
61+
return bad_request(e), 400 # 400 Bad Request
62+
63+
@with_context
3564
@require_sql
3665
@auth_regular_admin
37-
def get(self, product_id):
38-
pass
66+
def get(self, ctx, product_id):
67+
""" Get a specific account. """
68+
LOG.debug("http_product_get_called", extra=log_extra(ctx, product_id=product_id))
69+
try:
70+
result = self.product_manager.get_by_id(ctx, product_id)
71+
return _map_product_to_http_response(result), 200 # 200 OK
72+
except ProductNotFoundError:
73+
return NoContent, 404 # 404 Not Found
3974

75+
@with_context
4076
@require_sql
4177
@auth_regular_admin
42-
def patch(self, product_id, body):
43-
pass
78+
def delete(self, ctx, name):
79+
""" Delete the specified Product from the database """
80+
LOG.debug("http_product_delete_called", extra=log_extra(ctx, name=name))
81+
try:
82+
self.product_manager.delete(ctx, name)
83+
return NoContent, 204 # 204 No Content
4484

85+
except ProductNotFoundError:
86+
return NoContent, 404 # 404 Not Found
4587

46-
def _map_account_to_http_response(product: Product) -> dict:
88+
@with_context
89+
@require_sql
90+
@auth_regular_admin
91+
def patch(self, ctx, product_id, body):
92+
""" Partially update a product from the database """
93+
LOG.debug("http_product_patch_called", extra=log_extra(ctx, product_id=product_id, request=body))
94+
try:
95+
mutation_request = _map_http_request_to_full_mutation_request(body)
96+
self.product_manager.update_or_create(ctx, mutation_request, product_id)
97+
return NoContent, 204 # 204 No Content
98+
99+
except ProductNotFoundError:
100+
return NoContent, 404 # 404 Not Found
101+
102+
103+
def _map_http_request_to_partial_mutation_request(body) -> PartialMutationRequest:
104+
return PartialMutationRequest(
105+
name=body.get('name'),
106+
selling_price=body.get('selling_price'),
107+
buying_price=body.get('buying_price'),
108+
)
109+
110+
111+
def _map_product_to_http_response(product: Product) -> dict:
47112
fields = {
48113
'name': product.name,
49114
'buying_price': product.buying_price,
50115
'selling_price': product.selling_price,
116+
'id': product.product_id,
51117
}
52-
return {k: v for k, v in fields.items() if v is not None}
118+
return {k: v for k, v in fields.items() if v is not None}
119+
120+
121+
def _map_http_request_to_full_mutation_request(body) -> FullMutationRequest:
122+
partial = _map_http_request_to_partial_mutation_request(body)
123+
return FullMutationRequest(**asdict(partial))
124+
125+

api_server/src/interface_adapter/sql/account_repository.py

-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ def create_account(self, ctx, name=None, actif=None, type=None, creation_date=No
4444

4545
return account
4646

47-
# TODO: update_account mais même problème qu'au dessus
48-
4947
def search_account_by(self, ctx, limit=None, offset=None, account_id=None, terms=None) -> (List[Account], int):
5048
"""
5149
Search for an account.

api_server/src/interface_adapter/sql/model/models.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -277,14 +277,25 @@ class PaymentMethod(Base):
277277
name = Column(String(255), nullable=False)
278278

279279

280-
class Product(Base):
280+
class Product(Base, RubyHashTrackable):
281281
__tablename__ = 'product'
282282

283-
id = Column(Integer, primary_key=True)
283+
id = Column(Integer, primary_key=True, unique=True)
284284
buying_price = Column(DECIMAL(8, 2), nullable=False)
285285
selling_price = Column(DECIMAL(8, 2), nullable=False)
286286
name = Column(String(255), nullable=False)
287287

288+
def serialize_snapshot_diff(self, snap_before: dict, snap_after: dict) -> str:
289+
"""
290+
Override this method to add the prefix.
291+
"""
292+
modif = rubydiff(snap_before, snap_after)
293+
modif = '--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\n' + modif
294+
return modif
295+
296+
def get_related_member(self):
297+
return self
298+
288299

289300
class Account(Base, RubyHashTrackable):
290301
__tablename__ = 'account'

api_server/src/interface_adapter/sql/product_repository.py

+74-24
Original file line numberDiff line numberDiff line change
@@ -4,59 +4,100 @@
44
"""
55
from typing import List
66

7-
from src.constants import CTX_SQL_SESSION, DEFAULT_LIMIT, DEFAULT_OFFSET
7+
from src.constants import CTX_SQL_SESSION
88
from src.entity.product import Product
9+
from src.exceptions import ProductNotFoundError
10+
from src.interface_adapter.sql.model.models import Product as SQLProduct
11+
from src.interface_adapter.sql.track_modifications import track_modifications
912
from src.use_case.interface.product_repository import ProductRepository
10-
11-
# TODO: update_product mais même problème qu'au dessus
12-
# TODO: delete_product
13+
from src.util.context import log_extra
14+
from src.util.log import LOG
1315

1416

1517
class ProductSQLRepository(ProductRepository):
1618
"""
1719
Represent the interface to the SQL database.
1820
"""
1921

20-
def create_product(self, ctx, name=None, buying_price=None, selling_price=None) -> None:
22+
def create_product(self, ctx, name=None, selling_price=None, buying_price=None):
23+
"""
24+
Create a product .
25+
26+
:raise ProductTypeNotFound ?
2127
"""
22-
Create a product.
23-
Possibly raise nothing ?
2428

2529
s = ctx.get(CTX_SQL_SESSION)
30+
LOG.debug("sql_product_repository_create_product_called", extra=log_extra(ctx, name=name))
2631

27-
TODO: LOG.debug
32+
product = SQLProduct(
33+
name=name,
34+
buying_price=buying_price,
35+
selling_price=selling_price,
36+
)
2837

29-
product = Product(
30-
name=name,
31-
buying_price=buying_price,
32-
selling_price=selling_price)
33-
"""
34-
pass
38+
with track_modifications(ctx, s, product):
39+
s.add(product)
40+
41+
return product
3542

36-
# TODO: voir si track_modifications prendre en compte product et si s.add(product) fonctionne
43+
# TODO: update_account mais même problème qu'au dessus
3744

38-
def search_product_by(self, ctx, limit=None, offset=None, name=None, terms=None) -> \
39-
(List[Product], int):
45+
def search_product_by(self, ctx, limit=None, offset=None, product_id=None, terms=None) -> (List[Product], int):
4046
"""
4147
Search for a product.
4248
"""
43-
pass
49+
LOG.debug("sql_product_repository_search_called", extra=log_extra(ctx, product_id=product_id, terms=terms))
50+
s = ctx.get(CTX_SQL_SESSION)
4451

45-
def update_product(self, ctx, product_to_update, name=None, buying_price=None, selling_price=None):
52+
q = s.query(SQLProduct)
53+
54+
if product_id:
55+
q = q.filter(SQLProduct.id == product_id)
56+
if terms:
57+
q = q.filter(SQLProduct.name.contains(terms))
58+
59+
count = q.count()
60+
#q = q.order_by(SQLProduct.creation_date.asc())
61+
q = q.offset(offset)
62+
q = q.limit(limit)
63+
r = q.all()
64+
65+
return list(map(_map_product_sql_to_entity, r)), count
66+
67+
def update_product(self, ctx, name=None, selling_price=None, buying_price=None, product_id=None) -> None:
4668
"""
4769
Update a product.
48-
49-
:raise ProductNotFound (one day)
70+
Will raise (one day) ProductNotFound
5071
"""
51-
pass
72+
s = ctx.get(CTX_SQL_SESSION)
73+
LOG.debug("sql_product_repository_update_product_called", extra=log_extra(ctx, product_id=product_id))
74+
75+
product = _get_product_by_id(s, product_id)
76+
if product is None:
77+
raise ProductNotFoundError(product_id)
78+
79+
with track_modifications(ctx, s, product):
80+
product.name = name or product.name
81+
product.buying_price = buying_price or product.buying_price
82+
product.selling_price = selling_price or product.selling_price
5283

5384
def delete_product(self, ctx, name=None):
5485
"""
5586
Delete a product.
5687
5788
:raise ProductNotFound (one day)
5889
"""
59-
pass
90+
s = ctx.get(CTX_SQL_SESSION)
91+
LOG.debug("sql_product_repository_delete_product_called", extra=log_extra(ctx, name=name))
92+
93+
# Find the soon-to-be deleted user
94+
product = _get_product_by_name(s, name)
95+
if not product:
96+
raise ProductNotFoundError(name)
97+
98+
with track_modifications(ctx, s, product):
99+
# Actually delete it
100+
s.delete(product)
60101

61102

62103
def _map_product_sql_to_entity(p) -> Product:
@@ -65,6 +106,15 @@ def _map_product_sql_to_entity(p) -> Product:
65106
"""
66107
return Product(
67108
name=p.name,
109+
buying_price=p.buying_price,
68110
selling_price=p.selling_price,
69-
buying_price=p.buying_price
111+
product_id=p.id
70112
)
113+
114+
115+
def _get_product_by_id(s, id) -> SQLProduct:
116+
return s.query(SQLProduct).filter(SQLProduct.id == id).one_or_none()
117+
118+
119+
def _get_product_by_name(s, name) -> Product:
120+
return s.query(Product).filter(Product.name == name).one_or_none()

api_server/src/use_case/account_manager.py

-6
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
from src.util.log import LOG
1717

1818

19-
# TODO: update_or_create
20-
2119
@dataclass
2220
class PartialMutationRequest:
2321
"""
@@ -83,8 +81,6 @@ def get_by_id(self, ctx, account_id=None) -> Account:
8381

8482
return result[0]
8583

86-
# TODO: get_by_type and get_by_status (actif or not)
87-
8884
def search(self, ctx, limit=DEFAULT_LIMIT, offset=DEFAULT_OFFSET, account_id=None, terms=None) -> (
8985
List[Account], int):
9086
"""
@@ -139,7 +135,6 @@ def update_or_create(self, ctx, req: FullMutationRequest, account_id=None) -> bo
139135
))
140136
# No account with that name, creating one...
141137
self.account_repository.create_account(ctx, **fields)
142-
# TODO: LOG.info
143138
return True
144139

145140
else:
@@ -150,5 +145,4 @@ def update_or_create(self, ctx, req: FullMutationRequest, account_id=None) -> bo
150145
account_id=account_id,
151146
))
152147
self.account_repository.update_account(ctx, account_id=account_id, **fields)
153-
# TODO: LOG.info
154148
return False

0 commit comments

Comments
 (0)