xmas-app 0.14.0__tar.gz → 0.15.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {xmas_app-0.14.0 → xmas_app-0.15.1}/PKG-INFO +4 -3
- {xmas_app-0.14.0 → xmas_app-0.15.1}/README.md +2 -1
- {xmas_app-0.14.0 → xmas_app-0.15.1}/pyproject.toml +2 -2
- {xmas_app-0.14.0 → xmas_app-0.15.1}/xmas_app/components/association.py +4 -3
- {xmas_app-0.14.0 → xmas_app-0.15.1}/xmas_app/form.py +83 -44
- {xmas_app-0.14.0 → xmas_app-0.15.1}/xmas_app/main.py +36 -414
- {xmas_app-0.14.0 → xmas_app-0.15.1}/xmas_app/models/mappings.py +1 -0
- xmas_app-0.15.1/xmas_app/pages/__init__.py +3 -0
- xmas_app-0.15.1/xmas_app/pages/base.py +29 -0
- xmas_app-0.15.1/xmas_app/pages/planmanager.py +432 -0
- xmas_app-0.15.1/xmas_app/services/__init__.py +0 -0
- xmas_app-0.14.0/xmas_app/split_service.py → xmas_app-0.15.1/xmas_app/services/split.py +3 -4
- {xmas_app-0.14.0 → xmas_app-0.15.1}/xmas_app/settings.py +17 -36
- xmas_app-0.15.1/xmas_app/util/__init__.py +3 -0
- {xmas_app-0.14.0/xmas_app → xmas_app-0.15.1/xmas_app/util}/db.py +46 -20
- xmas_app-0.15.1/xmas_app/util/get_models.py +67 -0
- {xmas_app-0.14.0 → xmas_app-0.15.1}/LICENSE +0 -0
- {xmas_app-0.14.0 → xmas_app-0.15.1}/xmas_app/components/__init__.py +0 -0
- {xmas_app-0.14.0 → xmas_app-0.15.1}/xmas_app/components/buttons.py +0 -0
- {xmas_app-0.14.0 → xmas_app-0.15.1}/xmas_app/components/label.py +0 -0
- {xmas_app-0.14.0 → xmas_app-0.15.1}/xmas_app/deps/version_guard.py +0 -0
- {xmas_app-0.14.0/xmas_app/util → xmas_app-0.15.1/xmas_app/models}/__init__.py +0 -0
- {xmas_app-0.14.0 → xmas_app-0.15.1}/xmas_app/models/crud.py +0 -0
- /xmas_app-0.14.0/xmas_app/schema.py → /xmas_app-0.15.1/xmas_app/models/split.py +0 -0
- {xmas_app-0.14.0 → xmas_app-0.15.1}/xmas_app/pygeoapi/config.yaml +0 -0
- {xmas_app-0.14.0 → xmas_app-0.15.1}/xmas_app/pygeoapi/openapi.yaml +0 -0
- {xmas_app-0.14.0 → xmas_app-0.15.1}/xmas_app/pygeoapi/provider.py +0 -0
- {xmas_app-0.14.0 → xmas_app-0.15.1}/xmas_app/resources/mappings.toml +0 -0
- {xmas_app-0.14.0 → xmas_app-0.15.1}/xmas_app/services/crud.py +0 -0
- {xmas_app-0.14.0 → xmas_app-0.15.1}/xmas_app/util/codelist.py +0 -0
- {xmas_app-0.14.0/xmas_app → xmas_app-0.15.1/xmas_app/util}/db_uow.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xmas-app
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.1
|
|
4
4
|
Summary: The XLeitstelle model-driven application schema app.
|
|
5
5
|
License: EUPL-1.2-or-later
|
|
6
6
|
License-File: LICENSE
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
15
15
|
Requires-Dist: nicegui (>=2.20,<3.0)
|
|
16
16
|
Requires-Dist: pydantic-extra-types[semver]
|
|
17
17
|
Requires-Dist: pydantic-settings (>=2.0,<3.0)
|
|
18
|
-
Requires-Dist: xplan-tools (>=1.
|
|
18
|
+
Requires-Dist: xplan-tools (>=1.13.1,<1.14.0)
|
|
19
19
|
Project-URL: Homepage, https://gitlab.opencode.de/xleitstelle/xmas-app
|
|
20
20
|
Project-URL: Issues, https://gitlab.opencode.de/xleitstelle/xmas-app/-/issues
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
@@ -66,7 +66,7 @@ pixi install
|
|
|
66
66
|
## Running
|
|
67
67
|
|
|
68
68
|
### Preconditions
|
|
69
|
-
A Postgres DB with PostGIS extension and adequate permissions for the used role. If
|
|
69
|
+
A Postgres DB with PostGIS extension and adequate permissions for the used role. If required tables were not previously created with `xplan-tools`, they will be generated on initialization.
|
|
70
70
|
|
|
71
71
|
For convenience, you can run
|
|
72
72
|
|
|
@@ -97,6 +97,7 @@ The database connection parameters can be either user name, password etc. or a s
|
|
|
97
97
|
| APP_PORT | the port on which the app should run | x | 1337 |
|
|
98
98
|
| APPSCHEMA | the default appschema | x | xplan |
|
|
99
99
|
| APPSCHEMA_VERSION | the default appschema's version | x | 6.0 |
|
|
100
|
+
| XMAS_DB_SCHEMA | the database schema to use | | myschema |
|
|
100
101
|
|
|
101
102
|
First activate the pixi shell
|
|
102
103
|
|
|
@@ -44,7 +44,7 @@ pixi install
|
|
|
44
44
|
## Running
|
|
45
45
|
|
|
46
46
|
### Preconditions
|
|
47
|
-
A Postgres DB with PostGIS extension and adequate permissions for the used role. If
|
|
47
|
+
A Postgres DB with PostGIS extension and adequate permissions for the used role. If required tables were not previously created with `xplan-tools`, they will be generated on initialization.
|
|
48
48
|
|
|
49
49
|
For convenience, you can run
|
|
50
50
|
|
|
@@ -75,6 +75,7 @@ The database connection parameters can be either user name, password etc. or a s
|
|
|
75
75
|
| APP_PORT | the port on which the app should run | x | 1337 |
|
|
76
76
|
| APPSCHEMA | the default appschema | x | xplan |
|
|
77
77
|
| APPSCHEMA_VERSION | the default appschema's version | x | 6.0 |
|
|
78
|
+
| XMAS_DB_SCHEMA | the database schema to use | | myschema |
|
|
78
79
|
|
|
79
80
|
First activate the pixi shell
|
|
80
81
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "xmas-app"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.15.1"
|
|
4
4
|
description = "The XLeitstelle model-driven application schema app."
|
|
5
5
|
authors = [{ name = "Tobias Kraft", email = "tobias.kraft@gv.hamburg.de" }]
|
|
6
6
|
classifiers = [
|
|
@@ -16,7 +16,7 @@ license-files = ["LICENSE"]
|
|
|
16
16
|
dependencies = [
|
|
17
17
|
"pydantic-settings>=2.0,<3.0",
|
|
18
18
|
"pydantic-extra-types[semver]",
|
|
19
|
-
"xplan-tools~=1.
|
|
19
|
+
"xplan-tools~=1.13.1",
|
|
20
20
|
"nicegui>=2.20,<3.0",
|
|
21
21
|
]
|
|
22
22
|
requires-python = ">=3.11,<3.14"
|
|
@@ -3,7 +3,7 @@ import logging
|
|
|
3
3
|
from nicegui import app, binding, events, run, ui
|
|
4
4
|
|
|
5
5
|
from xmas_app.components.buttons import FeatureInteractionButton
|
|
6
|
-
from xmas_app.db import get_ref_candidates, get_ref_objects
|
|
6
|
+
from xmas_app.util.db import get_ref_candidates, get_ref_objects
|
|
7
7
|
|
|
8
8
|
logger = logging.getLogger("xmas_app")
|
|
9
9
|
|
|
@@ -174,7 +174,7 @@ class AssociationList(ui.button):
|
|
|
174
174
|
},
|
|
175
175
|
{
|
|
176
176
|
"name": "updated",
|
|
177
|
-
"label": "
|
|
177
|
+
"label": "Aktualisierung",
|
|
178
178
|
"field": "updated",
|
|
179
179
|
"align": "left",
|
|
180
180
|
"sortable": True,
|
|
@@ -203,6 +203,7 @@ class AssociationList(ui.button):
|
|
|
203
203
|
square
|
|
204
204
|
dense
|
|
205
205
|
virtual-scroll
|
|
206
|
+
bordered
|
|
206
207
|
"""
|
|
207
208
|
)
|
|
208
209
|
.classes("w-full h-full")
|
|
@@ -223,7 +224,7 @@ class AssociationList(ui.button):
|
|
|
223
224
|
ui.input("Filter")
|
|
224
225
|
.bind_value(table, "filter")
|
|
225
226
|
.tooltip("über alle Spalten nach Ausdruck filtern")
|
|
226
|
-
.props("clearable square dense")
|
|
227
|
+
.props("stack-label filled clearable square dense")
|
|
227
228
|
.classes("w-64")
|
|
228
229
|
)
|
|
229
230
|
with search.add_slot("prepend"):
|
|
@@ -137,6 +137,7 @@ class ModelForm:
|
|
|
137
137
|
mode="json",
|
|
138
138
|
exclude_unset=True,
|
|
139
139
|
exclude={"id", self.model.get_geom_field()},
|
|
140
|
+
context={"datatype": True},
|
|
140
141
|
),
|
|
141
142
|
"featuretype": self.model.get_name(),
|
|
142
143
|
"id": model_instance.id,
|
|
@@ -231,14 +232,16 @@ class ModelForm:
|
|
|
231
232
|
setattr(feature, geom_field, geom)
|
|
232
233
|
try:
|
|
233
234
|
return self.model.model_validate(feature)
|
|
234
|
-
except ValidationError
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
message,
|
|
235
|
+
except ValidationError:
|
|
236
|
+
if not getattr(self, "validation_error_notification", False):
|
|
237
|
+
self.validation_error_notification = True
|
|
238
|
+
ui.notification(
|
|
239
|
+
"Fehlende Pflichtattribute",
|
|
240
240
|
type="negative",
|
|
241
241
|
position="bottom",
|
|
242
|
+
on_dismiss=lambda: setattr(
|
|
243
|
+
self, "validation_error_notification", False
|
|
244
|
+
),
|
|
242
245
|
)
|
|
243
246
|
|
|
244
247
|
async def _set_stylesheetId_and_schriftinhalt(self) -> None:
|
|
@@ -502,24 +505,25 @@ class ModelForm:
|
|
|
502
505
|
)
|
|
503
506
|
if not preview:
|
|
504
507
|
with ui.item_section().props("side"):
|
|
505
|
-
delete_button = (
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
508
|
+
delete_button = ui.button(
|
|
509
|
+
icon="delete",
|
|
510
|
+
on_click=partial(delete_form, list_item, item),
|
|
511
|
+
).props("flat round")
|
|
512
|
+
if not self.editable or (
|
|
513
|
+
not prop_info["nullable"]
|
|
514
|
+
and prop_info["list"]
|
|
515
|
+
and index == 0
|
|
516
|
+
):
|
|
517
|
+
delete_button.disable()
|
|
518
|
+
else:
|
|
519
|
+
delete_button.bind_enabled_from(
|
|
512
520
|
bind_obj,
|
|
513
521
|
key,
|
|
514
522
|
backward=lambda x: len(x) > 1
|
|
515
523
|
if not prop_info["nullable"] and isinstance(x, list)
|
|
516
|
-
else
|
|
517
|
-
if prop_info["nullable"]
|
|
518
|
-
else x,
|
|
524
|
+
else prop_info["nullable"],
|
|
519
525
|
)
|
|
520
|
-
|
|
521
|
-
if not self.editable:
|
|
522
|
-
delete_button.disable()
|
|
526
|
+
|
|
523
527
|
return list_item
|
|
524
528
|
|
|
525
529
|
if not prop_info["nullable"] and not getattr(bind_obj, key, None):
|
|
@@ -548,12 +552,6 @@ class ModelForm:
|
|
|
548
552
|
sub_model.get_name(), sub_model.__doc__
|
|
549
553
|
).classes("text-bold")
|
|
550
554
|
ui.separator()
|
|
551
|
-
if value is not None:
|
|
552
|
-
if isinstance(value, list):
|
|
553
|
-
for index, item in enumerate(value):
|
|
554
|
-
await build_item(sub_model, item, index)
|
|
555
|
-
else:
|
|
556
|
-
await build_item(sub_model, value)
|
|
557
555
|
if not preview:
|
|
558
556
|
if prop_info["list"] or (prop_info["nullable"] and not value):
|
|
559
557
|
with ui.item().classes("justify-center"):
|
|
@@ -579,10 +577,16 @@ class ModelForm:
|
|
|
579
577
|
next(x.sender.descendants()).text
|
|
580
578
|
),
|
|
581
579
|
)
|
|
580
|
+
if value is not None:
|
|
581
|
+
if isinstance(value, list):
|
|
582
|
+
for index, item in enumerate(value):
|
|
583
|
+
await build_item(sub_model, item, index)
|
|
584
|
+
else:
|
|
585
|
+
await build_item(sub_model, value)
|
|
582
586
|
|
|
583
587
|
async def _build_model_form(
|
|
584
588
|
self,
|
|
585
|
-
model: BaseFeature,
|
|
589
|
+
model: type[BaseFeature],
|
|
586
590
|
bind_obj: type,
|
|
587
591
|
main_form: bool = False,
|
|
588
592
|
preview: bool = False,
|
|
@@ -632,7 +636,7 @@ class ModelForm:
|
|
|
632
636
|
)
|
|
633
637
|
self.radio_filter = radio_filter
|
|
634
638
|
class_info.move(self.header if not preview else self.parent, 0)
|
|
635
|
-
with ui.grid(columns="minmax(0, 1fr) minmax(0, 2fr)"):
|
|
639
|
+
with ui.grid(columns="minmax(0, 1fr) minmax(0, 2fr)").classes("gap-5"):
|
|
636
640
|
for key, field_info in model.model_fields.items():
|
|
637
641
|
if key == model.get_geom_field():
|
|
638
642
|
continue
|
|
@@ -1103,7 +1107,7 @@ class ModelForm:
|
|
|
1103
1107
|
else:
|
|
1104
1108
|
input = (
|
|
1105
1109
|
ui.select(
|
|
1106
|
-
|
|
1110
|
+
[getattr(bind_obj, key)],
|
|
1107
1111
|
multiple=prop_info["list"],
|
|
1108
1112
|
clearable=prop_info["nullable"],
|
|
1109
1113
|
validation=None
|
|
@@ -1131,12 +1135,13 @@ class ModelForm:
|
|
|
1131
1135
|
)
|
|
1132
1136
|
else:
|
|
1133
1137
|
input.set_options(targets)
|
|
1134
|
-
if
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1138
|
+
if not input.value: # only if no value was set
|
|
1139
|
+
if parent_id := app.storage.client.get(
|
|
1140
|
+
"parent_id", None
|
|
1141
|
+
):
|
|
1142
|
+
input.set_value(parent_id)
|
|
1143
|
+
elif len((keys := list(targets.keys()))) == 1:
|
|
1144
|
+
input.set_value(keys[0])
|
|
1140
1145
|
input.bind_enabled_from(
|
|
1141
1146
|
self,
|
|
1142
1147
|
"editable",
|
|
@@ -1164,24 +1169,21 @@ class ModelForm:
|
|
|
1164
1169
|
)
|
|
1165
1170
|
case "Measure":
|
|
1166
1171
|
uom = str(prop_info["uom"])
|
|
1172
|
+
initial_value = isinstance(
|
|
1173
|
+
getattr(bind_obj, key, None), dict
|
|
1174
|
+
)
|
|
1167
1175
|
input = (
|
|
1168
1176
|
ui.number(
|
|
1169
|
-
|
|
1170
|
-
|
|
1177
|
+
prop_info["typename"],
|
|
1178
|
+
value=getattr(bind_obj, key)["value"]
|
|
1179
|
+
if initial_value
|
|
1180
|
+
else None,
|
|
1171
1181
|
min=-360.0 if uom == "grad" else None,
|
|
1172
1182
|
max=360.0 if uom == "grad" else None,
|
|
1173
1183
|
validation=None
|
|
1174
1184
|
if prop_info["nullable"]
|
|
1175
1185
|
else {"Pflichtfeld": lambda x: x},
|
|
1176
1186
|
)
|
|
1177
|
-
.bind_value(
|
|
1178
|
-
bind_obj,
|
|
1179
|
-
key,
|
|
1180
|
-
forward=lambda x, uom=uom: None
|
|
1181
|
-
if not x
|
|
1182
|
-
else {"value": x, "uom": uom},
|
|
1183
|
-
backward=lambda x: None if not x else x["value"],
|
|
1184
|
-
)
|
|
1185
1187
|
.classes("w-full q-pa-none q-gutter-none")
|
|
1186
1188
|
.props("stack-label square filled dense")
|
|
1187
1189
|
)
|
|
@@ -1192,6 +1194,43 @@ class ModelForm:
|
|
|
1192
1194
|
**{"add" if not v else "remove": "readonly"}
|
|
1193
1195
|
),
|
|
1194
1196
|
)
|
|
1197
|
+
with input.add_slot("after"):
|
|
1198
|
+
uom_input = (
|
|
1199
|
+
ui.input(
|
|
1200
|
+
"uom",
|
|
1201
|
+
value=getattr(bind_obj, key)["uom"]
|
|
1202
|
+
if initial_value
|
|
1203
|
+
else uom,
|
|
1204
|
+
)
|
|
1205
|
+
.classes("w-24 q-pa-none q-gutter-none")
|
|
1206
|
+
.props("stack-label square filled dense")
|
|
1207
|
+
).tooltip(
|
|
1208
|
+
"Vorsicht beim Editieren - in den meisten Fällen ist die Maßeinheit vom Standard vorgegeben"
|
|
1209
|
+
)
|
|
1210
|
+
input.bind_value(
|
|
1211
|
+
bind_obj,
|
|
1212
|
+
key,
|
|
1213
|
+
forward=lambda x, uom_input=uom_input: None
|
|
1214
|
+
if not x
|
|
1215
|
+
else {"value": x, "uom": uom_input.value},
|
|
1216
|
+
backward=lambda x: None if not x else x["value"],
|
|
1217
|
+
)
|
|
1218
|
+
uom_input.bind_enabled_from(
|
|
1219
|
+
self,
|
|
1220
|
+
"editable",
|
|
1221
|
+
backward=lambda v, input=uom_input: input.props(
|
|
1222
|
+
**{"add" if not v else "remove": "readonly"}
|
|
1223
|
+
),
|
|
1224
|
+
).bind_value(
|
|
1225
|
+
bind_obj,
|
|
1226
|
+
key,
|
|
1227
|
+
forward=lambda x, input=input: None
|
|
1228
|
+
if not input.value
|
|
1229
|
+
else {"value": input.value, "uom": x},
|
|
1230
|
+
backward=lambda x, uom=uom: uom
|
|
1231
|
+
if not x
|
|
1232
|
+
else x["uom"],
|
|
1233
|
+
)
|
|
1195
1234
|
input.validate()
|
|
1196
1235
|
case _:
|
|
1197
1236
|
ui.label("TODO")
|