From 56ca5156c479f860f1ba1affc7dd01acd5b07135 Mon Sep 17 00:00:00 2001 From: ewandor Date: Tue, 1 Apr 2025 18:40:45 +0200 Subject: [PATCH] Implementing filter and sort in back --- back/app/contract/models.py | 17 +++---- back/app/core/filter.py | 27 ++++++++-- back/app/core/models.py | 17 +++++-- back/app/core/routes.py | 51 +------------------ back/app/entity/models.py | 10 ++-- back/app/template/models.py | 18 ++----- .../src/common/crud/list/list.component.ts | 2 +- .../src/common/crud/types/foreignkey.type.ts | 2 +- 8 files changed, 53 insertions(+), 91 deletions(-) diff --git a/back/app/contract/models.py b/back/app/contract/models.py index e7530081..9bf25ff5 100644 --- a/back/app/contract/models.py +++ b/back/app/contract/models.py @@ -6,7 +6,7 @@ from uuid import UUID from pydantic import BaseModel, Field from ..core.models import CrudDocument, RichtextSingleline, RichtextMultiline, DictionaryEntry -from ..core.filter import Filter +from ..core.filter import Filter, FilterSchema from ..entity.models import Entity @@ -247,20 +247,17 @@ def replace_variables_in_value(variables, value: str): value = value.replace('%{}%'.format(v.key), v.value) return value -class ContractDraftFilters(Filter): - name__like: Optional[str] = None - order_by: Optional[list[str]] = None +class ContractDraftFilters(FilterSchema): + status: Optional[str] = None class Constants(Filter.Constants): model = ContractDraft - search_model_fields = ["name"] + search_model_fields = ["label", "status"] -class ContractFilters(Filter): - name__like: Optional[str] = None - - order_by: Optional[list[str]] = None +class ContractFilters(FilterSchema): + status: Optional[str] = None class Constants(Filter.Constants): model = Contract - search_model_fields = ["name"] \ No newline at end of file + search_model_fields = ["label", "status"] diff --git a/back/app/core/filter.py b/back/app/core/filter.py index 26858fe4..e5cc03b1 100644 --- a/back/app/core/filter.py +++ b/back/app/core/filter.py @@ -46,10 +46,21 @@ class Filter(BaseFilterModel): ``` """ - def sort(self, query): + def sort(self): if not self.ordering_values: - return query - return query.sort(*self.ordering_values) + return None + + sort = {} + for column in self.ordering_values: + direction = 1 + if column[0] in ["+", "-"]: + if column[0] == "-": + direction = -1 + column = column[1:] + + sort[column] = direction + + return sort @field_validator("*", mode="before") @classmethod @@ -112,5 +123,13 @@ class Filter(BaseFilterModel): def filter(self, query): data = self._get_filter_conditions() for filter_condition, filter_kwargs in data: - query = query.find(filter_condition, **filter_kwargs) + for field_name, value in filter_condition.items(): + if field_name in query: + query[field_name] = query[field_name] | value + else: + query[field_name] = value return query + +class FilterSchema(Filter): + label__ilike: Optional[str] = None + order_by: Optional[list[str]] = None diff --git a/back/app/core/models.py b/back/app/core/models.py index cfb28c47..ae718a55 100644 --- a/back/app/core/models.py +++ b/back/app/core/models.py @@ -2,6 +2,7 @@ from datetime import datetime, UTC from typing import Optional from beanie import PydanticObjectId +from motor.motor_asyncio import AsyncIOMotorCollection from pydantic import BaseModel, Field, computed_field @@ -31,7 +32,7 @@ class CrudDocument(BaseModel): return cls.__name__ @classmethod - def _get_collection(cls, db): + def _get_collection(cls, db) -> AsyncIOMotorCollection: return db.get_collection(cls._collection_name()) @classmethod @@ -42,10 +43,16 @@ class CrudDocument(BaseModel): return await cls.get(db, result.inserted_id) @classmethod - def list(cls, db, filters): - query = filters.filter(cls._get_collection(db)) - query = filters.sort(query) - return query + def find(cls, db, filters): + return { + "collection": cls._get_collection(db), + "query_filter": filters.filter({}), + "sort": filters.sort(), + } + + @classmethod + def list(cls, db): + return cls._get_collection(db).find({}) @classmethod async def get(cls, db, model_id): diff --git a/back/app/core/routes.py b/back/app/core/routes.py index 07ddb09b..620da1f0 100644 --- a/back/app/core/routes.py +++ b/back/app/core/routes.py @@ -12,55 +12,6 @@ from .schemas import Writer, Reader from ..db import get_db_client -def parse_sort(sort_by): - if not sort_by: - return [] - - fields = [] - for field in sort_by.split(','): - direction, column = field.split('(') - fields.append((column[:-1], 1 if direction == 'asc' else -1)) - - return fields - - -def Or(filters): - return {'$or': filters} - - -def parse_query(query: str, model): - if query is None: - return {} - - and_array = [] - for criterion in query.split(' AND '): - [column, operator, value] = criterion.split(' ', 2) - column = column.lower() - operand = None - if column == 'fulltext': - if not model.Settings.fulltext_search: - continue - - or_array = [] - for field in model.Settings.fulltext_search: - words_and_array = [] - for word in value.split(' '): - words_and_array.append(RegEx(field, word, 'i')) - or_array.append(And(*words_and_array) if len(words_and_array) > 1 else words_and_array[0]) - operand = Or(or_array) if len(or_array) > 1 else or_array[0] - - elif operator == 'eq': - operand = Eq(column, value) - elif operator == 'in': - operand = In(column, value.split(',')) - - if operand: - and_array.append(operand) - - if and_array: - return And(*and_array) if len(and_array) > 1 else and_array[0] - else: - return {} #instance: str="westside", firm: str="cht", def get_tenant_db_cursor(db_client=Depends(get_db_client)): instance = "westside" @@ -80,7 +31,7 @@ def get_crud_router(model: CrudDocument, model_create: Writer, model_read: Reade @router.get("/", response_model=Page[model_read], response_description=f"{model_name} records retrieved") async def read_list(filters: model_filter=FilterDepends(model_filter), db=Depends(get_logged_tenant_db_cursor)) -> Page[model_read]: - return await paginate(model.list(db, filters)) + return await paginate(**model.find(db, filters)) @router.post("/", response_description=f"{model_name} added to the database") async def create(schema: model_create, db=Depends(get_logged_tenant_db_cursor)) -> model_read: diff --git a/back/app/entity/models.py b/back/app/entity/models.py index 4875372d..ad777cfc 100644 --- a/back/app/entity/models.py +++ b/back/app/entity/models.py @@ -5,7 +5,7 @@ from pydantic import Field, BaseModel from beanie import Indexed from ..core.models import CrudDocument -from ..core.filter import Filter +from ..core.filter import Filter, FilterSchema class EntityType(BaseModel): @@ -95,11 +95,7 @@ class Entity(CrudDocument): title = 'Client' -class EntityFilters(Filter): - name__like: Optional[str] = None - - order_by: Optional[list[str]] = None - +class EntityFilters(FilterSchema): class Constants(Filter.Constants): model = Entity - search_model_fields = ["name"] + search_model_fields = ["label"] diff --git a/back/app/template/models.py b/back/app/template/models.py index 98445c53..c1c5b4b4 100644 --- a/back/app/template/models.py +++ b/back/app/template/models.py @@ -4,7 +4,7 @@ from html import unescape from pydantic import BaseModel, Field from ..core.models import CrudDocument, RichtextMultiline, RichtextSingleline, DictionaryEntry -from ..core.filter import Filter +from ..core.filter import Filter, FilterSchema class PartyTemplate(BaseModel): @@ -105,21 +105,13 @@ class ContractTemplate(CrudDocument): title = 'Template de contrat' -class ContractTemplateFilters(Filter): - name__like: Optional[str] = None - - order_by: Optional[list[str]] = None - +class ContractTemplateFilters(FilterSchema): class Constants(Filter.Constants): model = ContractTemplate - search_model_fields = ["name"] + search_model_fields = ["label"] -class ProvisionTemplateFilters(Filter): - name__like: Optional[str] = None - - order_by: Optional[list[str]] = None - +class ProvisionTemplateFilters(FilterSchema): class Constants(Filter.Constants): model = ProvisionTemplate - search_model_fields = ["name"] + search_model_fields = ["label"] diff --git a/front/app/src/common/crud/list/list.component.ts b/front/app/src/common/crud/list/list.component.ts index 5969ebde..166b59e3 100644 --- a/front/app/src/common/crud/list/list.component.ts +++ b/front/app/src/common/crud/list/list.component.ts @@ -111,7 +111,7 @@ export class ListComponent implements OnInit { private _search() { this._loading$.next(true); let sortBy = new SortBy(this.sortColumn, this.sortDirection) - let filters = this.searchTerm ? [new Filters('fulltext', 'eq', this.searchTerm)] : []; + let filters = this.searchTerm ? [new Filters('label', 'ilike', this.searchTerm)] : []; for (let f in this.searchFilters) { if (Array.isArray(this.searchFilters[f])) { filters.push(new Filters(f, 'in', this.searchFilters[f])) diff --git a/front/app/src/common/crud/types/foreignkey.type.ts b/front/app/src/common/crud/types/foreignkey.type.ts index 3fd3e982..ec2fec13 100644 --- a/front/app/src/common/crud/types/foreignkey.type.ts +++ b/front/app/src/common/crud/types/foreignkey.type.ts @@ -142,7 +142,7 @@ export class ForeignkeyTypeComponent extends FieldType implemen 1, 10, [], - [new Filters('fulltext', 'eq', term)] + [new Filters('label', 'ilike', term)] ).pipe( map((result: any) => result["items"]), );