Some improvements from lan-maestro
This commit is contained in:
parent
e68b10933f
commit
ab3f0b07c2
@ -193,7 +193,7 @@ class Query(QueryABC):
|
|||||||
|
|
||||||
if "key" in kwargs:
|
if "key" in kwargs:
|
||||||
return await userSettingsDao.find_by(
|
return await userSettingsDao.find_by(
|
||||||
{UserSetting.user_id: user.id, UserSetting.key: kwargs["key"]}
|
[{UserSetting.user_id: user.id}, {UserSetting.key: kwargs["key"]}]
|
||||||
)
|
)
|
||||||
return await userSettingsDao.find_by({UserSetting.user_id: user.id})
|
return await userSettingsDao.find_by({UserSetting.user_id: user.id})
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ from typing import Generic, Optional, Union, TypeVar, Any, Type
|
|||||||
from core.const import DATETIME_FORMAT
|
from core.const import DATETIME_FORMAT
|
||||||
from core.database.abc.db_model_abc import DbModelABC
|
from core.database.abc.db_model_abc import DbModelABC
|
||||||
from core.database.database import Database
|
from core.database.database import Database
|
||||||
|
from core.database.external_data_temp_table_builder import ExternalDataTempTableBuilder
|
||||||
from core.get_value import get_value
|
from core.get_value import get_value
|
||||||
from core.logger import DBLogger
|
from core.logger import DBLogger
|
||||||
from core.string import camel_to_snake
|
from core.string import camel_to_snake
|
||||||
@ -16,6 +17,7 @@ T_DBM = TypeVar("T_DBM", bound=DbModelABC)
|
|||||||
|
|
||||||
|
|
||||||
class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||||
|
_external_fields: dict[str, ExternalDataTempTableBuilder] = {}
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def __init__(self, source: str, model_type: Type[T_DBM], table_name: str):
|
def __init__(self, source: str, model_type: Type[T_DBM], table_name: str):
|
||||||
@ -30,6 +32,7 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
|
|
||||||
self.__db_names: dict[str, str] = {}
|
self.__db_names: dict[str, str] = {}
|
||||||
self.__foreign_tables: dict[str, str] = {}
|
self.__foreign_tables: dict[str, str] = {}
|
||||||
|
self.__foreign_table_keys: dict[str, str] = {}
|
||||||
|
|
||||||
self.__date_attributes: set[str] = set()
|
self.__date_attributes: set[str] = set()
|
||||||
self.__ignored_attributes: set[str] = set()
|
self.__ignored_attributes: set[str] = set()
|
||||||
@ -42,12 +45,12 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
return self._table_name
|
return self._table_name
|
||||||
|
|
||||||
def attribute(
|
def attribute(
|
||||||
self,
|
self,
|
||||||
attr_name: Attribute,
|
attr_name: Attribute,
|
||||||
attr_type: type,
|
attr_type: type,
|
||||||
db_name: str = None,
|
db_name: str = None,
|
||||||
ignore=False,
|
ignore=False,
|
||||||
primary_key=False,
|
primary_key=False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Add an attribute for db and object mapping to the data access object
|
Add an attribute for db and object mapping to the data access object
|
||||||
@ -77,21 +80,20 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
self.__date_attributes.add(db_name)
|
self.__date_attributes.add(db_name)
|
||||||
|
|
||||||
def reference(
|
def reference(
|
||||||
self,
|
self,
|
||||||
attr: Attribute,
|
attr: Attribute,
|
||||||
primary_attr: Attribute,
|
primary_attr: Attribute,
|
||||||
foreign_attr: Attribute,
|
foreign_attr: Attribute,
|
||||||
table_name: str,
|
table_name: str,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Add a reference to another table for the given attribute
|
Add a reference to another table for the given attribute
|
||||||
|
:param Attribute attr: Name of the attribute in the object
|
||||||
:param str primary_attr: Name of the primary key in the foreign object
|
:param str primary_attr: Name of the primary key in the foreign object
|
||||||
:param str foreign_attr: Name of the foreign key in the object
|
:param str foreign_attr: Name of the foreign key in the object
|
||||||
:param str table_name: Name of the table to reference
|
:param str table_name: Name of the table to reference
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if table_name == self._table_name:
|
|
||||||
return
|
|
||||||
if isinstance(attr, property):
|
if isinstance(attr, property):
|
||||||
attr = attr.fget.__name__
|
attr = attr.fget.__name__
|
||||||
|
|
||||||
@ -105,11 +107,18 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
|
|
||||||
foreign_attr = foreign_attr.lower().replace("_", "")
|
foreign_attr = foreign_attr.lower().replace("_", "")
|
||||||
|
|
||||||
|
self.__foreign_table_keys[attr] = foreign_attr
|
||||||
|
if table_name == self._table_name:
|
||||||
|
return
|
||||||
|
|
||||||
self.__joins[foreign_attr] = (
|
self.__joins[foreign_attr] = (
|
||||||
f"LEFT JOIN {table_name} ON {table_name}.{primary_attr} = {self._table_name}.{foreign_attr}"
|
f"LEFT JOIN {table_name} ON {table_name}.{primary_attr} = {self._table_name}.{foreign_attr}"
|
||||||
)
|
)
|
||||||
self.__foreign_tables[attr] = table_name
|
self.__foreign_tables[attr] = table_name
|
||||||
|
|
||||||
|
def use_external_fields(self, builder: ExternalDataTempTableBuilder):
|
||||||
|
self._external_fields[builder.table_name] = builder
|
||||||
|
|
||||||
def to_object(self, result: dict) -> T_DBM:
|
def to_object(self, result: dict) -> T_DBM:
|
||||||
"""
|
"""
|
||||||
Convert a result from the database to an object
|
Convert a result from the database to an object
|
||||||
@ -136,7 +145,11 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
query += f" {self.__joins[join]}"
|
query += f" {self.__joins[join]}"
|
||||||
|
|
||||||
if filters is not None and (not isinstance(filters, list) or len(filters) > 0):
|
if filters is not None and (not isinstance(filters, list) or len(filters) > 0):
|
||||||
query += f" WHERE {self._build_conditions(filters)}"
|
conditions, external_table_deps = await self._build_conditions(filters)
|
||||||
|
query = await self._handle_query_external_temp_tables(
|
||||||
|
query, external_table_deps, ignore_fields=True
|
||||||
|
)
|
||||||
|
query += f" WHERE {conditions};"
|
||||||
|
|
||||||
result = await self._db.select_map(query)
|
result = await self._db.select_map(query)
|
||||||
if len(result) == 0:
|
if len(result) == 0:
|
||||||
@ -168,11 +181,11 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
return self.to_object(result[0])
|
return self.to_object(result[0])
|
||||||
|
|
||||||
async def get_by(
|
async def get_by(
|
||||||
self,
|
self,
|
||||||
filters: AttributeFilters = None,
|
filters: AttributeFilters = None,
|
||||||
sorts: AttributeSorts = None,
|
sorts: AttributeSorts = None,
|
||||||
take: int = None,
|
take: int = None,
|
||||||
skip: int = None,
|
skip: int = None,
|
||||||
) -> list[T_DBM]:
|
) -> list[T_DBM]:
|
||||||
"""
|
"""
|
||||||
Get all objects by the given filters
|
Get all objects by the given filters
|
||||||
@ -185,7 +198,7 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
:raises ValueError: When no result is found
|
:raises ValueError: When no result is found
|
||||||
"""
|
"""
|
||||||
result = await self._db.select_map(
|
result = await self._db.select_map(
|
||||||
self._build_conditional_query(filters, sorts, take, skip)
|
await self._build_conditional_query(filters, sorts, take, skip)
|
||||||
)
|
)
|
||||||
if not result or len(result) == 0:
|
if not result or len(result) == 0:
|
||||||
raise ValueError("No result found")
|
raise ValueError("No result found")
|
||||||
@ -193,11 +206,11 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
return [self.to_object(x) for x in result]
|
return [self.to_object(x) for x in result]
|
||||||
|
|
||||||
async def get_single_by(
|
async def get_single_by(
|
||||||
self,
|
self,
|
||||||
filters: AttributeFilters = None,
|
filters: AttributeFilters = None,
|
||||||
sorts: AttributeSorts = None,
|
sorts: AttributeSorts = None,
|
||||||
take: int = None,
|
take: int = None,
|
||||||
skip: int = None,
|
skip: int = None,
|
||||||
) -> T_DBM:
|
) -> T_DBM:
|
||||||
"""
|
"""
|
||||||
Get a single object by the given filters
|
Get a single object by the given filters
|
||||||
@ -218,11 +231,11 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
return result[0]
|
return result[0]
|
||||||
|
|
||||||
async def find_by(
|
async def find_by(
|
||||||
self,
|
self,
|
||||||
filters: AttributeFilters = None,
|
filters: AttributeFilters = None,
|
||||||
sorts: AttributeSorts = None,
|
sorts: AttributeSorts = None,
|
||||||
take: int = None,
|
take: int = None,
|
||||||
skip: int = None,
|
skip: int = None,
|
||||||
) -> list[Optional[T_DBM]]:
|
) -> list[Optional[T_DBM]]:
|
||||||
"""
|
"""
|
||||||
Find all objects by the given filters
|
Find all objects by the given filters
|
||||||
@ -234,7 +247,7 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
:rtype: list[Optional[T_DBM]]
|
:rtype: list[Optional[T_DBM]]
|
||||||
"""
|
"""
|
||||||
result = await self._db.select_map(
|
result = await self._db.select_map(
|
||||||
self._build_conditional_query(filters, sorts, take, skip)
|
await self._build_conditional_query(filters, sorts, take, skip)
|
||||||
)
|
)
|
||||||
if not result or len(result) == 0:
|
if not result or len(result) == 0:
|
||||||
return []
|
return []
|
||||||
@ -242,11 +255,11 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
return [self.to_object(x) for x in result]
|
return [self.to_object(x) for x in result]
|
||||||
|
|
||||||
async def find_single_by(
|
async def find_single_by(
|
||||||
self,
|
self,
|
||||||
filters: AttributeFilters = None,
|
filters: AttributeFilters = None,
|
||||||
sorts: AttributeSorts = None,
|
sorts: AttributeSorts = None,
|
||||||
take: int = None,
|
take: int = None,
|
||||||
skip: int = None,
|
skip: int = None,
|
||||||
) -> Optional[T_DBM]:
|
) -> Optional[T_DBM]:
|
||||||
"""
|
"""
|
||||||
Find a single object by the given filters
|
Find a single object by the given filters
|
||||||
@ -346,7 +359,7 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
await self._db.execute(query)
|
await self._db.execute(query)
|
||||||
|
|
||||||
async def _build_delete_statement(
|
async def _build_delete_statement(
|
||||||
self, obj: T_DBM, hard_delete: bool = False
|
self, obj: T_DBM, hard_delete: bool = False
|
||||||
) -> str:
|
) -> str:
|
||||||
if hard_delete:
|
if hard_delete:
|
||||||
return f"""
|
return f"""
|
||||||
@ -424,7 +437,7 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
return "NULL"
|
return "NULL"
|
||||||
|
|
||||||
if isinstance(value, Enum):
|
if isinstance(value, Enum):
|
||||||
return str(value.value)
|
return f"'{value.value}'"
|
||||||
|
|
||||||
if isinstance(value, bool):
|
if isinstance(value, bool):
|
||||||
return "true" if value else "false"
|
return "true" if value else "false"
|
||||||
@ -461,77 +474,115 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
|
|
||||||
return cast_type(value)
|
return cast_type(value)
|
||||||
|
|
||||||
def _build_conditional_query(
|
async def _handle_query_external_temp_tables(
|
||||||
self,
|
self, query: str, external_table_deps: list[str], ignore_fields=False
|
||||||
filters: AttributeFilters = None,
|
) -> str:
|
||||||
sorts: AttributeSorts = None,
|
for dep in external_table_deps:
|
||||||
take: int = None,
|
temp_table = self._external_fields[dep]
|
||||||
skip: int = None,
|
temp_table_sql = await temp_table.build()
|
||||||
|
|
||||||
|
if not ignore_fields:
|
||||||
|
query = query.replace(
|
||||||
|
" FROM",
|
||||||
|
f", {','.join([f'{temp_table.table_name}.{x}' for x in temp_table.fields.keys() if x not in self.__db_names])} FROM",
|
||||||
|
)
|
||||||
|
|
||||||
|
query = f"{temp_table_sql}\n{query}"
|
||||||
|
query += f" LEFT JOIN {temp_table.table_name} ON {temp_table.join_ref_table}.{self.__primary_key} = {temp_table.table_name}.{temp_table.primary_key}"
|
||||||
|
|
||||||
|
return query
|
||||||
|
|
||||||
|
async def _build_conditional_query(
|
||||||
|
self,
|
||||||
|
filters: AttributeFilters = None,
|
||||||
|
sorts: AttributeSorts = None,
|
||||||
|
take: int = None,
|
||||||
|
skip: int = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
query = f"SELECT {self._table_name}.* FROM {self._table_name}"
|
query = f"SELECT {self._table_name}.* FROM {self._table_name}"
|
||||||
|
|
||||||
for join in self.__joins:
|
for join in self.__joins:
|
||||||
query += f" {self.__joins[join]}"
|
query += f" {self.__joins[join]}"
|
||||||
|
|
||||||
if filters is not None and (not isinstance(filters, list) or len(filters) > 0):
|
if filters is not None and (not isinstance(filters, list) or len(filters) > 0):
|
||||||
query += f" WHERE {self._build_conditions(filters)}"
|
conditions, external_table_deps = await self._build_conditions(filters)
|
||||||
|
query = await self._handle_query_external_temp_tables(
|
||||||
|
query, external_table_deps
|
||||||
|
)
|
||||||
|
query += f" WHERE {conditions}"
|
||||||
if sorts is not None and (not isinstance(sorts, list) or len(sorts) > 0):
|
if sorts is not None and (not isinstance(sorts, list) or len(sorts) > 0):
|
||||||
query += f" ORDER BY {self._build_order_by(sorts)}"
|
query += f" ORDER BY {self._build_order_by(sorts)}"
|
||||||
if take is not None:
|
if take is not None:
|
||||||
query += f" LIMIT {take}"
|
query += f" LIMIT {take}"
|
||||||
if skip is not None:
|
if skip is not None:
|
||||||
query += f" OFFSET {skip}"
|
query += f" OFFSET {skip}"
|
||||||
|
|
||||||
|
if not query.endswith(";"):
|
||||||
|
query += ";"
|
||||||
return query
|
return query
|
||||||
|
|
||||||
def _build_conditions(self, filters: AttributeFilters) -> str:
|
def _get_external_field_key(self, field_name: str) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Returns the key to get the external field if found, otherwise None.
|
||||||
|
:param str field_name: The name of the field to search for.
|
||||||
|
:return: The key if found, otherwise None.
|
||||||
|
:rtype: Optional[str]
|
||||||
|
"""
|
||||||
|
for key, builder in self._external_fields.items():
|
||||||
|
if field_name in builder.fields and field_name not in self.__db_names:
|
||||||
|
return key
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _build_conditions(self, filters: AttributeFilters) -> (str, list[str]):
|
||||||
"""
|
"""
|
||||||
Build SQL conditions from the given filters
|
Build SQL conditions from the given filters
|
||||||
:param filters:
|
:param filters:
|
||||||
:return:
|
:return: SQL conditions & External field table dependencies
|
||||||
"""
|
"""
|
||||||
|
external_field_table_deps = []
|
||||||
if not isinstance(filters, list):
|
if not isinstance(filters, list):
|
||||||
filters = [filters]
|
filters = [filters]
|
||||||
|
|
||||||
conditions = []
|
conditions = []
|
||||||
for f in filters:
|
for f in filters:
|
||||||
|
f_conditions = []
|
||||||
|
|
||||||
for attr, values in f.items():
|
for attr, values in f.items():
|
||||||
if isinstance(attr, property):
|
if isinstance(attr, property):
|
||||||
attr = attr.fget.__name__
|
attr = attr.fget.__name__
|
||||||
|
|
||||||
if attr in self.__foreign_tables:
|
if attr in self.__foreign_tables:
|
||||||
foreign_table = self.__foreign_tables[attr]
|
foreign_table = self.__foreign_tables[attr]
|
||||||
conditions.extend(
|
cons, eftd = self._build_foreign_conditions(foreign_table, values)
|
||||||
self._build_foreign_conditions(foreign_table, values)
|
if eftd:
|
||||||
)
|
external_field_table_deps.extend(eftd)
|
||||||
|
|
||||||
|
f_conditions.extend(cons)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if attr == "fuzzy":
|
if attr == "fuzzy":
|
||||||
conditions.append(
|
self._handle_fuzzy_filter_conditions(
|
||||||
" OR ".join(
|
f_conditions, external_field_table_deps, values
|
||||||
self._build_fuzzy_conditions(
|
|
||||||
[
|
|
||||||
(
|
|
||||||
self.__db_names[x]
|
|
||||||
if x in self.__db_names
|
|
||||||
else self.__db_names[camel_to_snake(x)]
|
|
||||||
)
|
|
||||||
for x in get_value(values, "fields", list[str])
|
|
||||||
],
|
|
||||||
get_value(values, "term", str),
|
|
||||||
get_value(values, "threshold", int, 5),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
db_name = self.__db_names[attr]
|
external_fields_table_name = self._get_external_field_key(attr)
|
||||||
|
if external_fields_table_name is not None:
|
||||||
|
external_fields_table = self._external_fields[
|
||||||
|
external_fields_table_name
|
||||||
|
]
|
||||||
|
db_name = f"{external_fields_table.table_name}.{attr}"
|
||||||
|
external_field_table_deps.append(external_fields_table.table_name)
|
||||||
|
elif (
|
||||||
|
isinstance(values, dict) or isinstance(values, list)
|
||||||
|
) and not attr in self.__foreign_tables:
|
||||||
|
db_name = f"{self._table_name}.{self.__db_names[attr]}"
|
||||||
|
else:
|
||||||
|
db_name = self.__db_names[attr]
|
||||||
|
|
||||||
if isinstance(values, dict):
|
if isinstance(values, dict):
|
||||||
for operator, value in values.items():
|
for operator, value in values.items():
|
||||||
conditions.append(
|
f_conditions.append(
|
||||||
self._build_condition(
|
self._build_condition(f"{db_name}", operator, value)
|
||||||
f"{self._table_name}.{db_name}", operator, value
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
elif isinstance(values, list):
|
elif isinstance(values, list):
|
||||||
sub_conditions = []
|
sub_conditions = []
|
||||||
@ -539,38 +590,42 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
for operator, val in value.items():
|
for operator, val in value.items():
|
||||||
sub_conditions.append(
|
sub_conditions.append(
|
||||||
self._build_condition(
|
self._build_condition(f"{db_name}", operator, val)
|
||||||
f"{self._table_name}.{db_name}", operator, val
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
sub_conditions.append(
|
sub_conditions.append(
|
||||||
self._get_value_validation_sql(db_name, value)
|
self._get_value_validation_sql(db_name, value)
|
||||||
)
|
)
|
||||||
conditions.append(f"({' OR '.join(sub_conditions)})")
|
f_conditions.append(f"({' OR '.join(sub_conditions)})")
|
||||||
else:
|
else:
|
||||||
conditions.append(self._get_value_validation_sql(db_name, values))
|
f_conditions.append(self._get_value_validation_sql(db_name, values))
|
||||||
|
|
||||||
return " AND ".join(conditions)
|
conditions.append(f"({' OR '.join(f_conditions)})")
|
||||||
|
|
||||||
|
return " AND ".join(conditions), external_field_table_deps
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def _build_fuzzy_conditions(
|
def _build_fuzzy_conditions(
|
||||||
self, fields: list[str], term: str, threshold: int = 10
|
fields: list[str], term: str, threshold: int = 10
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
conditions = []
|
conditions = []
|
||||||
for field in fields:
|
for field in fields:
|
||||||
conditions.append(
|
conditions.append(
|
||||||
f"levenshtein({field}, '{term}') <= {threshold}"
|
f"levenshtein({field}::TEXT, '{term}') <= {threshold}"
|
||||||
) # Adjust the threshold as needed
|
) # Adjust the threshold as needed
|
||||||
|
|
||||||
return conditions
|
return conditions
|
||||||
|
|
||||||
def _build_foreign_conditions(self, table: str, values: dict) -> list[str]:
|
def _build_foreign_conditions(
|
||||||
|
self, table: str, values: dict
|
||||||
|
) -> (list[str], list[str]):
|
||||||
"""
|
"""
|
||||||
Build SQL conditions for foreign key references
|
Build SQL conditions for foreign key references
|
||||||
:param table: Foreign table name
|
:param table: Foreign table name
|
||||||
:param values: Filter values
|
:param values: Filter values
|
||||||
:return: List of conditions
|
:return: List of conditions, List of external field tables
|
||||||
"""
|
"""
|
||||||
|
external_field_table_deps = []
|
||||||
conditions = []
|
conditions = []
|
||||||
for attr, sub_values in values.items():
|
for attr, sub_values in values.items():
|
||||||
if isinstance(attr, property):
|
if isinstance(attr, property):
|
||||||
@ -578,25 +633,43 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
|
|
||||||
if attr in self.__foreign_tables:
|
if attr in self.__foreign_tables:
|
||||||
foreign_table = self.__foreign_tables[attr]
|
foreign_table = self.__foreign_tables[attr]
|
||||||
conditions.extend(
|
sub_conditions, eftd = self._build_foreign_conditions(
|
||||||
self._build_foreign_conditions(foreign_table, sub_values)
|
foreign_table, sub_values
|
||||||
|
)
|
||||||
|
if len(eftd) > 0:
|
||||||
|
external_field_table_deps.extend(eftd)
|
||||||
|
|
||||||
|
conditions.extend(sub_conditions)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if attr == "fuzzy":
|
||||||
|
self._handle_fuzzy_filter_conditions(
|
||||||
|
conditions, external_field_table_deps, sub_values
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
db_name = f"{table}.{attr.lower().replace('_', '')}"
|
external_fields_table_name = self._get_external_field_key(attr)
|
||||||
|
if external_fields_table_name is not None:
|
||||||
|
external_fields_table = self._external_fields[
|
||||||
|
external_fields_table_name
|
||||||
|
]
|
||||||
|
db_name = f"{external_fields_table.table_name}.{attr}"
|
||||||
|
external_field_table_deps.append(external_fields_table.table_name)
|
||||||
|
else:
|
||||||
|
db_name = f"{table}.{attr.lower().replace('_', '')}"
|
||||||
|
|
||||||
if isinstance(sub_values, dict):
|
if isinstance(sub_values, dict):
|
||||||
for operator, value in sub_values.items():
|
for operator, value in sub_values.items():
|
||||||
conditions.append(
|
conditions.append(
|
||||||
f"({self._build_condition(db_name, operator, value)} OR {self._build_condition(db_name, "isNull", None)})")
|
f"{self._build_condition(db_name, operator, value)}"
|
||||||
|
)
|
||||||
elif isinstance(sub_values, list):
|
elif isinstance(sub_values, list):
|
||||||
sub_conditions = []
|
sub_conditions = []
|
||||||
for value in sub_values:
|
for value in sub_values:
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
for operator, val in value.items():
|
for operator, val in value.items():
|
||||||
sub_conditions.append(
|
sub_conditions.append(
|
||||||
f"({self._build_condition(db_name, operator, val)} OR {self._build_condition(db_name, "isNull", None)})"
|
f"{self._build_condition(db_name, operator, val)}"
|
||||||
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
sub_conditions.append(
|
sub_conditions.append(
|
||||||
@ -606,14 +679,55 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
else:
|
else:
|
||||||
conditions.append(self._get_value_validation_sql(db_name, sub_values))
|
conditions.append(self._get_value_validation_sql(db_name, sub_values))
|
||||||
|
|
||||||
return conditions
|
return conditions, external_field_table_deps
|
||||||
|
|
||||||
|
def _handle_fuzzy_filter_conditions(
|
||||||
|
self, conditions, external_field_table_deps, sub_values
|
||||||
|
):
|
||||||
|
fuzzy_fields = get_value(sub_values, "fields", list[str])
|
||||||
|
fuzzy_fields_db_names = []
|
||||||
|
for fuzzy_field in fuzzy_fields:
|
||||||
|
external_fields_table_name = self._get_external_field_key(fuzzy_field)
|
||||||
|
if external_fields_table_name is not None:
|
||||||
|
external_fields_table = self._external_fields[
|
||||||
|
external_fields_table_name
|
||||||
|
]
|
||||||
|
fuzzy_fields_db_names.append(
|
||||||
|
f"{external_fields_table.table_name}.{fuzzy_field}"
|
||||||
|
)
|
||||||
|
external_field_table_deps.append(external_fields_table.table_name)
|
||||||
|
elif fuzzy_field in self.__db_names:
|
||||||
|
fuzzy_fields_db_names.append(
|
||||||
|
f"{self._table_name}.{self.__db_names[fuzzy_field]}"
|
||||||
|
)
|
||||||
|
elif fuzzy_field in self.__foreign_tables:
|
||||||
|
fuzzy_fields_db_names.append(
|
||||||
|
f"{self._table_name}.{self.__foreign_table_keys[fuzzy_field]}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
fuzzy_fields_db_names.append(
|
||||||
|
self.__db_names[camel_to_snake(fuzzy_field)]
|
||||||
|
)
|
||||||
|
conditions.append(
|
||||||
|
f"({' OR '.join(
|
||||||
|
self._build_fuzzy_conditions(
|
||||||
|
[x for x in fuzzy_fields_db_names],
|
||||||
|
get_value(sub_values, "term", str),
|
||||||
|
get_value(sub_values, "threshold", int, 5),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})"
|
||||||
|
)
|
||||||
|
|
||||||
def _get_value_validation_sql(self, field: str, value: Any):
|
def _get_value_validation_sql(self, field: str, value: Any):
|
||||||
value = self._get_value_sql(value)
|
value = self._get_value_sql(value)
|
||||||
|
field_selector = f"{self._table_name}.{field}"
|
||||||
|
if field in self.__foreign_tables:
|
||||||
|
field_selector = self.__db_names[field]
|
||||||
|
|
||||||
if value == "NULL":
|
if value == "NULL":
|
||||||
return f"{self._table_name}.{field} IS NULL"
|
return f"{field_selector} IS NULL"
|
||||||
return f"{self._table_name}.{field} = {value}"
|
return f"{field_selector} = {value}"
|
||||||
|
|
||||||
def _build_condition(self, db_name: str, operator: str, value: Any) -> str:
|
def _build_condition(self, db_name: str, operator: str, value: Any) -> str:
|
||||||
"""
|
"""
|
||||||
|
72
api/src/core/database/external_data_temp_table_builder.py
Normal file
72
api/src/core/database/external_data_temp_table_builder.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import textwrap
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalDataTempTableBuilder:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._table_name = None
|
||||||
|
self._fields: dict[str, str] = {}
|
||||||
|
self._primary_key = "id"
|
||||||
|
self._join_ref_table = None
|
||||||
|
self._value_getter = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def table_name(self) -> str:
|
||||||
|
return self._table_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fields(self) -> dict[str, str]:
|
||||||
|
return self._fields
|
||||||
|
|
||||||
|
@property
|
||||||
|
def primary_key(self) -> str:
|
||||||
|
return self._primary_key
|
||||||
|
|
||||||
|
@property
|
||||||
|
def join_ref_table(self) -> str:
|
||||||
|
return self._join_ref_table
|
||||||
|
|
||||||
|
def with_table_name(self, table_name: str) -> "ExternalDataTempTableBuilder":
|
||||||
|
self._join_ref_table = table_name
|
||||||
|
|
||||||
|
if "." in table_name:
|
||||||
|
table_name = table_name.split(".")[-1]
|
||||||
|
|
||||||
|
if not table_name.endswith("_temp"):
|
||||||
|
table_name = f"{table_name}_temp"
|
||||||
|
|
||||||
|
self._table_name = table_name
|
||||||
|
return self
|
||||||
|
|
||||||
|
def with_field(
|
||||||
|
self, name: str, sql_type: str, primary=False
|
||||||
|
) -> "ExternalDataTempTableBuilder":
|
||||||
|
if primary:
|
||||||
|
sql_type += " PRIMARY KEY"
|
||||||
|
self._primary_key = name
|
||||||
|
self._fields[name] = sql_type
|
||||||
|
return self
|
||||||
|
|
||||||
|
def with_value_getter(
|
||||||
|
self, value_getter: Callable
|
||||||
|
) -> "ExternalDataTempTableBuilder":
|
||||||
|
self._value_getter = value_getter
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def build(self) -> str:
|
||||||
|
assert self._table_name is not None, "Table name is required"
|
||||||
|
assert self._value_getter is not None, "Value getter is required"
|
||||||
|
|
||||||
|
values_str = ", ".join([f"{value}" for value in await self._value_getter()])
|
||||||
|
|
||||||
|
return textwrap.dedent(
|
||||||
|
f"""
|
||||||
|
DROP TABLE IF EXISTS {self._table_name};
|
||||||
|
CREATE TEMP TABLE {self._table_name} (
|
||||||
|
{", ".join([f"{k} {v}" for k, v in self._fields.items()])}
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO {self._table_name} VALUES {values_str};
|
||||||
|
"""
|
||||||
|
)
|
@ -1,12 +1,37 @@
|
|||||||
<div class="flex justify-between w-full">
|
<div class="flex justify-between w-full">
|
||||||
<div *ngFor="let element of elements">
|
<div *ngFor="let element of visibleElements" class="flex justify-center">
|
||||||
<p-button
|
<ng-container *ngIf="element.visible !== false && element.routerLink">
|
||||||
*ngIf="element.visible !== false"
|
<a
|
||||||
type="button"
|
[routerLink]="element.routerLink"
|
||||||
label="{{ element.label | translate }}"
|
(click)="element.command?.()"
|
||||||
[icon]="element.icon"
|
class="icon-btn btn flex gap-2">
|
||||||
class="icon-btn btn"
|
<i *ngIf="element.icon" class="{{ element.icon }}"></i>
|
||||||
[routerLink]="element.routerLink"
|
<b>{{ element.label | translate }}</b>
|
||||||
(onClick)="element.command?.()"></p-button>
|
</a>
|
||||||
</div>
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="element.visible !== false && element.items">
|
||||||
|
<p-button
|
||||||
|
class="icon-btn btn"
|
||||||
|
icon="{{ element.icon }}"
|
||||||
|
(onClick)="overlayPanel.toggle($event)"
|
||||||
|
label="{{ element.label | translate }}">
|
||||||
|
</p-button>
|
||||||
|
|
||||||
|
<p-overlayPanel [styleClass]="theme" #overlayPanel>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div *ngFor="let item of element.items" class="flex gap-1">
|
||||||
|
<a
|
||||||
|
*ngIf="item.visible !== false"
|
||||||
|
[routerLink]="item.routerLink"
|
||||||
|
(click)="overlayPanel.toggle($event); item.command?.()"
|
||||||
|
class="icon-btn btn flex gap-2">
|
||||||
|
<i *ngIf="item.icon" class="{{ item.icon }}"></i>
|
||||||
|
<b>{{ item.label | translate }}</b>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</p-overlayPanel>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +1,32 @@
|
|||||||
import { Component, Input } from "@angular/core";
|
import { Component, EventEmitter, Input, OnDestroy } from '@angular/core';
|
||||||
import { MenuElement } from "src/app/model/view/menu-element";
|
import { MenuElement } from 'src/app/model/view/menu-element';
|
||||||
|
import { GuiService } from 'src/app/service/gui.service';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-menu-bar",
|
selector: 'app-menu-bar',
|
||||||
templateUrl: "./menu-bar.component.html",
|
templateUrl: './menu-bar.component.html',
|
||||||
styleUrl: "./menu-bar.component.scss",
|
styleUrl: './menu-bar.component.scss',
|
||||||
})
|
})
|
||||||
export class MenuBarComponent {
|
export class MenuBarComponent implements OnDestroy {
|
||||||
@Input() elements: MenuElement[] = [];
|
@Input() elements: MenuElement[] = [];
|
||||||
|
protected theme = '';
|
||||||
|
private unsubscribe$ = new EventEmitter<void>();
|
||||||
|
|
||||||
|
get visibleElements() {
|
||||||
|
return this.elements.filter(e => e.visible !== false);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private guiService: GuiService) {
|
||||||
|
this.guiService.theme$
|
||||||
|
.pipe(takeUntil(this.unsubscribe$))
|
||||||
|
.subscribe(theme => {
|
||||||
|
this.theme = theme;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.unsubscribe$.next();
|
||||||
|
this.unsubscribe$.complete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,147 +1,172 @@
|
|||||||
<p-table
|
<p-table
|
||||||
[dataKey]="dataKey"
|
[dataKey]="dataKey"
|
||||||
[value]="rows"
|
[value]="rows"
|
||||||
[paginator]="true"
|
[paginator]="true"
|
||||||
[rowsPerPageOptions]="rowsPerPageOptions"
|
[rowsPerPageOptions]="rowsPerPageOptions"
|
||||||
[rows]="take"
|
[rows]="take"
|
||||||
(rowsChange)="takeChange.emit($event)"
|
(rowsChange)="takeChange.emit($event)"
|
||||||
[first]="skip"
|
[first]="skip"
|
||||||
(firstChange)="skipChange.emit($event)"
|
(firstChange)="skipChange.emit($event)"
|
||||||
[totalRecords]="totalCount"
|
[totalRecords]="totalCount"
|
||||||
[lazy]="true"
|
[lazy]="true"
|
||||||
(onLazyLoad)="loadData($event)"
|
(onLazyLoad)="loadData($event)"
|
||||||
[loading]="loading"
|
[loading]="loading"
|
||||||
[resizableColumns]="false"
|
[resizableColumns]="false"
|
||||||
[reorderableColumns]="false"
|
[reorderableColumns]="false"
|
||||||
[responsiveLayout]="responsiveLayout"
|
[responsiveLayout]="responsiveLayout"
|
||||||
columnResizeMode="expand"
|
columnResizeMode="expand"
|
||||||
[breakpoint]="'900px'"
|
[breakpoint]="'900px'"
|
||||||
[rowHover]="true">
|
[rowHover]="true">
|
||||||
<ng-template pTemplate="caption">
|
<ng-template pTemplate="caption">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
<div *ngIf="countHeaderTranslation" class="table-caption-table-info">
|
<div *ngIf="countHeaderTranslation" class="table-caption-table-info">
|
||||||
<div class="table-caption-text">
|
<div class="table-caption-text">
|
||||||
<ng-container *ngIf="!loading"
|
<ng-container *ngIf="!loading"
|
||||||
>{{ skip + 1 }} {{ 'table.to' | translate }}
|
>{{ skip + 1 }} {{ 'table.to' | translate }}
|
||||||
{{ skip + rows.length }} {{ 'table.of' | translate }}
|
{{ skip + rows.length }} {{ 'table.of' | translate }}
|
||||||
{{ totalCount }}
|
{{ totalCount }}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
{{ countHeaderTranslation | translate }}
|
{{ countHeaderTranslation | translate }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2">
|
||||||
<ng-container *ngTemplateOutlet="customActions"></ng-container>
|
<ng-container *ngTemplateOutlet="customActions"></ng-container>
|
||||||
<p-button
|
<p-button
|
||||||
*ngIf="create && hasPermissions.create"
|
*ngIf="create && hasPermissions.create"
|
||||||
class="icon-btn btn"
|
class="icon-btn btn"
|
||||||
icon="pi pi-plus"
|
icon="pi pi-plus"
|
||||||
tooltipPosition="left"
|
tooltipPosition="left"
|
||||||
pTooltip="{{ 'table.create' | translate }}"
|
pTooltip="{{ 'table.create' | translate }}"
|
||||||
routerLink="{{ updateBaseUrl }}create"></p-button>
|
routerLink="{{ updateBaseUrl }}create"></p-button>
|
||||||
<p-button
|
<p-button
|
||||||
class="icon-btn btn"
|
class="icon-btn btn"
|
||||||
[styleClass]="showDeleted ? 'highlight2' : ''"
|
[styleClass]="showDeleted ? 'highlight2' : ''"
|
||||||
icon="pi pi-trash"
|
icon="pi pi-trash"
|
||||||
tooltipPosition="left"
|
tooltipPosition="left"
|
||||||
pTooltip="{{
|
pTooltip="{{
|
||||||
(showDeleted ? 'table.hide_deleted' : 'table.show_deleted')
|
(showDeleted ? 'table.hide_deleted' : 'table.show_deleted')
|
||||||
| translate
|
| translate
|
||||||
}}"
|
}}"
|
||||||
(click)="toggleShowDeleted()"></p-button>
|
(click)="toggleShowDeleted()"></p-button>
|
||||||
<p-button
|
<p-button
|
||||||
class="icon-btn btn"
|
class="icon-btn btn"
|
||||||
icon="pi pi-sort-alt-slash"
|
icon="pi pi-sort-alt-slash"
|
||||||
tooltipPosition="left"
|
tooltipPosition="left"
|
||||||
pTooltip="{{ 'table.reset_sort' | translate }}"
|
pTooltip="{{ 'table.reset_sort' | translate }}"
|
||||||
(click)="resetSort()"></p-button>
|
(click)="resetSort()"></p-button>
|
||||||
<p-button
|
<p-button
|
||||||
class="icon-btn btn"
|
class="icon-btn btn"
|
||||||
icon="pi pi-filter-slash"
|
icon="pi pi-filter-slash"
|
||||||
tooltipPosition="left"
|
tooltipPosition="left"
|
||||||
pTooltip="{{ 'table.reset_filters' | translate }}"
|
pTooltip="{{ 'table.reset_filters' | translate }}"
|
||||||
(click)="resetFilters()"></p-button>
|
(click)="resetFilters()"></p-button>
|
||||||
<app-column-selector
|
<app-column-selector
|
||||||
*ngIf="selectableColumns"
|
*ngIf="selectableColumns"
|
||||||
[columns]="columns"
|
[columns]="columns"
|
||||||
(selectChange)="resolveColumns()"></app-column-selector>
|
(selectChange)="resolveColumns()"></app-column-selector>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template pTemplate="header">
|
|
||||||
<tr>
|
|
||||||
<th
|
|
||||||
*ngFor="let column of visibleColumns"
|
|
||||||
[pSortableColumn]="column.name"
|
|
||||||
[class]="column.class ?? ''"
|
|
||||||
[style.min-width]="
|
|
||||||
column.minWidth ? column.minWidth + ' !important' : ''
|
|
||||||
"
|
|
||||||
[style.width]="column.width ? column.width + ' !important' : ''"
|
|
||||||
[style.max-width]="
|
|
||||||
column.maxWidth ? column.maxWidth + ' !important' : ''
|
|
||||||
">
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<span>{{ column.translationKey | translate }}</span>
|
|
||||||
<p-sortIcon [field]="column.name"></p-sortIcon>
|
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</ng-template>
|
||||||
<th *ngIf="update || delete.observed"></th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr *ngIf="showFilters">
|
<ng-template pTemplate="header">
|
||||||
<th
|
<tr>
|
||||||
*ngFor="let column of visibleColumns"
|
<th
|
||||||
[class]="column.class ?? ''"
|
*ngFor="let column of visibleColumns"
|
||||||
[style.min-width]="
|
[pSortableColumn]="column.name"
|
||||||
|
[class]="column.class ?? ''"
|
||||||
|
[style.min-width]="
|
||||||
column.minWidth ? column.minWidth + ' !important' : ''
|
column.minWidth ? column.minWidth + ' !important' : ''
|
||||||
"
|
"
|
||||||
[style.width]="column.width ? column.width + ' !important' : ''"
|
[style.width]="column.width ? column.width + ' !important' : ''"
|
||||||
[style.max-width]="
|
[style.max-width]="
|
||||||
column.maxWidth ? column.maxWidth + ' !important' : ''
|
column.maxWidth ? column.maxWidth + ' !important' : ''
|
||||||
">
|
">
|
||||||
<form *ngIf="filterForm && column.filterable" [formGroup]="filterForm">
|
<div class="flex items-center space-x-2">
|
||||||
<ng-container [ngSwitch]="column.type">
|
<span>{{ column.translationKey | translate }}</span>
|
||||||
<input
|
<p-sortIcon [field]="column.name"></p-sortIcon>
|
||||||
*ngSwitchCase="'date'"
|
</div>
|
||||||
pInputText
|
</th>
|
||||||
type="text"
|
<th *ngIf="update || delete.observed || customRowActionsVisible"></th>
|
||||||
[formControlName]="column.name"
|
</tr>
|
||||||
class="w-full box-border" />
|
|
||||||
<p-triStateCheckbox
|
<tr *ngIf="fuzzyFilter">
|
||||||
*ngSwitchCase="'bool'"
|
<th [attr.colspan]="visibleColumns.length">
|
||||||
[formControlName]="column.name"
|
<form
|
||||||
class="w-full box-border"></p-triStateCheckbox>
|
*ngIf="filterForm"
|
||||||
<input
|
[formGroup]="filterForm"
|
||||||
*ngSwitchDefault
|
class="flex gap-2 justify-between items-center">
|
||||||
pInputText
|
<input
|
||||||
[formControlName]="column.name"
|
pInputText
|
||||||
class="w-full box-border" />
|
type="text"
|
||||||
</ng-container>
|
class="w-full box-border"
|
||||||
</form>
|
formControlName="fuzzy"
|
||||||
</th>
|
placeholder="{{ 'table.search' | translate }}" />
|
||||||
<th *ngIf="update || delete.observed"></th>
|
<p-button
|
||||||
</tr>
|
class="btn icon-btn"
|
||||||
</ng-template>
|
icon="pi pi-times"
|
||||||
<ng-template pTemplate="body" let-row let-i="rowIndex">
|
(onClick)="filterForm.get('fuzzy')?.setValue(undefined)"
|
||||||
<tr *ngIf="hasPermissions.read && resolvedColumns.length > 0">
|
[disabled]="!filterForm.get('fuzzy')?.value"></p-button>
|
||||||
<ng-container *ngFor="let r of resolvedColumns[i - take * (skip / take)]">
|
</form>
|
||||||
<td
|
</th>
|
||||||
[ngClass]="{ deleted: row?.deleted }"
|
<th *ngIf="update || delete.observed || customRowActionsVisible"></th>
|
||||||
[style.min-width]="
|
</tr>
|
||||||
|
|
||||||
|
<tr *ngIf="showFilters">
|
||||||
|
<th
|
||||||
|
*ngFor="let column of visibleColumns"
|
||||||
|
[class]="column.class ?? ''"
|
||||||
|
[style.min-width]="
|
||||||
|
column.minWidth ? column.minWidth + ' !important' : ''
|
||||||
|
"
|
||||||
|
[style.width]="column.width ? column.width + ' !important' : ''"
|
||||||
|
[style.max-width]="
|
||||||
|
column.maxWidth ? column.maxWidth + ' !important' : ''
|
||||||
|
">
|
||||||
|
<form *ngIf="filterForm && column.filterable" [formGroup]="filterForm">
|
||||||
|
<ng-container [ngSwitch]="column.type">
|
||||||
|
<input
|
||||||
|
*ngSwitchCase="'date'"
|
||||||
|
pInputText
|
||||||
|
type="text"
|
||||||
|
[formControlName]="column.name"
|
||||||
|
class="w-full box-border" />
|
||||||
|
<p-triStateCheckbox
|
||||||
|
*ngSwitchCase="'bool'"
|
||||||
|
[formControlName]="column.name"
|
||||||
|
class="w-full box-border"></p-triStateCheckbox>
|
||||||
|
<input
|
||||||
|
*ngSwitchDefault
|
||||||
|
pInputText
|
||||||
|
[formControlName]="column.name"
|
||||||
|
class="w-full box-border" />
|
||||||
|
</ng-container>
|
||||||
|
</form>
|
||||||
|
</th>
|
||||||
|
<th *ngIf="update || delete.observed || customRowActionsVisible"></th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template pTemplate="body" let-row let-i="rowIndex">
|
||||||
|
<tr *ngIf="hasPermissions.read && resolvedColumns.length > 0">
|
||||||
|
<ng-container *ngFor="let r of resolvedColumns[i - take * (skip / take)]">
|
||||||
|
<td
|
||||||
|
[ngClass]="{
|
||||||
|
deleted: row?.deleted,
|
||||||
|
'row-disabled': rowDisabled(row),
|
||||||
|
}"
|
||||||
|
[style.min-width]="
|
||||||
r.column.minWidth ? r.column.minWidth + ' !important' : ''
|
r.column.minWidth ? r.column.minWidth + ' !important' : ''
|
||||||
"
|
"
|
||||||
[style.width]="r.column.width ? r.column.width + ' !important' : ''"
|
[style.width]="r.column.width ? r.column.width + ' !important' : ''"
|
||||||
[style.max-width]="
|
[style.max-width]="
|
||||||
r.column.maxWidth ? r.column.maxWidth + ' !important' : ''
|
r.column.maxWidth ? r.column.maxWidth + ' !important' : ''
|
||||||
">
|
">
|
||||||
<ng-container [ngSwitch]="r.column.type">
|
<ng-container [ngSwitch]="r.column.type">
|
||||||
<span *ngSwitchCase="'date'">
|
<span *ngSwitchCase="'date'">
|
||||||
{{ r.value | customDate: 'dd.MM.yyyy HH:mm:ss' }}
|
{{ r.value | customDate: 'dd.MM.yyyy HH:mm:ss' }}
|
||||||
</span>
|
</span>
|
||||||
<span *ngSwitchCase="'bool'">
|
<span *ngSwitchCase="'bool'">
|
||||||
<ng-container [ngSwitch]="r.value">
|
<ng-container [ngSwitch]="r.value">
|
||||||
<span *ngSwitchCase="true">
|
<span *ngSwitchCase="true">
|
||||||
<span class="pi pi-check-circle info"></span>
|
<span class="pi pi-check-circle info"></span>
|
||||||
@ -151,56 +176,57 @@
|
|||||||
</span>
|
</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</span>
|
</span>
|
||||||
<span *ngSwitchCase="'password'">
|
<span *ngSwitchCase="'password'">
|
||||||
{{ r.value | protect }}
|
{{ r.value | protect }}
|
||||||
<p-button
|
<p-button
|
||||||
class="btn icon-btn"
|
class="btn icon-btn"
|
||||||
icon="pi pi-copy"
|
icon="pi pi-copy"
|
||||||
(onClick)="copy(r.value)"></p-button>
|
(onClick)="copy(r.value)"></p-button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngSwitchDefault>{{ r.value }}</span>
|
<span *ngSwitchDefault>{{ r.value }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<td class="flex max-w-32">
|
<td class="flex max-w-32">
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngTemplateOutlet="
|
*ngTemplateOutlet="
|
||||||
customRowActions;
|
customRowActions;
|
||||||
context: { $implicit: row }
|
context: { $implicit: row, rowDisabled: rowDisabled(row) }
|
||||||
"></ng-container>
|
"></ng-container>
|
||||||
<p-button
|
<p-button
|
||||||
*ngIf="update && hasPermissions.update"
|
*ngIf="update && hasPermissions.update"
|
||||||
class="icon-btn btn"
|
class="icon-btn btn"
|
||||||
icon="pi pi-pencil"
|
icon="pi pi-pencil"
|
||||||
tooltipPosition="left"
|
tooltipPosition="left"
|
||||||
pTooltip="{{ 'table.update' | translate }}"
|
pTooltip="{{ 'table.update' | translate }}"
|
||||||
[disabled]="row?.deleted"
|
[disabled]="rowDisabled(row) || row?.deleted"
|
||||||
routerLink="{{ updateBaseUrl }}edit/{{ row.id }}"></p-button>
|
routerLink="{{ updateBaseUrl }}edit/{{ row.id }}"></p-button>
|
||||||
<p-button
|
<p-button
|
||||||
*ngIf="delete.observed && hasPermissions.delete"
|
*ngIf="delete.observed && hasPermissions.delete"
|
||||||
class="icon-btn btn danger-icon-btn"
|
class="icon-btn btn danger-icon-btn"
|
||||||
icon="pi pi-trash"
|
icon="pi pi-trash"
|
||||||
tooltipPosition="left"
|
tooltipPosition="left"
|
||||||
pTooltip="{{ 'table.delete' | translate }}"
|
pTooltip="{{ 'table.delete' | translate }}"
|
||||||
[disabled]="row?.deleted"
|
[disabled]="rowDisabled(row) || row?.deleted"
|
||||||
(click)="delete.emit(row)"></p-button>
|
(click)="delete.emit(row)"></p-button>
|
||||||
<p-button
|
<p-button
|
||||||
*ngIf="restore.observed && row?.deleted && hasPermissions.restore"
|
*ngIf="restore.observed && row?.deleted && hasPermissions.restore"
|
||||||
class="icon-btn btn"
|
class="icon-btn btn"
|
||||||
icon="pi pi-undo"
|
icon="pi pi-undo"
|
||||||
tooltipPosition="left"
|
tooltipPosition="left"
|
||||||
pTooltip="{{ 'table.restore' | translate }}"
|
pTooltip="{{ 'table.restore' | translate }}"
|
||||||
(click)="restore.emit(row)"></p-button>
|
(click)="restore.emit(row)"
|
||||||
</td>
|
[disabled]="rowDisabled(row)"></p-button>
|
||||||
</tr>
|
</td>
|
||||||
</ng-template>
|
</tr>
|
||||||
<ng-template pTemplate="emptymessage">
|
</ng-template>
|
||||||
<tr></tr>
|
<ng-template pTemplate="emptymessage">
|
||||||
<tr>
|
<tr></tr>
|
||||||
<td [attr.colspan]="columns.length + 1">
|
<tr>
|
||||||
{{ 'table.no_entries_found' | translate }}
|
<td [attr.colspan]="columns.length + 1">
|
||||||
</td>
|
{{ 'table.no_entries_found' | translate }}
|
||||||
</tr>
|
</td>
|
||||||
<tr></tr>
|
</tr>
|
||||||
</ng-template>
|
<tr></tr>
|
||||||
|
</ng-template>
|
||||||
</p-table>
|
</p-table>
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
ResolvedTableColumn,
|
ResolvedTableColumn,
|
||||||
TableColumn,
|
TableColumn,
|
||||||
|
TableColumnFuzzyFilter,
|
||||||
TableRequireAnyPermissions,
|
TableRequireAnyPermissions,
|
||||||
} from 'src/app/modules/shared/components/table/table.model';
|
} from 'src/app/modules/shared/components/table/table.model';
|
||||||
import { TableLazyLoadEvent } from 'primeng/table';
|
import { TableLazyLoadEvent } from 'primeng/table';
|
||||||
@ -46,6 +47,7 @@ export class TableComponent<T> implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Input({ required: true }) columns: TableColumn<T>[] = [];
|
@Input({ required: true }) columns: TableColumn<T>[] = [];
|
||||||
|
@Input() fuzzyFilter?: TableColumnFuzzyFilter;
|
||||||
|
|
||||||
get visibleColumns() {
|
get visibleColumns() {
|
||||||
return this.columns.filter(x => x.visible);
|
return this.columns.filter(x => x.visible);
|
||||||
@ -70,6 +72,8 @@ export class TableComponent<T> implements OnInit {
|
|||||||
@Input() sort: Sort[] = [];
|
@Input() sort: Sort[] = [];
|
||||||
@Output() sortChange = new EventEmitter<Sort[]>();
|
@Output() sortChange = new EventEmitter<Sort[]>();
|
||||||
|
|
||||||
|
@Input() rowDisabled: (row: T) => boolean = () => false;
|
||||||
|
|
||||||
// eslint-disable-next-line @angular-eslint/no-output-native
|
// eslint-disable-next-line @angular-eslint/no-output-native
|
||||||
@Output() load = new EventEmitter<void>();
|
@Output() load = new EventEmitter<void>();
|
||||||
@Input() loading = true;
|
@Input() loading = true;
|
||||||
@ -92,6 +96,10 @@ export class TableComponent<T> implements OnInit {
|
|||||||
@ContentChild(CustomRowActionsDirective, { read: TemplateRef })
|
@ContentChild(CustomRowActionsDirective, { read: TemplateRef })
|
||||||
customRowActions!: TemplateRef<never>;
|
customRowActions!: TemplateRef<never>;
|
||||||
|
|
||||||
|
get customRowActionsVisible() {
|
||||||
|
return !!this.customRowActions;
|
||||||
|
}
|
||||||
|
|
||||||
protected resolvedColumns: ResolvedTableColumn<T>[][] = [];
|
protected resolvedColumns: ResolvedTableColumn<T>[][] = [];
|
||||||
protected filterForm!: FormGroup;
|
protected filterForm!: FormGroup;
|
||||||
protected defaultFilterForm!: FormGroup;
|
protected defaultFilterForm!: FormGroup;
|
||||||
@ -244,6 +252,13 @@ export class TableComponent<T> implements OnInit {
|
|||||||
|
|
||||||
buildDefaultFilterForm() {
|
buildDefaultFilterForm() {
|
||||||
this.defaultFilterForm = new FormGroup({});
|
this.defaultFilterForm = new FormGroup({});
|
||||||
|
if (this.fuzzyFilter) {
|
||||||
|
this.defaultFilterForm.addControl(
|
||||||
|
'fuzzy',
|
||||||
|
new FormControl<string | undefined>(undefined)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.columns
|
this.columns
|
||||||
.filter(x => x.filterable)
|
.filter(x => x.filterable)
|
||||||
.forEach(x => {
|
.forEach(x => {
|
||||||
@ -301,6 +316,17 @@ export class TableComponent<T> implements OnInit {
|
|||||||
changes[key] !== ''
|
changes[key] !== ''
|
||||||
)
|
)
|
||||||
.map(key => {
|
.map(key => {
|
||||||
|
if (key === 'fuzzy') {
|
||||||
|
if (!this.fuzzyFilter) return {};
|
||||||
|
return {
|
||||||
|
fuzzy: {
|
||||||
|
term: changes[key],
|
||||||
|
fields: this.fuzzyFilter?.columns,
|
||||||
|
threshold: this.fuzzyFilter?.threshold,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const column = this.columns.find(x => x.name === key);
|
const column = this.columns.find(x => x.name === key);
|
||||||
if (!column || !column.filterable) {
|
if (!column || !column.filterable) {
|
||||||
return {};
|
return {};
|
||||||
|
@ -22,6 +22,11 @@ export interface TableColumn<T> {
|
|||||||
class?: string;
|
class?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TableColumnFuzzyFilter {
|
||||||
|
columns: string[];
|
||||||
|
threshold?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ResolvedTableColumn<T> {
|
export interface ResolvedTableColumn<T> {
|
||||||
value: TableColumnValue<T>;
|
value: TableColumnValue<T>;
|
||||||
data: T;
|
data: T;
|
||||||
|
@ -15,8 +15,8 @@ export class ConfigService {
|
|||||||
imprintURL: '',
|
imprintURL: '',
|
||||||
themes: [
|
themes: [
|
||||||
{
|
{
|
||||||
label: 'Maxlan',
|
label: 'Open-redirect',
|
||||||
name: 'maxlan',
|
name: 'open-redirect',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
loadingScreen: {
|
loadingScreen: {
|
||||||
@ -51,8 +51,8 @@ export class ConfigService {
|
|||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
if (this.settings.themes.length === 0) {
|
if (this.settings.themes.length === 0) {
|
||||||
this.settings.themes.push({
|
this.settings.themes.push({
|
||||||
label: 'Maxlan',
|
label: 'Open-redirect',
|
||||||
name: 'maxlan',
|
name: 'open-redirect',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
|
@ -29,6 +29,10 @@ export class GuiService {
|
|||||||
this.sidebarService.hide();
|
this.sidebarService.hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.theme$.subscribe(theme => {
|
||||||
|
console.warn('theme changed', theme);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
|
Loading…
Reference in New Issue
Block a user