Implementing filter and sort in back

This commit is contained in:
2025-04-01 18:40:45 +02:00
parent 59cc709ed5
commit 56ca5156c4
8 changed files with 53 additions and 91 deletions

View File

@@ -6,7 +6,7 @@ from uuid import UUID
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from ..core.models import CrudDocument, RichtextSingleline, RichtextMultiline, DictionaryEntry from ..core.models import CrudDocument, RichtextSingleline, RichtextMultiline, DictionaryEntry
from ..core.filter import Filter from ..core.filter import Filter, FilterSchema
from ..entity.models import Entity 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) value = value.replace('%{}%'.format(v.key), v.value)
return 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): class Constants(Filter.Constants):
model = ContractDraft model = ContractDraft
search_model_fields = ["name"] search_model_fields = ["label", "status"]
class ContractFilters(Filter): class ContractFilters(FilterSchema):
name__like: Optional[str] = None status: Optional[str] = None
order_by: Optional[list[str]] = None
class Constants(Filter.Constants): class Constants(Filter.Constants):
model = Contract model = Contract
search_model_fields = ["name"] search_model_fields = ["label", "status"]

View File

@@ -46,10 +46,21 @@ class Filter(BaseFilterModel):
``` ```
""" """
def sort(self, query): def sort(self):
if not self.ordering_values: if not self.ordering_values:
return query return None
return query.sort(*self.ordering_values)
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") @field_validator("*", mode="before")
@classmethod @classmethod
@@ -112,5 +123,13 @@ class Filter(BaseFilterModel):
def filter(self, query): def filter(self, query):
data = self._get_filter_conditions() data = self._get_filter_conditions()
for filter_condition, filter_kwargs in data: 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 return query
class FilterSchema(Filter):
label__ilike: Optional[str] = None
order_by: Optional[list[str]] = None

View File

@@ -2,6 +2,7 @@ from datetime import datetime, UTC
from typing import Optional from typing import Optional
from beanie import PydanticObjectId from beanie import PydanticObjectId
from motor.motor_asyncio import AsyncIOMotorCollection
from pydantic import BaseModel, Field, computed_field from pydantic import BaseModel, Field, computed_field
@@ -31,7 +32,7 @@ class CrudDocument(BaseModel):
return cls.__name__ return cls.__name__
@classmethod @classmethod
def _get_collection(cls, db): def _get_collection(cls, db) -> AsyncIOMotorCollection:
return db.get_collection(cls._collection_name()) return db.get_collection(cls._collection_name())
@classmethod @classmethod
@@ -42,10 +43,16 @@ class CrudDocument(BaseModel):
return await cls.get(db, result.inserted_id) return await cls.get(db, result.inserted_id)
@classmethod @classmethod
def list(cls, db, filters): def find(cls, db, filters):
query = filters.filter(cls._get_collection(db)) return {
query = filters.sort(query) "collection": cls._get_collection(db),
return query "query_filter": filters.filter({}),
"sort": filters.sort(),
}
@classmethod
def list(cls, db):
return cls._get_collection(db).find({})
@classmethod @classmethod
async def get(cls, db, model_id): async def get(cls, db, model_id):

View File

@@ -12,55 +12,6 @@ from .schemas import Writer, Reader
from ..db import get_db_client 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", #instance: str="westside", firm: str="cht",
def get_tenant_db_cursor(db_client=Depends(get_db_client)): def get_tenant_db_cursor(db_client=Depends(get_db_client)):
instance = "westside" 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") @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]: 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") @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: async def create(schema: model_create, db=Depends(get_logged_tenant_db_cursor)) -> model_read:

View File

@@ -5,7 +5,7 @@ from pydantic import Field, BaseModel
from beanie import Indexed from beanie import Indexed
from ..core.models import CrudDocument from ..core.models import CrudDocument
from ..core.filter import Filter from ..core.filter import Filter, FilterSchema
class EntityType(BaseModel): class EntityType(BaseModel):
@@ -95,11 +95,7 @@ class Entity(CrudDocument):
title = 'Client' title = 'Client'
class EntityFilters(Filter): class EntityFilters(FilterSchema):
name__like: Optional[str] = None
order_by: Optional[list[str]] = None
class Constants(Filter.Constants): class Constants(Filter.Constants):
model = Entity model = Entity
search_model_fields = ["name"] search_model_fields = ["label"]

View File

@@ -4,7 +4,7 @@ from html import unescape
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from ..core.models import CrudDocument, RichtextMultiline, RichtextSingleline, DictionaryEntry from ..core.models import CrudDocument, RichtextMultiline, RichtextSingleline, DictionaryEntry
from ..core.filter import Filter from ..core.filter import Filter, FilterSchema
class PartyTemplate(BaseModel): class PartyTemplate(BaseModel):
@@ -105,21 +105,13 @@ class ContractTemplate(CrudDocument):
title = 'Template de contrat' title = 'Template de contrat'
class ContractTemplateFilters(Filter): class ContractTemplateFilters(FilterSchema):
name__like: Optional[str] = None
order_by: Optional[list[str]] = None
class Constants(Filter.Constants): class Constants(Filter.Constants):
model = ContractTemplate model = ContractTemplate
search_model_fields = ["name"] search_model_fields = ["label"]
class ProvisionTemplateFilters(Filter): class ProvisionTemplateFilters(FilterSchema):
name__like: Optional[str] = None
order_by: Optional[list[str]] = None
class Constants(Filter.Constants): class Constants(Filter.Constants):
model = ProvisionTemplate model = ProvisionTemplate
search_model_fields = ["name"] search_model_fields = ["label"]

View File

@@ -111,7 +111,7 @@ export class ListComponent implements OnInit {
private _search() { private _search() {
this._loading$.next(true); this._loading$.next(true);
let sortBy = new SortBy(this.sortColumn, this.sortDirection) 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) { for (let f in this.searchFilters) {
if (Array.isArray(this.searchFilters[f])) { if (Array.isArray(this.searchFilters[f])) {
filters.push(new Filters(f, 'in', this.searchFilters[f])) filters.push(new Filters(f, 'in', this.searchFilters[f]))

View File

@@ -142,7 +142,7 @@ export class ForeignkeyTypeComponent extends FieldType<FieldTypeConfig> implemen
1, 1,
10, 10,
[], [],
[new Filters('fulltext', 'eq', term)] [new Filters('label', 'ilike', term)]
).pipe( ).pipe(
map((result: any) => result["items"]), map((result: any) => result["items"]),
); );