xmas-app 0.11.3__tar.gz → 0.12.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.
- {xmas_app-0.11.3 → xmas_app-0.12.0}/PKG-INFO +2 -2
- {xmas_app-0.11.3 → xmas_app-0.12.0}/pyproject.toml +2 -2
- {xmas_app-0.11.3 → xmas_app-0.12.0}/xmas_app/db.py +39 -0
- {xmas_app-0.11.3 → xmas_app-0.12.0}/xmas_app/form.py +16 -3
- {xmas_app-0.11.3 → xmas_app-0.12.0}/xmas_app/main.py +147 -46
- {xmas_app-0.11.3 → xmas_app-0.12.0}/xmas_app/settings.py +2 -4
- {xmas_app-0.11.3 → xmas_app-0.12.0}/LICENSE +0 -0
- {xmas_app-0.11.3 → xmas_app-0.12.0}/README.md +0 -0
- {xmas_app-0.11.3 → xmas_app-0.12.0}/xmas_app/db_uow.py +0 -0
- {xmas_app-0.11.3 → xmas_app-0.12.0}/xmas_app/deps/version_guard.py +0 -0
- {xmas_app-0.11.3 → xmas_app-0.12.0}/xmas_app/models/crud.py +0 -0
- {xmas_app-0.11.3 → xmas_app-0.12.0}/xmas_app/pygeoapi/config.yaml +0 -0
- {xmas_app-0.11.3 → xmas_app-0.12.0}/xmas_app/pygeoapi/openapi.yaml +0 -0
- {xmas_app-0.11.3 → xmas_app-0.12.0}/xmas_app/pygeoapi/provider.py +0 -0
- {xmas_app-0.11.3 → xmas_app-0.12.0}/xmas_app/schema.py +0 -0
- {xmas_app-0.11.3 → xmas_app-0.12.0}/xmas_app/services/crud.py +0 -0
- {xmas_app-0.11.3 → xmas_app-0.12.0}/xmas_app/split_service.py +0 -0
- {xmas_app-0.11.3 → xmas_app-0.12.0}/xmas_app/util/__init__.py +0 -0
- {xmas_app-0.11.3 → xmas_app-0.12.0}/xmas_app/util/codelist.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xmas-app
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.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.
|
|
18
|
+
Requires-Dist: xplan-tools (>=1.11.0,<1.12.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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "xmas-app"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.12.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,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
|
|
19
|
+
"xplan-tools~=1.11.0",
|
|
20
20
|
"nicegui>=2.20,<3.0",
|
|
21
21
|
# "uvicorn[standard]>=0.29,<0.31"
|
|
22
22
|
# "pygeoapi @ git+https://github.com/geopython/pygeoapi.git",
|
|
@@ -73,6 +73,45 @@ def get_db_feature_ids(
|
|
|
73
73
|
return results
|
|
74
74
|
|
|
75
75
|
|
|
76
|
+
def get_db_plans() -> list[dict]:
|
|
77
|
+
"""Get plan data from the database.
|
|
78
|
+
|
|
79
|
+
Query the database for plan objects.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
list[dict]: List of dicts containing plan data, i.e. name, id, appschema, version, last update.
|
|
83
|
+
"""
|
|
84
|
+
results = []
|
|
85
|
+
try:
|
|
86
|
+
with settings.repo.Session() as session:
|
|
87
|
+
stmt = select(Feature).where(Feature.featuretype.regexp_match("^.*Plan$"))
|
|
88
|
+
try:
|
|
89
|
+
db_result = session.execute(stmt)
|
|
90
|
+
features = db_result.unique().scalars().all()
|
|
91
|
+
logger.info(f"Found {len(features)} feature(s) in DB.")
|
|
92
|
+
except Exception as db_ex:
|
|
93
|
+
logger.error(f"Database query failed: {db_ex}", exc_info=True)
|
|
94
|
+
for feature in features:
|
|
95
|
+
plan_data = {
|
|
96
|
+
"id": str(feature.id),
|
|
97
|
+
"label": feature.properties.get("name", "<Name unbekannt>"),
|
|
98
|
+
"appschema": {
|
|
99
|
+
"name": feature.appschema.upper(),
|
|
100
|
+
"version": feature.version,
|
|
101
|
+
},
|
|
102
|
+
"updated": {
|
|
103
|
+
"date": f"{feature.updated_at.strftime('%m.%d.%Y')}",
|
|
104
|
+
"time": f"{feature.updated_at.strftime('%H:%M:%S Uhr')}",
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
results.append(plan_data)
|
|
108
|
+
|
|
109
|
+
except Exception as ex:
|
|
110
|
+
logger.error(f"Failed to get plan data: {ex}", exc_info=True)
|
|
111
|
+
raise
|
|
112
|
+
return results
|
|
113
|
+
|
|
114
|
+
|
|
76
115
|
def delete_db_association(base_id: str, related_id: str, repo: DBRepository):
|
|
77
116
|
try:
|
|
78
117
|
with repo.Session() as session:
|
|
@@ -627,7 +627,7 @@ class ModelForm:
|
|
|
627
627
|
class_info.move(self.header if not preview else self.parent, 0)
|
|
628
628
|
with ui.grid(columns="310px auto"):
|
|
629
629
|
for key, field_info in model.model_fields.items():
|
|
630
|
-
if key ==
|
|
630
|
+
if key == model.get_geom_field():
|
|
631
631
|
continue
|
|
632
632
|
prop_info = model.get_property_info(key)
|
|
633
633
|
if preview and (
|
|
@@ -661,7 +661,11 @@ class ModelForm:
|
|
|
661
661
|
with ui.expansion(key).props(
|
|
662
662
|
"expand-icon=o_info dense dense-toggle header-class=text-bold"
|
|
663
663
|
):
|
|
664
|
-
ui.label(
|
|
664
|
+
ui.label(
|
|
665
|
+
"Identifikator des Features."
|
|
666
|
+
if key == "id"
|
|
667
|
+
else field_info.description or ""
|
|
668
|
+
)
|
|
665
669
|
with ui.row() as property_row:
|
|
666
670
|
if main_form and not preview:
|
|
667
671
|
property_row.bind_visibility_from(
|
|
@@ -679,6 +683,16 @@ class ModelForm:
|
|
|
679
683
|
if prop_info["nullable"]:
|
|
680
684
|
marker.append("nullable")
|
|
681
685
|
property_row.mark(" ".join(marker))
|
|
686
|
+
if key == "id":
|
|
687
|
+
input = (
|
|
688
|
+
ui.label()
|
|
689
|
+
.bind_text_from(
|
|
690
|
+
bind_obj,
|
|
691
|
+
key,
|
|
692
|
+
)
|
|
693
|
+
.classes("w-full")
|
|
694
|
+
)
|
|
695
|
+
continue
|
|
682
696
|
if key == "stylesheetId":
|
|
683
697
|
input = (
|
|
684
698
|
ui.label()
|
|
@@ -691,7 +705,6 @@ class ModelForm:
|
|
|
691
705
|
)
|
|
692
706
|
.classes("w-full")
|
|
693
707
|
)
|
|
694
|
-
# input.disable()
|
|
695
708
|
continue
|
|
696
709
|
match prop_info["stereotype"]:
|
|
697
710
|
case "BasicType":
|
|
@@ -29,7 +29,7 @@ from xplan_tools.util import (
|
|
|
29
29
|
get_geometry_type_from_wkt,
|
|
30
30
|
)
|
|
31
31
|
|
|
32
|
-
from xmas_app.db import get_db_feature_ids, get_nodes
|
|
32
|
+
from xmas_app.db import get_db_feature_ids, get_db_plans, get_nodes
|
|
33
33
|
from xmas_app.deps.version_guard import enforce_plugin_version
|
|
34
34
|
from xmas_app.form import ModelForm
|
|
35
35
|
from xmas_app.models.crud import InsertPayload, UpdatePayload
|
|
@@ -257,23 +257,23 @@ async def plan_tree(
|
|
|
257
257
|
on_click=lambda e, target=target: confirm_delete(target),
|
|
258
258
|
)
|
|
259
259
|
elif len(path) == 3:
|
|
260
|
-
origin_featuretype, origin_id,
|
|
260
|
+
origin_featuretype, origin_id, rel = path
|
|
261
261
|
with action_dialog, ui.card(align_items="stretch"):
|
|
262
262
|
ui.label(origin_featuretype + " " + origin_id[:8]).classes(
|
|
263
263
|
"text-bold"
|
|
264
264
|
)
|
|
265
|
-
if
|
|
266
|
-
ui.label(f"Keine Aktionen für Referenz {repr(
|
|
265
|
+
if rel == "bereich":
|
|
266
|
+
ui.label(f"Keine Aktionen für Referenz {repr(rel)} verfügbar")
|
|
267
267
|
else:
|
|
268
|
-
ui.label(f"Neues Feature für Referenz {repr(
|
|
268
|
+
ui.label(f"Neues Feature für Referenz {repr(rel)} erstellen")
|
|
269
269
|
feature = settings.repo.get(origin_id)
|
|
270
|
-
prop_info = feature.get_property_info(
|
|
270
|
+
prop_info = feature.get_property_info(rel)
|
|
271
271
|
typename = prop_info["typename"]
|
|
272
272
|
targets = typename if isinstance(typename, list) else [typename]
|
|
273
273
|
target_select = ui.select(
|
|
274
274
|
targets,
|
|
275
275
|
label="Featuretype auswählen",
|
|
276
|
-
value=
|
|
276
|
+
value=None,
|
|
277
277
|
with_input=True,
|
|
278
278
|
)
|
|
279
279
|
if len(targets) == 1:
|
|
@@ -281,17 +281,103 @@ async def plan_tree(
|
|
|
281
281
|
with target_select.add_slot("append"):
|
|
282
282
|
ui.badge(str(len(targets))).props("floating")
|
|
283
283
|
with target_select.add_slot("after"):
|
|
284
|
-
ui.
|
|
284
|
+
dropdown = ui.dropdown_button(
|
|
285
285
|
icon="play_circle",
|
|
286
|
-
|
|
287
|
-
"noch nicht implementiert"
|
|
288
|
-
),
|
|
286
|
+
split=True,
|
|
289
287
|
)
|
|
288
|
+
target_select.on_value_change(
|
|
289
|
+
lambda e,
|
|
290
|
+
origin_featuretype=origin_featuretype,
|
|
291
|
+
origin_id=origin_id,
|
|
292
|
+
rel=rel: prepare_add_feature(
|
|
293
|
+
dropdown,
|
|
294
|
+
origin_featuretype,
|
|
295
|
+
origin_id,
|
|
296
|
+
rel,
|
|
297
|
+
e.value,
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
if len(targets) == 1:
|
|
301
|
+
target_select.set_value(targets[0])
|
|
302
|
+
else:
|
|
303
|
+
dropdown.props("disable")
|
|
290
304
|
|
|
291
305
|
else:
|
|
292
306
|
return
|
|
293
307
|
action_dialog.open()
|
|
294
308
|
|
|
309
|
+
async def prepare_add_feature(
|
|
310
|
+
dropdown: ui.dropdown_button,
|
|
311
|
+
origin_featuretype: str,
|
|
312
|
+
origin_id: str,
|
|
313
|
+
rel: str,
|
|
314
|
+
target_featuretype: str,
|
|
315
|
+
):
|
|
316
|
+
def trigger_add_feature(geometry_type):
|
|
317
|
+
rel_inv = origin_feature.get_property_info(rel)["assoc_info"]["reverse"]
|
|
318
|
+
data = {
|
|
319
|
+
"origin_featuretype": origin_featuretype,
|
|
320
|
+
"origin_id": origin_id,
|
|
321
|
+
"rel": rel,
|
|
322
|
+
"rel_list": origin_feature.get_property_info(rel)["list"],
|
|
323
|
+
"rel_inv": rel_inv,
|
|
324
|
+
"rel_inv_list": target_model.get_property_info(rel_inv)["list"]
|
|
325
|
+
if rel_inv
|
|
326
|
+
else False,
|
|
327
|
+
"target_featuretype": target_featuretype,
|
|
328
|
+
"target_geometrytype": geometry_type,
|
|
329
|
+
}
|
|
330
|
+
print(data)
|
|
331
|
+
try:
|
|
332
|
+
logger.info("Sending add_feature to QWebChannel handler")
|
|
333
|
+
ui.run_javascript(f"""new QWebChannel(qt.webChannelTransport, function (channel) {{
|
|
334
|
+
channel.objects.handler.add_feature({data})
|
|
335
|
+
}});""")
|
|
336
|
+
except Exception as ex:
|
|
337
|
+
logger.error(f"Exception while add_feature called: {ex}", exc_info=True)
|
|
338
|
+
|
|
339
|
+
GEOMETRY_MAP = {
|
|
340
|
+
"Polygon": {"label": "Fläche", "icon": "o_space_dashboard"},
|
|
341
|
+
"Line": {"label": "Linie", "icon": "show_chart"},
|
|
342
|
+
"Point": {"label": "Punkt", "icon": "o_location_on"},
|
|
343
|
+
"Null": {"label": "Keine Geometrie", "icon": "o_text_snippet"},
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
logger.debug("add_feature called")
|
|
347
|
+
dropdown.clear()
|
|
348
|
+
dropdown.props(remove="disable")
|
|
349
|
+
origin_feature = settings.repo.get(origin_id)
|
|
350
|
+
target_model = model_factory(
|
|
351
|
+
target_featuretype,
|
|
352
|
+
origin_feature.get_version(),
|
|
353
|
+
origin_feature.get_appschema(),
|
|
354
|
+
)
|
|
355
|
+
geometry_types = []
|
|
356
|
+
if geom_types := target_model.get_geom_types():
|
|
357
|
+
for geom_type in geom_types:
|
|
358
|
+
type_name = geom_type.get_name().replace("Multi", "")
|
|
359
|
+
if type_name not in geometry_types:
|
|
360
|
+
geometry_types.append(type_name)
|
|
361
|
+
else:
|
|
362
|
+
geometry_types.append("Null")
|
|
363
|
+
if len(geometry_types) == 1:
|
|
364
|
+
dropdown.on_click(lambda: trigger_add_feature(geometry_types[0]))
|
|
365
|
+
dropdown.props("disable-dropdown")
|
|
366
|
+
else:
|
|
367
|
+
with dropdown:
|
|
368
|
+
dropdown.on_click(lambda e: e.sender.open())
|
|
369
|
+
dropdown.props(remove="disable-dropdown")
|
|
370
|
+
for geometry_type in geometry_types:
|
|
371
|
+
with ui.item(
|
|
372
|
+
on_click=lambda geometry_type=geometry_type: trigger_add_feature(
|
|
373
|
+
geometry_type
|
|
374
|
+
),
|
|
375
|
+
):
|
|
376
|
+
with ui.item_section().props("avatar"):
|
|
377
|
+
ui.icon(GEOMETRY_MAP[geometry_type]["icon"])
|
|
378
|
+
with ui.item_section():
|
|
379
|
+
ui.item_label(GEOMETRY_MAP[geometry_type]["label"])
|
|
380
|
+
|
|
295
381
|
async def highlight_feature(e: ClickEventArguments, target: str):
|
|
296
382
|
logger.debug("highlight_feature called")
|
|
297
383
|
try:
|
|
@@ -426,35 +512,31 @@ async def plans(
|
|
|
426
512
|
logger.info("Updating plan select options")
|
|
427
513
|
plan_select.props("loading")
|
|
428
514
|
try:
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
"name",
|
|
436
|
-
),
|
|
515
|
+
selected = plan_select.options.index(plan_select.value)
|
|
516
|
+
except ValueError:
|
|
517
|
+
selected = 0
|
|
518
|
+
try:
|
|
519
|
+
plans = await asyncio.wait_for(
|
|
520
|
+
run.io_bound(get_db_plans),
|
|
437
521
|
timeout=10,
|
|
438
522
|
)
|
|
439
|
-
if not
|
|
523
|
+
if not plans: # Handle empty result or None
|
|
440
524
|
logger.warning("No plans returned from the database.")
|
|
441
525
|
ui.notify(
|
|
442
526
|
"Keine Pläne gefunden (möglicherweise keine Verbindung zur Datenbank)!",
|
|
443
527
|
type="negative",
|
|
444
528
|
)
|
|
445
|
-
plan_select.set_options(
|
|
529
|
+
plan_select.set_options([])
|
|
446
530
|
else:
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
else:
|
|
451
|
-
logger.warning("No plans found or database unreachable.")
|
|
531
|
+
plan_select.set_options(plans)
|
|
532
|
+
plan_select.set_value(plans[selected])
|
|
533
|
+
|
|
452
534
|
except asyncio.TimeoutError:
|
|
453
535
|
logger.error("Timeout: DB connection took too long.")
|
|
454
536
|
ui.notify(
|
|
455
537
|
"Verbindung zur Datenbank dauert zu lange (Timeout)!", type="negative"
|
|
456
538
|
)
|
|
457
|
-
plan_select.set_options(
|
|
539
|
+
plan_select.set_options([])
|
|
458
540
|
except Exception as e:
|
|
459
541
|
print("Fehler beim Laden der Pläne:", e)
|
|
460
542
|
logger.error(f"Fehler beim Laden der Pläne: {e}")
|
|
@@ -465,7 +547,7 @@ async def plans(
|
|
|
465
547
|
ui.notify("Keine Verbindung zur Datenbank!", type="negative")
|
|
466
548
|
else:
|
|
467
549
|
ui.notify(f"Fehler beim Laden der Pläne: {e}", type="negative")
|
|
468
|
-
plan_select.set_options(
|
|
550
|
+
plan_select.set_options([])
|
|
469
551
|
finally:
|
|
470
552
|
plan_select.props(remove="loading")
|
|
471
553
|
|
|
@@ -476,10 +558,12 @@ async def plans(
|
|
|
476
558
|
)
|
|
477
559
|
try:
|
|
478
560
|
download_dropdown.props(add="loading")
|
|
479
|
-
plan = await run.io_bound(
|
|
561
|
+
plan = await run.io_bound(
|
|
562
|
+
settings.repo.get_plan_by_id, plan_select.value["id"]
|
|
563
|
+
)
|
|
480
564
|
|
|
481
|
-
appschema = plan.features[plan_select.value].
|
|
482
|
-
version = plan.features[plan_select.value].get_version()
|
|
565
|
+
appschema = plan.features[plan_select.value["id"]].get_appschema()
|
|
566
|
+
version = plan.features[plan_select.value["id"]].get_version()
|
|
483
567
|
logger.debug(
|
|
484
568
|
"Loaded plan metadata: version=%s schema=%s", version, appschema
|
|
485
569
|
)
|
|
@@ -490,17 +574,14 @@ async def plans(
|
|
|
490
574
|
) # TODO get data from in-memory sqlite db?
|
|
491
575
|
logger.debug("Using temporary sqlite file %s", temp_file.name)
|
|
492
576
|
uri = f"gpkg:///{temp_file.name}"
|
|
493
|
-
repo = DBRepository(uri,
|
|
494
|
-
repo.create_tables(srid=plan.srid)
|
|
577
|
+
repo = DBRepository(uri, srid=plan.srid)
|
|
495
578
|
repo.save_all(plan)
|
|
496
579
|
data = Path(temp_file.name).read_bytes()
|
|
497
580
|
Path(temp_file.name).unlink()
|
|
498
581
|
else:
|
|
499
582
|
buffer = io.BytesIO()
|
|
500
583
|
logger.debug("Serializing to %s via repo_factory", format)
|
|
501
|
-
repo_factory(buffer,
|
|
502
|
-
plan
|
|
503
|
-
)
|
|
584
|
+
repo_factory(buffer, repo_type=format).save_all(plan)
|
|
504
585
|
data = buffer.getvalue()
|
|
505
586
|
|
|
506
587
|
logger.info("Prepared download payload (%d bytes)", len(data))
|
|
@@ -533,22 +614,23 @@ async def plans(
|
|
|
533
614
|
|
|
534
615
|
e.sender.props(add="loading")
|
|
535
616
|
try:
|
|
536
|
-
await run.io_bound(settings.repo.delete_plan_by_id, plan_select.value)
|
|
617
|
+
await run.io_bound(settings.repo.delete_plan_by_id, plan_select.value["id"])
|
|
537
618
|
logging.info("Plan %s deleted successfully", plan_select.value)
|
|
538
619
|
|
|
539
620
|
options = plan_select.options
|
|
540
|
-
options.
|
|
621
|
+
options.remove(plan_select.value)
|
|
541
622
|
plan_select.set_options(options)
|
|
542
623
|
|
|
543
624
|
except Exception:
|
|
544
625
|
logging.exception("Failed to delete plan %s", plan_select.value)
|
|
545
|
-
ui.
|
|
626
|
+
ui.notify("Fehler beim Löschen des Plans.", type="negative")
|
|
546
627
|
|
|
547
628
|
finally:
|
|
548
629
|
e.sender.props(remove="loading")
|
|
549
630
|
|
|
550
631
|
async def handle_load_plan(e: ClickEventArguments):
|
|
551
|
-
"""
|
|
632
|
+
"""Load a plan in QGIS.
|
|
633
|
+
|
|
552
634
|
Handles loading a selected plan and sends the plan data to the QWebChannel handler.
|
|
553
635
|
Fetches the selected plan, retrieves its associated 'bereiche', and passes the structured
|
|
554
636
|
data to the web view. Logs key steps and errors, and provides user feedback on failure.
|
|
@@ -560,7 +642,7 @@ async def plans(
|
|
|
560
642
|
e.sender.props(add="loading")
|
|
561
643
|
try:
|
|
562
644
|
logger.info(f"Attempting to load plan with ID: {plan_select.value}")
|
|
563
|
-
plan = await run.io_bound(settings.repo.get, plan_select.value)
|
|
645
|
+
plan = await run.io_bound(settings.repo.get, plan_select.value["id"])
|
|
564
646
|
if plan is None:
|
|
565
647
|
logger.error(f"No plan found for ID: {plan_select.value}")
|
|
566
648
|
ui.notify("Fehler: Plan nicht gefunden", type="negative")
|
|
@@ -593,7 +675,7 @@ async def plans(
|
|
|
593
675
|
plan_data = {
|
|
594
676
|
"plan_id": plan.id,
|
|
595
677
|
"plan_name": plan.name,
|
|
596
|
-
"appschema": plan.
|
|
678
|
+
"appschema": plan.get_appschema(),
|
|
597
679
|
"version": plan.get_version(),
|
|
598
680
|
"plan_type": plan.get_name(),
|
|
599
681
|
"bereiche": bereiche,
|
|
@@ -617,7 +699,7 @@ async def plans(
|
|
|
617
699
|
async def handle_import_plan(e):
|
|
618
700
|
data = io.BytesIO(e.content.read())
|
|
619
701
|
try:
|
|
620
|
-
repo = GMLRepository(data
|
|
702
|
+
repo = GMLRepository(data)
|
|
621
703
|
except Exception as e:
|
|
622
704
|
print(e)
|
|
623
705
|
return ui.notify("Fehler beim Lesen der Datei", type="negative")
|
|
@@ -643,10 +725,29 @@ async def plans(
|
|
|
643
725
|
with ui.column(align_items="start").classes("w-full"):
|
|
644
726
|
ui.label("Vorhandene Pläne").classes("text-h6")
|
|
645
727
|
plan_select = (
|
|
646
|
-
ui.select(
|
|
647
|
-
.props("loading")
|
|
648
|
-
.classes("w-full")
|
|
728
|
+
ui.select([], label="Plan auswählen", with_input=True)
|
|
729
|
+
.props("loading :option-label='(opt) => opt.label.label'")
|
|
730
|
+
.classes("w-full dense")
|
|
731
|
+
)
|
|
732
|
+
plan_select.add_slot(
|
|
733
|
+
"option",
|
|
734
|
+
r"""
|
|
735
|
+
<q-item v-bind="props.itemProps">
|
|
736
|
+
<q-item-section>
|
|
737
|
+
<q-item-label>{{ props.opt.label.label }}</q-item-label>
|
|
738
|
+
<q-item-label caption>{{ props.opt.label.updated.date }} {{ props.opt.label.updated.time }}</q-item-label>
|
|
739
|
+
</q-item-section>
|
|
740
|
+
<q-item-section side top>
|
|
741
|
+
<q-item-label caption>{{ props.opt.label.appschema.name }}</q-item-label>
|
|
742
|
+
<q-item-label caption>v{{ props.opt.label.appschema.version }}</q-item-label>
|
|
743
|
+
</q-item-section>
|
|
744
|
+
</q-item>
|
|
745
|
+
""",
|
|
649
746
|
)
|
|
747
|
+
with plan_select.add_slot("append"):
|
|
748
|
+
ui.badge().bind_text_from(
|
|
749
|
+
plan_select, "options", backward=lambda x: len(x)
|
|
750
|
+
).props("floating")
|
|
650
751
|
with plan_select.add_slot("after"):
|
|
651
752
|
ui.button(icon="refresh", on_click=update_plan_select_options)
|
|
652
753
|
ui.button(
|
|
@@ -14,7 +14,7 @@ class AppSchema(TypedDict):
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
APPSCHEMAS: Dict[str, AppSchema] = {
|
|
17
|
-
"XPlanung": {"type": "xplan", "versions": ["5.4", "6.0"]},
|
|
17
|
+
"XPlanung": {"type": "xplan", "versions": ["5.4", "6.0", "6.1"]},
|
|
18
18
|
"XTrasse": {"type": "xtrasse", "versions": ["2.0"]},
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -55,7 +55,7 @@ class Settings(BaseSettings):
|
|
|
55
55
|
"https://registry.gdi-de.org/codelist/de.xleitstelle.xplanung"
|
|
56
56
|
)
|
|
57
57
|
qgis_plugin_name: str = "XMAS-Plugin"
|
|
58
|
-
qgis_plugin_min_version: _VersionPydanticAnnotation = Version(major=0, minor=
|
|
58
|
+
qgis_plugin_min_version: _VersionPydanticAnnotation = Version(major=0, minor=13)
|
|
59
59
|
|
|
60
60
|
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
|
|
61
61
|
|
|
@@ -76,8 +76,6 @@ class Settings(BaseSettings):
|
|
|
76
76
|
)
|
|
77
77
|
self.repo = DBRepository(
|
|
78
78
|
"postgresql://",
|
|
79
|
-
self.appschema_version,
|
|
80
|
-
self.appschema,
|
|
81
79
|
srid=self.srid,
|
|
82
80
|
with_views=True,
|
|
83
81
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|