Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for translated models #113

Merged
merged 18 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
761db94
[tr] Add translation support to GPKG db connector and deal with trans…
gacarrillor Oct 24, 2024
2c1c773
Bump ili2db to 5.2.0
gacarrillor Oct 25, 2024
4c6d789
Set preferred language in generator; get available languages in gpkg …
gacarrillor Oct 25, 2024
82fe2c8
Lazy load of tables info in GPKP connector (this avoid too early init…
gacarrillor Oct 25, 2024
2447bf4
Translate relationship names when translations are available
gacarrillor Oct 27, 2024
e5d030f
Make sure form tabs get a translated name when available
gacarrillor Oct 27, 2024
efa33e0
Pass --createNlsTab parameter to schema import operation (since ili2d…
gacarrillor Oct 27, 2024
db0c194
Ignore t_ili2db_nls table (it's used to get translated names, but not…
gacarrillor Oct 27, 2024
2b2dec8
Avoid bug when asking for ili2db command and tool is not yet downloaded
gacarrillor Oct 28, 2024
c24293b
[ux] Add support for translated models on PostgreSQL
gacarrillor Oct 28, 2024
d43368d
[ux] Add support for translated models on MSSQL
gacarrillor Oct 29, 2024
a9a3c29
Merge branch 'main' into translations_support
signedav Oct 31, 2024
d2ccc3f
[tests] Add tests for translated models for GPKG and PG
gacarrillor Nov 1, 2024
64d0a08
only consider new parameter on ili2db >3
signedav Nov 4, 2024
783c216
increase number of ignored layers in tests
signedav Nov 4, 2024
d71a930
consider improved alias names for tabs in the tests
signedav Nov 4, 2024
a0d4618
consider aliases in tabs
signedav Nov 4, 2024
429cefe
use old way for getting the field according the attributename
signedav Nov 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions modelbaker/dataobjects/layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,9 +327,9 @@ def post_generate(self, project: Project) -> None:
continue

if nm_relation:
tab = FormTab(nm_relation.referenced_layer.name)
tab = FormTab(nm_relation.referenced_layer.alias)
else:
tab = FormTab(relation.referencing_layer.name)
tab = FormTab(relation.referencing_layer.alias)

widget = FormRelationWidget(relation, nm_relation)
tab.addChild(widget)
Expand Down
26 changes: 24 additions & 2 deletions modelbaker/dataobjects/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def __init__(self) -> None:
self.child_domain_name = None
self.qgis_relation = None
self._id = None
self.translate_name = False

def dump(self) -> dict:
definition = dict()
Expand Down Expand Up @@ -57,10 +58,31 @@ def create(

relation.setId(self._id)
relation.setName(self.name)
relation.setReferencingLayer(self.referencing_layer.create().id())
relation.setReferencedLayer(self.referenced_layer.create().id())
referencing_qgis_layer = self.referencing_layer.create()
referenced_qgis_layer = self.referenced_layer.create()
relation.setReferencingLayer(referencing_qgis_layer.id())
relation.setReferencedLayer(referenced_qgis_layer.id())
relation.addFieldPair(self.referencing_field, self.referenced_field)
relation.setStrength(self.strength)

if self.translate_name:
# Grab translated table and field names from QGIS objects
index = referencing_qgis_layer.fields().indexOf(self.referencing_field)
referencing_field_alias = referencing_qgis_layer.fields().at(index).alias()
index = referenced_qgis_layer.fields().indexOf(self.referenced_field)
referenced_field_alias = referenced_qgis_layer.fields().at(index).alias()
tr_name = "{}_({})_{}_({})".format(
referencing_qgis_layer.name(),
referencing_field_alias
if referencing_field_alias
else self.referencing_field,
referenced_qgis_layer.name(),
referenced_field_alias
if referenced_field_alias
else self.referenced_field,
)
relation.setName(tr_name)

self.qgis_relation = relation
return relation

Expand Down
2 changes: 2 additions & 0 deletions modelbaker/dbconnector/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
"T_ILI2DB_BASKET",
"t_ili2db_dataset",
"T_ILI2DB_DATASET",
"t_ili2db_nls",
"T_ILI2DB_NLS",
]

BASKET_TABLES = [
Expand Down
31 changes: 30 additions & 1 deletion modelbaker/dbconnector/db_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
* *
***************************************************************************/
"""

from qgis.PyQt.QtCore import QObject, pyqtSignal

from .config import BASKET_TABLES, IGNORED_ILI_ELEMENTS, IGNORED_SCHEMAS, IGNORED_TABLES
Expand All @@ -38,6 +37,7 @@ def __init__(self, uri, schema, parent=None):
self.dispName = "" # For BAG OF config, specific for each DB
self.basket_table_name = "" # For basket handling, specific for each DB
self.dataset_table_name = "" # For basket handling, specific for each DB
self._lang = "" # Preferred tr language for table/column info (2 characters)

def map_data_types(self, data_type):
"""Map provider date/time types to QGIS date/time types"""
Expand Down Expand Up @@ -404,6 +404,35 @@ def set_ili2db_sequence_value(self, value):
"""
return False, None

def set_preferred_translation(self, lang: str) -> bool:
"""
Returns whether the preferred translation language was successfully set.

Note: By convention, a value of __ means the preferred language will be
the original (non-translated) model language.
"""
if len(lang) == 2 and lang != "__":
self._lang = lang
return True

return False

def get_translation_handling(self) -> tuple[bool, str]:
"""
Whether there is translation support for this DB.

:return: Tuple containing:
- Whether the t_ili2db_nls is present and the DB connector has a preferred language set.
- The preferred language set.
"""
return False, ""

def get_available_languages(self) -> list[str]:
"""
Returns a list of available languages in the t_ili2db_nls table.
"""
return []


class DBConnectorError(Exception):
"""This error is raised when DbConnector could not connect to database.
Expand Down
74 changes: 68 additions & 6 deletions modelbaker/dbconnector/gpkg_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
GPKG_SETTINGS_TABLE = "T_ILI2DB_SETTINGS"
GPKG_DATASET_TABLE = "T_ILI2DB_DATASET"
GPKG_BASKET_TABLE = "T_ILI2DB_BASKET"
GPKG_NLS_TABLE = "T_ILI2DB_NLS"


class GPKGConnector(DBConnector):
Expand All @@ -49,7 +50,7 @@ def __init__(self, uri, schema):
self.conn.row_factory = sqlite3.Row
self.uri = uri
self._bMetadataTable = self._metadata_exists()
self._tables_info = self._get_tables_info()
self._tables_info = []
self.iliCodeName = "iliCode"
self.tid = "T_Id"
self.tilitid = "T_Ili_Tid"
Expand Down Expand Up @@ -97,6 +98,8 @@ def _table_exists(self, tablename):
return result

def get_tables_info(self):
if not self._tables_info:
self._tables_info = self._get_tables_info()
return self._tables_info

def _get_tables_info(self):
Expand All @@ -105,6 +108,12 @@ def _get_tables_info(self):
interlis_joins = ""

if self.metadata_exists():
tr_enabled, lang = self.get_translation_handling()
if tr_enabled:
self.stdout.emit(
f"Getting tables info with preferred language '{lang}'."
)

interlis_fields = """p.setting AS kind_settings,
alias.setting AS table_alias,
c.iliname AS ili_name,
Expand Down Expand Up @@ -139,7 +148,9 @@ def _get_tables_info(self):
substr(c.iliname, 0, instr(c.iliname, '.')) AS model,
attrs.sqlname as attribute_name,
{relevance_field},
{topics},""".format(
{topics},
{translations} -- Optional. Trailing comma omitted on purpose.
""".format(
relevance_field="""CASE WHEN c.iliname IN (
-- used to get the class names from the full names
WITH names AS (
Expand Down Expand Up @@ -208,6 +219,7 @@ def _get_tables_info(self):
)
SELECT childTopic, baseTopic, is_a_base FROM children)"""
),
translations="""nls.label AS table_tr,""" if tr_enabled else "",
)
interlis_joins = """LEFT JOIN T_ILI2DB_TABLE_PROP p
ON p.tablename = s.name
Expand All @@ -218,7 +230,15 @@ def _get_tables_info(self):
LEFT JOIN T_ILI2DB_CLASSNAME c
ON s.name == c.sqlname
LEFT JOIN T_ILI2DB_ATTRNAME attrs
ON c.iliname = attrs.iliname """
ON c.iliname = attrs.iliname
{translations}""".format(
translations=f"""LEFT JOIN T_ILI2DB_NLS nls
ON c.iliname = nls.ilielement
AND nls.lang = '{lang}'
"""
if tr_enabled
else ""
)
try:
cursor.execute(
"""
Expand Down Expand Up @@ -331,6 +351,8 @@ def get_fields_info(self, table_name):
columns_prop = list()
columns_full_name = list()
meta_attrs = list()
columns_tr = list()
tr_enabled, lang = self.get_translation_handling()

if self.metadata_exists():
cursor.execute(
Expand All @@ -342,7 +364,6 @@ def get_fields_info(self, table_name):
)
columns_prop = cursor.fetchall()

if self.metadata_exists():
if self.ili_version() == 3:
cursor.execute(
"""
Expand All @@ -361,9 +382,20 @@ def get_fields_info(self, table_name):
)
columns_full_name = cursor.fetchall()

if self.metadata_exists() and self._table_exists(GPKG_METAATTRS_TABLE):
meta_attrs = self.get_meta_attrs_info()
if self._table_exists(GPKG_METAATTRS_TABLE):
meta_attrs = self.get_meta_attrs_info()

if tr_enabled:
cursor.execute(
"""
SELECT ilielement, label
FROM T_ILI2DB_NLS
WHERE lang = ?;""",
(lang,),
)
columns_tr = cursor.fetchall()

# Build result dict from query results
complete_records = list()
for column_info in columns_info:
record = {}
Expand All @@ -385,6 +417,11 @@ def get_fields_info(self, table_name):
for column_full_name in columns_full_name:
if column_full_name["sqlname"] == column_info["name"]:
record["fully_qualified_name"] = column_full_name["iliname"]

for column_tr in columns_tr:
if column_full_name["iliname"] == column_tr["ilielement"]:
record["column_tr"] = column_tr["label"]
break
break

for column_prop in columns_prop:
Expand Down Expand Up @@ -486,6 +523,8 @@ def get_relations_info(self, filter_layer_list=[]):

cursor = self.conn.cursor()
complete_records = list()
tr_enabled, lang = self.get_translation_handling()

for table_info_name, table_info in tables_info_dict.items():
cursor.execute("""PRAGMA foreign_key_list("{}");""".format(table_info_name))
foreign_keys = cursor.fetchall()
Expand All @@ -504,6 +543,9 @@ def get_relations_info(self, filter_layer_list=[]):
record["referenced_table"],
record["referenced_column"],
)
if tr_enabled:
record["tr_enabled"] = True

if self._table_exists(GPKG_METAATTRS_TABLE):
# Get strength
cursor.execute(
Expand Down Expand Up @@ -1189,3 +1231,23 @@ def set_ili2db_sequence_value(self, value):
)

return False, self.tr("Could not reset T_LastUniqueId")

def get_translation_handling(self) -> tuple[bool, str]:
return self._table_exists(GPKG_NLS_TABLE) and self._lang != "", self._lang

def get_available_languages(self):
if not self._table_exists(GPKG_NLS_TABLE):
return []

cursor = self.conn.cursor()
cursor.execute(
"""SELECT DISTINCT
lang
FROM "{}";
""".format(
GPKG_NLS_TABLE
)
)
records = cursor.fetchall()
cursor.close()
return [record["lang"] for record in records]
37 changes: 37 additions & 0 deletions modelbaker/dbconnector/mssql_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
SETTINGS_TABLE = "t_ili2db_settings"
DATASET_TABLE = "T_ILI2DB_DATASET"
BASKET_TABLE = "T_ILI2DB_BASKET"
NLS_TABLE = "t_ili2db_nls"


class MssqlConnector(DBConnector):
Expand Down Expand Up @@ -114,6 +115,7 @@ def get_tables_info(self):

if self.schema:
metadata_exists = self.metadata_exists()
tr_enabled, lang = self.get_translation_handling()

ln = "\n"
stmt = ""
Expand Down Expand Up @@ -231,6 +233,8 @@ def get_tables_info(self):
+ """ ,substring( c.iliname, 1, CHARINDEX('.', substring( c.iliname, CHARINDEX('.', c.iliname)+1, len(c.iliname)))+CHARINDEX('.', c.iliname)-1) as base_topic
"""
)
if tr_enabled:
stmt += ln + " , nls.label AS table_tr"
stmt += ln + "FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS Tab"
stmt += ln + "INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS Col"
stmt += ln + " ON Col.Constraint_Name = Tab.Constraint_Name"
Expand Down Expand Up @@ -264,6 +268,10 @@ def get_tables_info(self):
stmt += ln + " ON tbls.TABLE_NAME = tgeomtype.tablename"
stmt += ln + " AND clm.COLUMN_NAME = tgeomtype.columnname"
stmt += ln + " AND tgeomtype.tag= 'ch.ehi.ili2db.geomType'"
if tr_enabled:
stmt += ln + "LEFT JOIN {schema}.t_ili2db_nls nls"
stmt += ln + " ON c.iliname = nls.ilielement"
stmt += ln + " AND nls.lang = '{lang}'".format(lang=lang)
stmt += (
ln
+ "WHERE tbls.TABLE_TYPE = 'BASE TABLE' AND tbls.TABLE_SCHEMA = '{schema}'"
Expand Down Expand Up @@ -481,6 +489,7 @@ def get_fields_info(self, table_name):
if self.schema:
metadata_exists = self.metadata_exists()
metaattrs_exists = self._table_exists(METAATTRS_TABLE)
tr_enabled, lang = self.get_translation_handling()
ln = "\n"
stmt = ""

Expand All @@ -506,6 +515,8 @@ def get_fields_info(self, table_name):
+ " , attr_mapping.attr_value AS attr_mapping"
)
stmt += ln + " , null AS comment"
if tr_enabled:
stmt += ln + " , nls.label AS column_tr"
stmt += ln + "FROM INFORMATION_SCHEMA.COLUMNS AS c"
if metadata_exists:
stmt += ln + "LEFT JOIN {schema}.t_ili2db_column_prop unit"
Expand Down Expand Up @@ -545,6 +556,10 @@ def get_fields_info(self, table_name):
stmt += ln + "LEFT JOIN {schema}.t_ili2db_meta_attrs attr_mapping"
stmt += ln + " ON full_name.iliname=attr_mapping.ilielement AND"
stmt += ln + " attr_mapping.attr_name='ili2db.mapping'"
if tr_enabled:
stmt += ln + "LEFT JOIN {schema}.t_ili2db_nls nls"
stmt += ln + " ON full_name.iliname = nls.ilielement"
stmt += ln + " AND nls.lang = '{lang}'".format(lang=lang)
stmt += ln + "WHERE TABLE_NAME = '{table}' AND TABLE_SCHEMA = '{schema}'"
if metadata_exists and metaattrs_exists:
stmt += ln + "ORDER BY attr_order;"
Expand Down Expand Up @@ -625,6 +640,9 @@ def get_relations_info(self, filter_layer_list=[]):

if self.schema:
cur = self.conn.cursor()
translate = (
", 1 AS tr_enabled" if self.get_translation_handling()[0] else ""
)
schema_where1 = (
"AND KCU1.CONSTRAINT_SCHEMA = '{}'".format(self.schema)
if self.schema
Expand Down Expand Up @@ -681,6 +699,7 @@ def get_relations_info(self, filter_layer_list=[]):
,KCU1.ORDINAL_POSITION AS ordinal_position
{strength_field}
{cardinality_max_field}
{translate}
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS RC

INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU1
Expand All @@ -706,6 +725,7 @@ def get_relations_info(self, filter_layer_list=[]):
strength_join=strength_join,
cardinality_max_field=cardinality_max_field,
cardinality_max_join=cardinality_max_join,
translate=translate,
)
cur.execute(query)
result = self._get_dict_result(cur)
Expand Down Expand Up @@ -1222,3 +1242,20 @@ def set_ili2db_sequence_value(self, value):
)

return False, self.tr("Could not reset sequence")

def get_translation_handling(self) -> tuple[bool, str]:
return self._table_exists(NLS_TABLE) and self._lang != "", self._lang

def get_available_languages(self):
if self.schema and self._table_exists(NLS_TABLE):
cur = self.conn.cursor()
cur.execute(
"""
SELECT DISTINCT
lang
FROM {schema}.t_ili2db_nls
"""
).format(schema=self.schema)

return [row.lang for row in cur.fetchall()]
return []
Loading
Loading