xmas-app 0.13.1__tar.gz → 0.15.0__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.
Files changed (33) hide show
  1. {xmas_app-0.13.1 → xmas_app-0.15.0}/PKG-INFO +4 -3
  2. {xmas_app-0.13.1 → xmas_app-0.15.0}/README.md +2 -1
  3. {xmas_app-0.13.1 → xmas_app-0.15.0}/pyproject.toml +2 -5
  4. {xmas_app-0.13.1 → xmas_app-0.15.0}/xmas_app/components/association.py +4 -3
  5. {xmas_app-0.13.1 → xmas_app-0.15.0}/xmas_app/form.py +89 -49
  6. xmas_app-0.15.0/xmas_app/main.py +711 -0
  7. {xmas_app-0.13.1 → xmas_app-0.15.0}/xmas_app/models/mappings.py +1 -0
  8. xmas_app-0.15.0/xmas_app/pages/__init__.py +3 -0
  9. xmas_app-0.15.0/xmas_app/pages/base.py +29 -0
  10. xmas_app-0.15.0/xmas_app/pages/planmanager.py +432 -0
  11. xmas_app-0.15.0/xmas_app/services/__init__.py +0 -0
  12. xmas_app-0.13.1/xmas_app/split_service.py → xmas_app-0.15.0/xmas_app/services/split.py +3 -4
  13. {xmas_app-0.13.1 → xmas_app-0.15.0}/xmas_app/settings.py +17 -36
  14. xmas_app-0.15.0/xmas_app/util/__init__.py +3 -0
  15. xmas_app-0.15.0/xmas_app/util/db.py +232 -0
  16. xmas_app-0.15.0/xmas_app/util/get_models.py +67 -0
  17. xmas_app-0.13.1/xmas_app/db.py +0 -320
  18. xmas_app-0.13.1/xmas_app/main.py +0 -1320
  19. {xmas_app-0.13.1 → xmas_app-0.15.0}/LICENSE +0 -0
  20. {xmas_app-0.13.1 → xmas_app-0.15.0}/xmas_app/components/__init__.py +0 -0
  21. {xmas_app-0.13.1 → xmas_app-0.15.0}/xmas_app/components/buttons.py +0 -0
  22. {xmas_app-0.13.1 → xmas_app-0.15.0}/xmas_app/components/label.py +0 -0
  23. {xmas_app-0.13.1 → xmas_app-0.15.0}/xmas_app/deps/version_guard.py +0 -0
  24. {xmas_app-0.13.1/xmas_app/util → xmas_app-0.15.0/xmas_app/models}/__init__.py +0 -0
  25. {xmas_app-0.13.1 → xmas_app-0.15.0}/xmas_app/models/crud.py +0 -0
  26. /xmas_app-0.13.1/xmas_app/schema.py → /xmas_app-0.15.0/xmas_app/models/split.py +0 -0
  27. {xmas_app-0.13.1 → xmas_app-0.15.0}/xmas_app/pygeoapi/config.yaml +0 -0
  28. {xmas_app-0.13.1 → xmas_app-0.15.0}/xmas_app/pygeoapi/openapi.yaml +0 -0
  29. {xmas_app-0.13.1 → xmas_app-0.15.0}/xmas_app/pygeoapi/provider.py +0 -0
  30. {xmas_app-0.13.1 → xmas_app-0.15.0}/xmas_app/resources/mappings.toml +0 -0
  31. {xmas_app-0.13.1 → xmas_app-0.15.0}/xmas_app/services/crud.py +0 -0
  32. {xmas_app-0.13.1 → xmas_app-0.15.0}/xmas_app/util/codelist.py +0 -0
  33. {xmas_app-0.13.1/xmas_app → xmas_app-0.15.0/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.13.1
3
+ Version: 0.15.0
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.11.1,<1.12.0)
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 no database schema was previously created with `xplan-tools`, it will be generated on initialization.
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 no database schema was previously created with `xplan-tools`, it will be generated on initialization.
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.13.1"
3
+ version = "0.15.0"
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,11 +16,8 @@ license-files = ["LICENSE"]
16
16
  dependencies = [
17
17
  "pydantic-settings>=2.0,<3.0",
18
18
  "pydantic-extra-types[semver]",
19
- "xplan-tools~=1.11.1",
19
+ "xplan-tools~=1.13.1",
20
20
  "nicegui>=2.20,<3.0",
21
- # "uvicorn[standard]>=0.29,<0.31"
22
- # "pygeoapi @ git+https://github.com/geopython/pygeoapi.git",
23
- # if git source is required, git must be installed in OSGeo4W Shell for the complete plugin installation
24
21
  ]
25
22
  requires-python = ">=3.11,<3.14"
26
23
 
@@ -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": "letzte Aktualisierung",
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 as e:
235
- message = f"Fehlerhafte Eingabe: {', '.join(set([str(error['loc'][0]) for error in e.errors()]))}"
236
- if getattr(self, "current_validation_error", None) != message:
237
- self.current_validation_error = message
238
- ui.notify(
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
- ui.button(
507
- icon="delete",
508
- on_click=partial(delete_form, list_item, item),
509
- )
510
- .props("flat round")
511
- .bind_enabled_from(
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 x is not None
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
@@ -1112,6 +1116,11 @@ class ModelForm:
1112
1116
  )
1113
1117
  .classes("w-full q-pa-none q-gutter-none")
1114
1118
  .props("square filled dense")
1119
+ .bind_value(
1120
+ bind_obj,
1121
+ key,
1122
+ backward=lambda x: None if not x else x,
1123
+ )
1115
1124
  )
1116
1125
  targets = await run.io_bound(
1117
1126
  self._get_association_targets,
@@ -1126,17 +1135,13 @@ class ModelForm:
1126
1135
  )
1127
1136
  else:
1128
1137
  input.set_options(targets)
1129
- if parent_id := app.storage.client.get(
1130
- "parent_id", None
1131
- ):
1132
- input.set_value(parent_id)
1133
- elif len((keys := list(targets.keys()))) == 1:
1134
- input.set_value(keys[0])
1135
- input.bind_value(
1136
- bind_obj,
1137
- key,
1138
- backward=lambda x: None if not x else x,
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
- f"{prop_info['typename']} [{prop_info['uom']}]",
1170
- suffix=prop_info["uom"],
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")
@@ -1222,3 +1261,4 @@ class ModelForm:
1222
1261
  feature[model_geom] = geom
1223
1262
  self.feature = self._get_bindable_object(self.model)(**feature)
1224
1263
  await self._build_model_form(self.model, self.feature, main_form, preview)
1264
+ self._propagate_data()