diff --git a/app/api/part_api.py b/app/api/part_api.py index 8884cf6..9eeeab5 100644 --- a/app/api/part_api.py +++ b/app/api/part_api.py @@ -1,14 +1,13 @@ import fastapi -from fastapi.param_functions import Depends -from app.schema.datatable import FormRequest +from app.schema.datatable import DataTableRequest, DataTableResponse from app.services import part_service api = fastapi.APIRouter() -@api.post("/api/part/table") -def table_datasource(request: FormRequest = Depends): +@api.post("/api/part/datatable") +async def table_datasource(request: DataTableRequest) -> DataTableResponse: """Parts Datatable Source Returns: @@ -17,14 +16,9 @@ def table_datasource(request: FormRequest = Depends): draw = request.draw - # search_str = request.query_params["search[value]"] - # is_regex = bool(request.query_params["search[regex]"]) - # start = int(request.query_params["start"]) - # limit = int(request.query_params["length"]) - data = part_service.latest_parts(limit=30) - return { - "draw": draw, - "recordsTotal": 100, - "recordsFiltered": 100, - "data": data, - } + data = await part_service.get_latest_parts( + start=request.start, limit=request.length + ) + return DataTableResponse( + draw=draw, recordsTotal=100, recordsFiltered=100, data=data + ) diff --git a/app/main.py b/app/main.py index 607ca59..2610e9f 100644 --- a/app/main.py +++ b/app/main.py @@ -13,7 +13,11 @@ from app.api import part_api -app = fastapi.FastAPI() +app = fastapi.FastAPI( + title="Maker Hub", + description="Personal Hub for makers: Manage Parts, projects, ideas, documentation, parts and footprints etc", + version="2021.0.0-dev1", +) def main(): diff --git a/app/schema/datatable.py b/app/schema/datatable.py index e94a658..c22ebe6 100644 --- a/app/schema/datatable.py +++ b/app/schema/datatable.py @@ -1,8 +1,9 @@ -from typing import List +from typing import List, Optional -from fastapi import Form from pydantic import BaseModel +from app.schema.part import Part + class Search(BaseModel): value: str = "" @@ -10,13 +11,13 @@ class Search(BaseModel): class Order(BaseModel): - column: int = 1 + column: int dir: str = "asc" class Column(BaseModel): - name: str = "" - data: str = "" + name: str + data: str searchable: bool = True orderable: bool = True search: Search @@ -26,27 +27,49 @@ class DataTableBase(BaseModel): draw: int -class TableRequest(DataTableBase): +class DataTableRequest(DataTableBase): start: int length: int search: Search - order: Order - columns: List[Column] = [] + order: List[Order] + columns: List[Column] -class TableResponse(DataTableBase): +class DataTableResponse(DataTableBase): recordsTotal: int recordsFiltered: int - data: list - error: str - - -class FormRequest(BaseModel): - draw: int - start: int - length: int - - def __init__( - self, draw: str = Form(...), start: int = Form(...), length: int = Form(...) - ): - super().__init__(draw, start, length) + data: List[Part] = [] + error: Optional[str] = None + + +# Example DataTable generated Query +# { +# "draw": 1, +# "columns": [ +# { +# "data": "id", +# "name": "", +# "searchable": false, +# "orderable": false, +# "search": {"value": "", "regex": false}, +# }, +# { +# "data": "name", +# "name": "", +# "searchable": true, +# "orderable": true, +# "search": {"value": "", "regex": false}, +# }, +# { +# "data": "description", +# "name": "", +# "searchable": true, +# "orderable": true, +# "search": {"value": "", "regex": false}, +# }, +# ], +# "order": [{"column": 1, "dir": "asc"}], +# "start": 0, +# "length": 100, +# "search": {"value": "", "regex": false}, +# } diff --git a/app/services/part_service.py b/app/services/part_service.py index ba00d95..36ac397 100644 --- a/app/services/part_service.py +++ b/app/services/part_service.py @@ -1,19 +1,22 @@ from typing import List +from app.schema.part import Part -def get_part_count() -> int: +async def get_part_count() -> int: return 283 -def get_total_stock() -> int: +async def get_total_stock() -> int: return 1_000 -def get_stock_value() -> int: +async def get_stock_value() -> int: return 1_500 -def get_latest_parts(start: int = 0, limit: int = 5) -> List: +async def get_latest_parts( + start: int = 0, limit: int = 5 +) -> List: # TODO change return to List[Part] when implemented start = max(0, start) limit = max(0, limit) @@ -521,4 +524,4 @@ def get_latest_parts(start: int = 0, limit: int = 5) -> List: }, ] - return data[start:limit] + return data[start : start + limit] # noqa: E203 diff --git a/app/services/project_service.py b/app/services/project_service.py index d2a01c8..8033b92 100644 --- a/app/services/project_service.py +++ b/app/services/project_service.py @@ -1,11 +1,11 @@ from typing import List -def get_project_count() -> int: +async def get_project_count() -> int: return 34 -def get_latest_projects(start: int = 0, limit: int = 5) -> List: +async def get_latest_projects(start: int = 0, limit: int = 5) -> List: start = max(0, start) limit = max(0, limit) diff --git a/app/services/storage_service.py b/app/services/storage_service.py index e4d3163..f708e21 100644 --- a/app/services/storage_service.py +++ b/app/services/storage_service.py @@ -1,6 +1,6 @@ -def get_location_count() -> int: +async def get_location_count() -> int: return 234 -def get_locations_used() -> int: +async def get_locations_used() -> int: return 230 diff --git a/app/templates/parts/partslist.pt b/app/templates/parts/partslist.pt index 411dc6c..820898c 100644 --- a/app/templates/parts/partslist.pt +++ b/app/templates/parts/partslist.pt @@ -62,15 +62,21 @@ 'select': { 'style': 'multi' }, - "lengthMenu": [[100, 30, 50, -1], [100, 30, 50, "All"]], + "lengthMenu": [[10, 30, 50, 100], [10, 30, 50, 100]], "responsive": 'true', 'order': [[1, 'asc']], "pagingType": "full_numbers", serverSide: true, ajax: { - url: '/api/part/table', + url: '/api/part/datatable', type: 'POST', - datasource: 'data' + datasource: 'data', + dataType: 'json', + contentType: 'application/json', + processData: false, // avoid being transformed into a query string, + "data": function (d) { + return JSON.stringify(d); + } }, columns: [ { data: 'id' }, diff --git a/app/viewmodels/home/index_viewmodel.py b/app/viewmodels/home/index_viewmodel.py index 03a76f0..f61fc55 100644 --- a/app/viewmodels/home/index_viewmodel.py +++ b/app/viewmodels/home/index_viewmodel.py @@ -1,5 +1,8 @@ from starlette.requests import Request +from typing import List + +from app.schema.part import Part from app.services import part_service, project_service, storage_service from app.viewmodels.shared.viewmodel import ViewModelBase @@ -8,8 +11,17 @@ class IndexViewModel(ViewModelBase): def __init__(self, request: Request): super().__init__(request) - self.part_count: int = part_service.get_part_count() - self.location_count: int = storage_service.get_location_count() - self.project_count: int = project_service.get_project_count() - self.latest_parts = part_service.get_latest_parts(limit=7) - self.latest_projects = project_service.get_latest_projects(limit=7) + self.part_count: int = 0 + self.location_count: int = 0 + self.project_count: int = 0 + self.latest_parts: List[Part] = [] + self.latest_projects: List[ + Part + ] = [] # Todo: Change to correct Type once Project schema has been completed + + async def load(self): + self.part_count = await part_service.get_part_count() + self.location_count = await storage_service.get_location_count() + self.project_count = await project_service.get_project_count() + self.latest_parts = await part_service.get_latest_parts(limit=7) + self.latest_projects = await project_service.get_latest_projects(limit=7) diff --git a/app/viewmodels/parts/partslist_viewmodel.py b/app/viewmodels/parts/partslist_viewmodel.py index 10b549b..30b5d56 100644 --- a/app/viewmodels/parts/partslist_viewmodel.py +++ b/app/viewmodels/parts/partslist_viewmodel.py @@ -10,4 +10,7 @@ def __init__(self, request: Request): super().__init__(request) self.part_count: int = part_service.get_part_count() - self.parts = part_service.get_latest_parts(limit=30) + + async def load(self): + + self.part_count: int = await part_service.get_part_count() diff --git a/app/viewmodels/projects/projectslist_viewmodel.py b/app/viewmodels/projects/projectslist_viewmodel.py index 28fec3d..1af822d 100644 --- a/app/viewmodels/projects/projectslist_viewmodel.py +++ b/app/viewmodels/projects/projectslist_viewmodel.py @@ -3,10 +3,16 @@ from app.services import project_service from app.viewmodels.shared.viewmodel import ViewModelBase +from typing import List + class ProjectlistViewModel(ViewModelBase): def __init__(self, request: Request): super().__init__(request) + self.project_count: int = 0 + self.projects: List = [] + + def load(self): self.project_count: int = project_service.get_project_count() self.projects = project_service.get_latest_projects(limit=30) diff --git a/app/viewmodels/reports/overview_viewmodel.py b/app/viewmodels/reports/overview_viewmodel.py index 869994a..8f9cc5c 100644 --- a/app/viewmodels/reports/overview_viewmodel.py +++ b/app/viewmodels/reports/overview_viewmodel.py @@ -9,13 +9,25 @@ def __init__(self, request: Request): super().__init__(request) # Parts Stats - self.total_parts: int = part_service.get_part_count() - self.total_stock: int = part_service.get_total_stock() - self.stock_value: float = part_service.get_stock_value() + self.total_parts: int = 0 + self.total_stock: int = 0 + self.stock_value: float = 0 # Location Stats - self.locations_total: int = storage_service.get_location_count() - self.locations_used: int = storage_service.get_locations_used() + self.locations_total: int = 0 + self.locations_used: int = 0 + # Project Stats + self.project_count: int = 0 + + async def load(self): + # Parts Stats + self.total_parts = await part_service.get_part_count() + self.total_stock = await part_service.get_total_stock() + self.stock_value = await part_service.get_stock_value() + + # Location Stats + self.locations_total = await storage_service.get_location_count() + self.locations_used = await storage_service.get_locations_used() # Project Stats - self.project_count: int = project_service.get_project_count() + self.project_count = await project_service.get_project_count() diff --git a/app/viewmodels/storage/storagelist_viewmodel.py b/app/viewmodels/storage/storagelist_viewmodel.py index 06b42c3..044af9b 100644 --- a/app/viewmodels/storage/storagelist_viewmodel.py +++ b/app/viewmodels/storage/storagelist_viewmodel.py @@ -8,4 +8,7 @@ class StoragelistViewModel(ViewModelBase): def __init__(self, request: Request): super().__init__(request) - self.project_count: int = storage_service.get_location_count() + self.project_count: int = 0 + + async def load(self): + self.project_count = await storage_service.get_location_count() diff --git a/app/views/home.py b/app/views/home.py index f21ce7f..211c54d 100644 --- a/app/views/home.py +++ b/app/views/home.py @@ -10,8 +10,9 @@ @router.get("/") @template() -def index(request: Request): +async def index(request: Request): vm = IndexViewModel(request) + await vm.load() return vm.to_dict() diff --git a/app/views/parts.py b/app/views/parts.py index 6d2484c..f251cce 100644 --- a/app/views/parts.py +++ b/app/views/parts.py @@ -10,8 +10,9 @@ @router.get("/parts") @template() -def partslist(request: Request): +async def partslist(request: Request): vm = PartslistViewModel(request) + await vm.load() return vm.to_dict() diff --git a/app/views/projects.py b/app/views/projects.py index 943dfc2..b683724 100644 --- a/app/views/projects.py +++ b/app/views/projects.py @@ -10,8 +10,9 @@ @router.get("/projects") @template() -def projectlist(request: Request): +async def projectlist(request: Request): vm = ProjectlistViewModel(request) + vm.load() return vm.to_dict() diff --git a/app/views/reports.py b/app/views/reports.py index f42e517..50fbd42 100644 --- a/app/views/reports.py +++ b/app/views/reports.py @@ -3,13 +3,13 @@ from starlette.requests import Request from app.viewmodels.reports.overview_viewmodel import OverviewViewModel -from app.viewmodels.shared.viewmodel import ViewModelBase router = fastapi.APIRouter() @router.get("/reports") @template() -def overview(request: Request): +async def overview(request: Request): vm = OverviewViewModel(request) + vm.load() return vm.to_dict() diff --git a/app/views/storage.py b/app/views/storage.py index b40ae32..644dc76 100644 --- a/app/views/storage.py +++ b/app/views/storage.py @@ -10,8 +10,9 @@ @router.get("/storage") @template() -def storagelist(request: Request): +async def storagelist(request: Request): vm = StoragelistViewModel(request) + vm.load() return vm.to_dict() diff --git a/setup.cfg b/setup.cfg index 0423559..ccd7999 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,10 @@ [flake8] ignore = D401,D202,E226,E302,E41 max-line-length=120 -exclude = migrations/* +exclude = + migrations/* + # TODO: remove these serivces once implemented + app/services/part_service.py + app/services/project_service.py + # max-complexity = 10 diff --git a/tests/test_services.py b/tests/test_services.py index 65824f8..3af1c1e 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -1,66 +1,70 @@ +import pytest from app.services import part_service from app.services import project_service from app.services import storage_service +@pytest.mark.asyncio class TestPartservice: """Test part service""" - def test_get_part_count(self): - assert part_service.get_part_count() == 283 + async def test_get_part_count(self): + assert await part_service.get_part_count() == 283 - def test_get_total_stock(self): - assert part_service.get_total_stock() == 1000 + async def test_get_total_stock(self): + assert await part_service.get_total_stock() == 1000 - def test_get_stock_value(self): - assert part_service.get_stock_value() == 1_500 + async def test_get_stock_value(self): + assert await part_service.get_stock_value() == 1_500 - def test_get_latest_parts_returns_a_list(self): - assert type(part_service.get_latest_parts()) == list + async def test_get_latest_parts_returns_a_list(self): + assert type(await part_service.get_latest_parts()) == list - def test_get_latest_parts_limits(self): - result = len(part_service.get_latest_parts()) + async def test_get_latest_parts_limits(self): + result = len(await part_service.get_latest_parts()) assert result == 5 - result = len(part_service.get_latest_parts(limit=10)) + result = len(await part_service.get_latest_parts(limit=10)) assert result == 10 - result = len(part_service.get_latest_parts(start=5, limit=10)) + result = len(await part_service.get_latest_parts(start=5, limit=10)) assert result == 10 +@pytest.mark.asyncio class TestProjectservice: """Test project service""" - def test_get_project_count(self): - assert project_service.get_project_count() == 34 + async def test_get_project_count(self): + assert await project_service.get_project_count() == 34 - def test_get_latest_projects_returns_a_list(self): - assert type(project_service.get_latest_projects()) == list + async def test_get_latest_projects_returns_a_list(self): + assert type(await project_service.get_latest_projects()) == list - def test_get_latest_parts_limits(self): - result = len(project_service.get_latest_projects()) + async def test_get_latest_parts_limits(self): + result = len(await project_service.get_latest_projects()) assert result == 5 - result = len(project_service.get_latest_projects(limit=10)) + result = len(await project_service.get_latest_projects(limit=10)) assert result == 10 - result = len(project_service.get_latest_projects(start=5, limit=10)) + result = len(await project_service.get_latest_projects(start=5, limit=10)) assert result == 5 - result = len(project_service.get_latest_projects(start=-1)) + result = len(await project_service.get_latest_projects(start=-1)) assert result == 5 - result = len(project_service.get_latest_projects(limit=-1)) + result = len(await project_service.get_latest_projects(limit=-1)) assert result == 0 - result = len(project_service.get_latest_projects(limit=0)) + result = len(await project_service.get_latest_projects(limit=0)) assert result == 0 +@pytest.mark.asyncio class TestStorageservice: - def test_get_location_count(self): - assert storage_service.get_location_count() == 234 + async def test_get_location_count(self): + assert await storage_service.get_location_count() == 234 - def test_get_locations_used(self) -> int: - assert storage_service.get_locations_used() == 230 + async def test_get_locations_used(self): + assert await storage_service.get_locations_used() == 230