prepare recursive UI build

This commit is contained in:
2026-05-11 16:28:20 +02:00
parent c3462d3d3a
commit 2c072e2252
3 changed files with 620 additions and 72 deletions

120
pdm.lock generated
View File

@@ -5,7 +5,7 @@
groups = ["default", "dev", "lint", "nb", "tests"]
strategy = ["inherit_metadata"]
lock_version = "4.5.0"
content_hash = "sha256:eb4aaeb1bc7efec0c69be5e4cde53b2d92fdacfcc170db0204ef694828f9e99d"
content_hash = "sha256:09d959f153789f965fe2d45e1eb65842eb8d0b446b67cdec76891c6048192b0c"
[[metadata.targets]]
requires_python = ">=3.11,<3.14"
@@ -2684,24 +2684,24 @@ files = [
[[package]]
name = "pydantic"
version = "2.13.3"
version = "2.13.4"
requires_python = ">=3.9"
summary = "Data validation using Python type hints"
groups = ["default", "dev"]
dependencies = [
"annotated-types>=0.6.0",
"pydantic-core==2.46.3",
"pydantic-core==2.46.4",
"typing-extensions>=4.14.1",
"typing-inspection>=0.4.2",
]
files = [
{file = "pydantic-2.13.3-py3-none-any.whl", hash = "sha256:6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927"},
{file = "pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d"},
{file = "pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba"},
{file = "pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6"},
]
[[package]]
name = "pydantic-core"
version = "2.46.3"
version = "2.46.4"
requires_python = ">=3.9"
summary = "Core functionality for Pydantic validation and serialization"
groups = ["default", "dev"]
@@ -2709,60 +2709,60 @@ dependencies = [
"typing-extensions>=4.14.1",
]
files = [
{file = "pydantic_core-2.46.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ab124d49d0459b2373ecf54118a45c28a1e6d4192a533fbc915e70f556feb8e5"},
{file = "pydantic_core-2.46.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cca67d52a5c7a16aed2b3999e719c4bcf644074eac304a5d3d62dd70ae7d4b2c"},
{file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c024e08c0ba23e6fd68c771a521e9d6a792f2ebb0fa734296b36394dc30390e"},
{file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6645ce7eec4928e29a1e3b3d5c946621d105d3e79f0c9cddf07c2a9770949287"},
{file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a712c7118e6c5ea96562f7b488435172abb94a3c53c22c9efc1412264a45cbbe"},
{file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69a868ef3ff206343579021c40faf3b1edc64b1cc508ff243a28b0a514ccb050"},
{file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc7e8c32db809aa0f6ea1d6869ebc8518a65d5150fdfad8bcae6a49ae32a22e2"},
{file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:3481bd1341dc85779ee506bc8e1196a277ace359d89d28588a9468c3ecbe63fa"},
{file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8690eba565c6d68ffd3a8655525cbdd5246510b44a637ee2c6c03a7ebfe64d3c"},
{file = "pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4de88889d7e88d50d40ee5b39d5dac0bcaef9ba91f7e536ac064e6b2834ecccf"},
{file = "pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:e480080975c1ef7f780b8f99ed72337e7cc5efea2e518a20a692e8e7b278eb8b"},
{file = "pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de3a5c376f8cd94da9a1b8fd3dd1c16c7a7b216ed31dc8ce9fd7a22bf13b836e"},
{file = "pydantic_core-2.46.3-cp311-cp311-win32.whl", hash = "sha256:fc331a5314ffddd5385b9ee9d0d2fee0b13c27e0e02dad71b1ae5d6561f51eeb"},
{file = "pydantic_core-2.46.3-cp311-cp311-win_amd64.whl", hash = "sha256:b5b9c6cf08a8a5e502698f5e153056d12c34b8fb30317e0c5fd06f45162a6346"},
{file = "pydantic_core-2.46.3-cp311-cp311-win_arm64.whl", hash = "sha256:5dfd51cf457482f04ec49491811a2b8fd5b843b64b11eecd2d7a1ee596ea78a6"},
{file = "pydantic_core-2.46.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b11b59b3eee90a80a36701ddb4576d9ae31f93f05cb9e277ceaa09e6bf074a67"},
{file = "pydantic_core-2.46.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af8653713055ea18a3abc1537fe2ebc42f5b0bbb768d1eb79fd74eb47c0ac089"},
{file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75a519dab6d63c514f3a81053e5266c549679e4aa88f6ec57f2b7b854aceb1b0"},
{file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6cd87cb1575b1ad05ba98894c5b5c96411ef678fa2f6ed2576607095b8d9789"},
{file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f80a55484b8d843c8ada81ebf70a682f3f00a3d40e378c06cf17ecb44d280d7d"},
{file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3861f1731b90c50a3266316b9044f5c9b405eecb8e299b0a7120596334e4fe9c"},
{file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb528e295ed31570ac3dcc9bfdd6e0150bc11ce6168ac87a8082055cf1a67395"},
{file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:367508faa4973b992b271ba1494acaab36eb7e8739d1e47be5035fb1ea225396"},
{file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ad3c826fe523e4becf4fe39baa44286cff85ef137c729a2c5e269afbfd0905d"},
{file = "pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ec638c5d194ef8af27db69f16c954a09797c0dc25015ad6123eb2c73a4d271ca"},
{file = "pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:28ed528c45446062ee66edb1d33df5d88828ae167de76e773a3c7f64bd14e976"},
{file = "pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aed19d0c783886d5bd86d80ae5030006b45e28464218747dcf83dabfdd092c7b"},
{file = "pydantic_core-2.46.3-cp312-cp312-win32.whl", hash = "sha256:06d5d8820cbbdb4147578c1fe7ffcd5b83f34508cb9f9ab76e807be7db6ff0a4"},
{file = "pydantic_core-2.46.3-cp312-cp312-win_amd64.whl", hash = "sha256:c3212fda0ee959c1dd04c60b601ec31097aaa893573a3a1abd0a47bcac2968c1"},
{file = "pydantic_core-2.46.3-cp312-cp312-win_arm64.whl", hash = "sha256:f1f8338dd7a7f31761f1f1a3c47503a9a3b34eea3c8b01fa6ee96408affb5e72"},
{file = "pydantic_core-2.46.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:12bc98de041458b80c86c56b24df1d23832f3e166cbaff011f25d187f5c62c37"},
{file = "pydantic_core-2.46.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85348b8f89d2c3508b65b16c3c33a4da22b8215138d8b996912bb1532868885f"},
{file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1105677a6df914b1fb71a81b96c8cce7726857e1717d86001f29be06a25ee6f8"},
{file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87082cd65669a33adeba5470769e9704c7cf026cc30afb9cc77fd865578ebaad"},
{file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e5f66e12c4f5212d08522963380eaaeac5ebd795826cfd19b2dfb0c7a52b9c"},
{file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6cdf19bf84128d5e7c37e8a73a0c5c10d51103a650ac585d42dd6ae233f2b7f"},
{file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031bb17f4885a43773c8c763089499f242aee2ea85cf17154168775dccdecf35"},
{file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:bcf2a8b2982a6673693eae7348ef3d8cf3979c1d63b54fca7c397a635cc68687"},
{file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28e8cf2f52d72ced402a137145923a762cbb5081e48b34312f7a0c8f55928ec3"},
{file = "pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:17eaface65d9fc5abb940003020309c1bf7a211f5f608d7870297c367e6f9022"},
{file = "pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:93fd339f23408a07e98950a89644f92c54d8729719a40b30c0a30bb9ebc55d23"},
{file = "pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:23cbdb3aaa74dfe0837975dbf69b469753bbde8eacace524519ffdb6b6e89eb7"},
{file = "pydantic_core-2.46.3-cp313-cp313-win32.whl", hash = "sha256:610eda2e3838f401105e6326ca304f5da1e15393ae25dacae5c5c63f2c275b13"},
{file = "pydantic_core-2.46.3-cp313-cp313-win_amd64.whl", hash = "sha256:68cc7866ed863db34351294187f9b729964c371ba33e31c26f478471c52e1ed0"},
{file = "pydantic_core-2.46.3-cp313-cp313-win_arm64.whl", hash = "sha256:f64b5537ac62b231572879cd08ec05600308636a5d63bcbdb15063a466977bec"},
{file = "pydantic_core-2.46.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:13afdd885f3d71280cf286b13b310ee0f7ccfefd1dbbb661514a474b726e2f25"},
{file = "pydantic_core-2.46.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f91c0aff3e3ee0928edd1232c57f643a7a003e6edf1860bc3afcdc749cb513f3"},
{file = "pydantic_core-2.46.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6529d1d128321a58d30afcc97b49e98836542f68dd41b33c2e972bb9e5290536"},
{file = "pydantic_core-2.46.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:975c267cff4f7e7272eacbe50f6cc03ca9a3da4c4fbd66fffd89c94c1e311aa1"},
{file = "pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2b8e4f2bbdf71415c544b4b1138b8060db7b6611bc927e8064c769f64bed651c"},
{file = "pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e61ea8e9fff9606d09178f577ff8ccdd7206ff73d6552bcec18e1033c4254b85"},
{file = "pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b504bda01bafc69b6d3c7a0c7f039dcf60f47fab70e06fe23f57b5c75bdc82b8"},
{file = "pydantic_core-2.46.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b00b76f7142fc60c762ce579bd29c8fa44aaa56592dd3c54fab3928d0d4ca6ff"},
{file = "pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c"},
{file = "pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594"},
{file = "pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c"},
{file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826"},
{file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04"},
{file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e"},
{file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3"},
{file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4"},
{file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398"},
{file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3"},
{file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848"},
{file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3"},
{file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109"},
{file = "pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda"},
{file = "pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33"},
{file = "pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d"},
{file = "pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2"},
{file = "pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f"},
{file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7"},
{file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7"},
{file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712"},
{file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4"},
{file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce"},
{file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987"},
{file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b"},
{file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458"},
{file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b"},
{file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c"},
{file = "pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894"},
{file = "pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89"},
{file = "pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a"},
{file = "pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008"},
{file = "pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4"},
{file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76"},
{file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3"},
{file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76"},
{file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4"},
{file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a"},
{file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262"},
{file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e"},
{file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd"},
{file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be"},
{file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d"},
{file = "pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb"},
{file = "pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292"},
{file = "pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d"},
{file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0"},
{file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7"},
{file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2"},
{file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9"},
{file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf"},
{file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30"},
{file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc"},
{file = "pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983"},
{file = "pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1"},
]
[[package]]

View File

@@ -428,10 +428,16 @@ class FormField:
raise ValueError("Invalid field definition: Dropdown requires options")
@dc.dataclass(slots=True)
class FormFieldDynList:
label: str
fields: Sequence[FormField]
@dc.dataclass(slots=True)
class FormFieldGroup:
label: str | None
fields: Sequence[FormField]
fields: Sequence[FormField] | FormFieldDynList
FORM_FIELD_DEF = [
@@ -594,6 +600,400 @@ FORM_FIELDS_CONTACT_PERSON = [
),
]
FORM_FIELDS_MASTER_DATA = [
FormField(
"Titel",
FormFieldType.TEXT,
required=False,
tooltip=(
"* nur wenn anrufende Person oder kontaktaufnehmende Person "
"nicht die zu beratende Person ist"
),
),
FormField(
"Anrede",
FormFieldType.TEXT,
required=True,
),
FormField(
"Name",
FormFieldType.TEXT,
required=True,
),
FormField(
"Vorname",
FormFieldType.TEXT,
required=False,
),
FormField(
"Geburtsdatum",
FormFieldType.DATE,
required=False,
tooltip=(
"* Wichtig zu erfragen, da u.a. Mindestgehaltsschwelle davon abhängt "
"(Regelung bei Ü45 Jahre)"
),
),
FormField(
"Herkunftsland",
FormFieldType.DROPDOWN,
required=True,
options=["LÄNDERLISTE NOCH ZU ERGÄNZEN"],
tooltip=("* Wichtig zu erfragen aufgrund eventueller EU-Freizügigkeitsregelung"),
),
FormField(
"Staatsangehörigkeit",
FormFieldType.DROPDOWN,
required=False,
options=["LÄNDERLISTE NOCH ZU ERGÄNZEN"],
tooltip=("* Wichtig zu erfragen aufgrund eventueller EU-Freizügigkeitsregelung"),
),
FormField(
"Rückkehrer",
FormFieldType.DROPDOWN,
required=False,
options=["ja", "nein"],
tooltip=("* Wichtig zu erfragen aufgrund eventueller EU-Freizügigkeitsregelung"),
),
FormField(
"Wo befindet sich die Person?",
FormFieldType.DROPDOWN,
required=True,
options=["Inland", "Ausland EU/EWR", "Ausland Drittstaat"],
),
FormField(
"Straße",
FormFieldType.TEXT,
required=False,
),
FormField(
"Hausnummer",
FormFieldType.TEXT,
required=False,
),
FormField(
"PLZ",
FormFieldType.TEXT,
required=False,
),
FormField(
"Ort",
FormFieldType.TEXT,
required=False,
),
FormField(
"Bundesland",
FormFieldType.DROPDOWN,
required=False,
options=["BUNDESLÄNDER NOCH ZU ERGÄNZEN"],
tooltip=(
"nur wenn Inland angegeben und die Angabe zieht es in keine Dokumente "
"rüber! Liste Bundesländer verwenden"
),
),
FormField(
"Land",
FormFieldType.TEXT,
required=False,
),
FormField(
"Festnetznummer",
FormFieldType.TEXT,
required=False,
),
FormField(
"Mobilfunknummer",
FormFieldType.TEXT,
required=False,
),
FormField(
"E-Mail",
FormFieldType.TEXT,
required=False,
),
FormField(
"Familienstand",
FormFieldType.TEXT,
required=False,
tooltip="* Wichtig zu erfragen aufgrund Lebensunterhaltssicherung",
),
FormField(
"Anzahl Kinder",
FormFieldType.DROPDOWN,
required=False,
options=[str(x) for x in range(11)],
tooltip="* Wichtig zu erfragen aufgrund Lebensunterhaltssicherung",
),
]
FORM_FIELDS_ADDITIONAL_DATA = [
FormField(
"Deutsch als Kommunikationssprache",
FormFieldType.DROPDOWN,
required=False,
options=["nein", "ja, als Muttersprache", "ja, als Fremdsprache"],
),
FormField(
"Aufenthaltstitel",
FormFieldType.DROPDOWN,
required=False,
options=[
"anerkannter Flüchtling §§ 22 - 26 AufenthG",
"Aufenthaltsgestattung §55 AufenthG",
"Blaue Karte EU § 18g AufenthG",
"BüMA (Bescheinigung über Meldung als Asylsuchender)",
"Duldung § 60 AufenthG",
"bisher kein Aufenthaltstitel",
"Deutscher",
"familiäre Gründe §§ 27 - 36 AufenthG",
"Niederlassungserlaubnis §9 AufenthG",
"Staatsbürger EUR/EWR/CH",
"Aufenthalt für Ausbildung §§ 16 - 17 AufenthG",
"Aufenthalt für Erwerbstätigkeit §§ 18- 21 AufenthG",
"Chancenaufenthaltsrecht §104c AufenthG",
"Sonstiges",
],
tooltip="sofern nicht bekannt, unbedingt einfordern",
),
FormField(
"Gültigkeit Aufenthaltsstatus",
FormFieldType.DATE,
required=False,
),
FormField(
"Arbeitsstatus aktuell",
FormFieldType.DROPDOWN,
required=False,
options=[
"Arbeitslos",
"Ausbildung/Qualifizierung Inland",
"geringfügig beschäftigt",
"in Anstellung Inland",
"selbstständig Inland",
"Ausbildung/Qualifizierung Ausland",
"in Anstellung Ausland",
"selbstständig Ausland",
],
),
FormField(
"Gemeldet bei Institutionen ",
FormFieldType.DROPDOWN,
required=False,
options=[
"bei keiner",
"Jobcenter mit Leistungsbezug",
"Jobcenter ohne Leistungsbezug",
"Sozialamt mit Leistungsbezug",
"Sozialamt ohne Leistungsbezug",
"Agentur für Arbeit mit Leistungsbezug",
"Agentur für Arbeit ohne Leistungsbezug",
],
),
]
FORM_FIELDS_SCHOOL = [
FormField(
"Abschluss",
FormFieldType.TEXT,
required=False,
),
FormField(
"Abschlussgrad laut Dokument",
FormFieldType.TEXT,
required=False,
),
FormField(
"Schule",
FormFieldType.TEXT,
required=False,
),
FormField(
"Ort",
FormFieldType.TEXT,
required=False,
),
FormField(
"Land",
FormFieldType.DROPDOWN,
required=False,
options=["LÄNDERLISTE ERGÄNZEN"],
),
FormField(
"Abschlussjahr",
FormFieldType.TEXT,
required=False,
),
FormField(
"Bemerkungsfeld",
FormFieldType.TEXT,
required=False,
),
]
FORM_FIELDS_HIGHER_EDUCATION = [
FormField(
"Anerkennung",
FormFieldType.TEXT,
required=False,
),
FormField(
"Abschlussgrad",
FormFieldType.TEXT,
required=False,
tooltip=(
"bitte den Titel eingeben z.B. Doktor, Diplom oder "
"Betriebswirt (Fachschulabschluss)"
),
),
FormField(
"Abschlussgrad laut Dokument",
FormFieldType.TEXT,
required=False,
),
FormField(
"Hochschule / Ausbildungsbetrieb / Berufsschule",
FormFieldType.TEXT,
required=False,
),
FormField(
"Beruf / Fachrichtung",
FormFieldType.TEXT,
required=False,
tooltip=(
"bitte spezifizieren z.B. Allgemeinmedizin, Ingenieur Maschinenbau, "
"technischer Betriebswirt Datenverarbeitung"
),
),
FormField(
"Land",
FormFieldType.DROPDOWN,
required=False,
options=["LÄNDERLISTE ERGÄNZEN"],
),
FormField(
"Ort",
FormFieldType.TEXT,
required=False,
),
FormField(
"Abschlussjahr",
FormFieldType.TEXT,
required=False,
),
FormField(
"Bemerkungsfeld",
FormFieldType.TEXT,
required=False,
tooltip="z.B. Promotionen oder den Studiengang angeben",
),
]
FORM_FIELDS_WORK_EXPERIENCE = [
FormField(
"Branche",
FormFieldType.DROPDOWN,
required=False,
options=["DROPDOWN-LISTE AN ANDERER STELLE DEFINIERT"],
),
FormField(
"Berufsbezeichnung/Tätigkeit",
FormFieldType.TEXT,
required=False,
),
FormField(
"Funktion",
FormFieldType.DROPDOWN,
required=False,
options=[
"Auszubildender",
"Fachkraft",
"Hilfskraft",
"Akademiker",
"Führungskraft",
"Praktikant",
"FSJ/BFD",
"Elternzeit",
"Sabbatical",
"Sonstiges",
],
),
FormField(
"Unternehmen",
FormFieldType.TEXT,
required=False,
),
FormField(
"Land",
FormFieldType.DROPDOWN,
required=False,
options=["LÄNDERLISTE ERGÄNZEN"],
),
FormField(
"Zeitspanne (von ... bis ...)",
FormFieldType.TEXT,
required=False,
),
FormField(
"Beschäftsigungsart",
FormFieldType.DROPDOWN,
required=False,
options=[
"Vollzeit",
"Teilzeit",
"Sonstiges",
],
tooltip="Minijob, Praktikum, Wehrdienst, soziale Dienste",
),
FormField(
"Bemerkungsfeld",
FormFieldType.TEXT,
required=False,
),
]
FORM_FIELDS_LANGUAGES = [
FormField(
"Sprache",
FormFieldType.TEXT,
required=False,
),
FormField(
"Niveau",
FormFieldType.DROPDOWN,
required=False,
options=[
"A1",
"A2",
"B1",
"B2",
"C1",
"C2",
],
),
FormField(
"Nachweis",
FormFieldType.DROPDOWN,
required=False,
options=[
"vorhanden",
"nicht vorhanden",
],
),
FormField(
"Art des Nachweises (NUR WENN VORHANDEN)",
FormFieldType.TEXT,
required=False,
),
FormField(
"Datum des Nachweises (NUR WENN VORHANDEN)",
FormFieldType.DATE,
required=False,
),
]
FORM_DYN_LIST = FormFieldDynList("Dynamische Liste", FORM_FIELDS_SCHOOL)
FORM_FIELD_GROUPS = [
FormFieldGroup(
"Status && Projektrelevanz",
@@ -608,6 +1008,13 @@ FORM_FIELD_GROUPS = [
],
),
FormFieldGroup("Daten Kontaktperson", FORM_FIELDS_CONTACT_PERSON),
FormFieldGroup("Stammdaten (ERGÄNZUNG KINDERALTER DYNAMISCH)", FORM_FIELDS_MASTER_DATA),
FormFieldGroup("weitere Informationen", FORM_FIELDS_ADDITIONAL_DATA),
FormFieldGroup("Schule (MIT PLUS-ERWEITERUNG)", FORM_FIELDS_SCHOOL),
FormFieldGroup("Studium/Ausbildung (MIT PLUS-ERWEITERUNG)", FORM_FIELDS_HIGHER_EDUCATION),
FormFieldGroup("Arbeitserfahrung (MIT PLUS-ERWEITERUNG)", FORM_FIELDS_WORK_EXPERIENCE),
FormFieldGroup("Sprachkenntnisse (MIT PLUS-ERWEITERUNG)", FORM_FIELDS_LANGUAGES),
FormFieldGroup("Dynamische Liste", FORM_DYN_LIST),
# FormFieldGroup("Test-2", FORM_FIELD_DEF2),
]
@@ -645,9 +1052,14 @@ class AutoForm(QWidget):
self.widgets: dict[str, QWidget] = {}
for fg in form_field_groups:
form_group = FormGroupWidget(fg.fields, fg.label)
self.main_layout.addWidget(form_group)
self._add_widgets(form_group.widgets)
_sub = fg.fields
if isinstance(_sub, FormFieldDynList):
widget = DynamicListWidget(_sub.fields, _sub.label)
else:
widget = FormGroupWidget(_sub, fg.label)
self.main_layout.addWidget(widget)
# TODO widget list is not updated with dynamic lists
# self._add_widgets(widget.widgets)
# self.widgets.update(form_group.widgets)
# buttons
@@ -707,7 +1119,10 @@ class AutoForm(QWidget):
# Wir gehen unsere Feld-Definitionen durch
for fg in self.form_field_groups:
for field in fg.fields:
if isinstance(fg.fields, DynamicListWidget):
continue
for field in fg.fields: # type: ignore
widget = self.widgets[field.key]
# 1. Zuerst setzen wir das Design des Feldes wieder auf "Normal" zurück.
@@ -747,8 +1162,9 @@ class AutoForm(QWidget):
# --- ERGEBNIS AUSWERTEN ---
if errors:
# Es gibt Fehler! Speichern abbrechen und Pop-up anzeigen.
error_text = "Bitte fülle die folgenden Pflichtfelder aus:\n\n- " + "\n- ".join(
errors
error_text = (
"Bitte füllen Sie die folgenden Pflichtfelder aus:\n\n- "
+ "\n- ".join(errors)
)
QMessageBox.warning(self, "Fehlende Angaben", error_text)
self._enable_save()
@@ -762,7 +1178,10 @@ class AutoForm(QWidget):
def reset_form(self) -> None:
for fg in self.form_field_groups:
for field in fg.fields:
if isinstance(fg.fields, DynamicListWidget):
continue
for field in fg.fields: # type: ignore
widget = self.widgets[field.key]
if field.readonly:
@@ -803,6 +1222,109 @@ class AutoForm(QWidget):
return data
class DynamicListWidget(QWidget):
"""
Ein Widget, das dynamisch beliebig viele Sub-Formulare
(z.B. Adressen) erzeugen und verwalten kann.
"""
def __init__(self, fields: Sequence[FormField], title="Eintrag"):
super().__init__()
self.fields = fields
self.title = title
self.widgets: dict[str, QWidget] = {}
# Das Hauptlayout für diese Liste
self.group_box = QGroupBox(title)
self.main_layout = QVBoxLayout(self)
self.main_layout.setContentsMargins(0, 0, 0, 0)
self.inner_layout = QVBoxLayout()
self.main_layout.addWidget(self.group_box)
self.group_box.setLayout(self.inner_layout)
# Liste, in der wir uns die generierten Sub-Formulare merken
self.sub_forms = []
# Der "+ Hinzufügen" Button
self.add_btn = QPushButton("+ Weitere hinzufügen")
self.add_btn.setStyleSheet(
"color: #0369a1; font-weight: bold; border: 1px dashed #0369a1; padding: 5px;"
)
self.add_btn.clicked.connect(self.add_entry)
self.inner_layout.addWidget(self.add_btn)
# Direkt beim Start ein leeres Formular anzeigen (optional)
self.add_entry()
def add_entry(self):
"""Erzeugt ein neues Sub-Formular und fügt es VOR dem Plus-Button ein."""
# 1. Den Rahmen für das einzelne Sub-Formular bauen
entry_box = QGroupBox(f"{self.title} {len(self.sub_forms) + 1}")
entry_layout = QHBoxLayout(entry_box)
# TEST auto form generator
# TODO: keys may not be fixed
form_widget = FormGroupWidget(self.fields, None)
self.widgets.update(form_widget.widgets)
# 2. Das eigentliche Formular (hier könntest du auch deinen automatisierten
# Form-Generator rekursiv aufrufen!)
# target_layout = QFormLayout()
# street_input = QLineEdit()
# city_input = QLineEdit()
# target_layout.addRow("Straße:", street_input)
# target_layout.addRow("Stadt:", city_input)
# 3. Den "Löschen" (-) Button bauen
del_btn = QPushButton("🗑️")
del_btn.setFixedSize(30, 30)
# Lambda mit default-Parameter, um genau DIESE entry_box zu löschen
del_btn.clicked.connect(lambda checked=False, box=entry_box: self.remove_entry(box))
# 4. Alles zusammensetzen
# entry_layout.addLayout(target_layout)
entry_layout.addWidget(form_widget)
entry_layout.addWidget(del_btn)
# 5. Daten merken, um sie später auszulesen
self.sub_forms.append({"box": entry_box, "form_widget": form_widget})
# 6. In das Hauptlayout einfügen (Index - 1 bedeutet: VOR dem "+" Button)
self.inner_layout.insertWidget(self.main_layout.count() - 1, entry_box)
self.update_titles()
def remove_entry(self, box_to_remove: QGroupBox):
"""Löscht ein Sub-Formular wieder."""
# Das Widget aus dem Layout entfernen und zerstören
self.main_layout.removeWidget(box_to_remove)
box_to_remove.deleteLater()
# Aus unserer internen Liste filtern
self.sub_forms = [f for f in self.sub_forms if f["box"] != box_to_remove]
self.update_titles()
def update_titles(self):
"""Nummeriert die Boxen nach dem Löschen neu durch."""
for index, form in enumerate(self.sub_forms):
form["box"].setTitle(f"{self.title} {index + 1}")
def get_data(self):
"""Liest die Daten aller dynamisch erzeugten Formulare aus!"""
raise NotImplementedError()
results = []
for form in self.sub_forms:
# Hier bauen wir ein Dictionary pro Sub-Formular
entry_data = {
"strasse": form["widgets"]["strasse"].text(),
"ort": form["widgets"]["ort"].text(),
}
results.append(entry_data)
return results
class FormGroupWidget(QWidget):
def __init__(
self,
@@ -844,16 +1366,21 @@ class FormGroupWidget(QWidget):
# --- LAYOUT ---
self.main_layout = QVBoxLayout(self)
self.main_layout.setContentsMargins(0, 0, 0, 0)
self.inner_layout = QVBoxLayout()
# self.inner_layout.setContentsMargins(0, 0, 0, 0)
self.form_layout = QFormLayout()
self.inner_layout.addLayout(self.form_layout)
self.group_box: QGroupBox | None = None
self.group_name = group_name
if self.group_name:
self.group_box = QGroupBox(group_name)
self.main_layout.addWidget(self.group_box)
self.group_box.setLayout(self.form_layout)
self.group_box.setLayout(self.inner_layout)
# self.group_box.setLayout(self.form_layout)
else:
self.main_layout.addLayout(self.form_layout)
# self.main_layout.addLayout(self.form_layout)
self.main_layout.addLayout(self.inner_layout)
self.form_layout.setSpacing(10) # Abstand zwischen den Zeilen
@@ -919,20 +1446,30 @@ class FormGroupWidget(QWidget):
case FormFieldType.DROPDOWN:
widget = QComboBox()
assert field.options
widget.addItem("--- Bitte wählen ---")
widget.addItems(field.options)
if field.placeholder:
widget.setPlaceholderText(field.placeholder)
if field.fill_value:
widget.setCurrentText(field.fill_value)
else:
widget.setCurrentIndex(0)
if field.readonly:
widget.setEnabled(False)
widget.setProperty("styleClass", "stempel")
# case FormFieldType.DYNAMIC_LIST:
# widget = DynamicListWidget(field.label)
case _:
raise NotImplementedError(f"Not supported field type: {field.type.value}")
self.widgets[field.key] = widget
# if field.type is FormFieldType.DYNAMIC_LIST:
# self.inner_layout.addWidget(widget)
# continue
if not field.tooltip:
self.form_layout.addRow(field.label, widget)
continue
@@ -952,6 +1489,17 @@ class FormGroupWidget(QWidget):
self.form_layout.addRow(field.label, field_layout)
# from PySide6.QtWidgets import (
# QFormLayout,
# QGroupBox,
# QHBoxLayout,
# QLineEdit,
# QPushButton,
# QVBoxLayout,
# QWidget,
# )
class ClickableCell(QFrame):
# Wir definieren ein Signal, das ein Dictionary (die Daten) mitschickt
clicked = Signal(dict)
@@ -1273,7 +1821,7 @@ class SearchFormPage(QWidget):
]
)
combo.setPlaceholderText("Bitte auswählen")
combo.model().item(0).setEnabled(False)
combo.model().item(0).setEnabled(False) # type: ignore
hor_layout.addWidget(label)
hor_layout.addWidget(combo, stretch=1)
container_layout.addLayout(hor_layout)

View File

@@ -5,7 +5,7 @@ description = "GUI for CRM of NAFKA project with WCE"
authors = [
{name = "d-opt GmbH, resp. Florian Förster", email = "f.foerster@d-opt.com"},
]
dependencies = ["nicegui>=3.10.0", "pyside6>=6.11.0", "sqlalchemy>=2.0.49", "polars>=1.40.1", "dopt-basics>=0.2.4"]
dependencies = ["nicegui>=3.10.0", "pyside6>=6.11.0", "sqlalchemy>=2.0.49", "polars>=1.40.1", "dopt-basics>=0.2.4", "pydantic>=2.13.4"]
requires-python = "<3.14,>=3.11"
readme = "README.md"
license = {text = "LicenseRef-Proprietary"}