arthexis 0.1.20__py3-none-any.whl → 0.1.21__py3-none-any.whl
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.
- {arthexis-0.1.20.dist-info → arthexis-0.1.21.dist-info}/METADATA +3 -4
- {arthexis-0.1.20.dist-info → arthexis-0.1.21.dist-info}/RECORD +25 -27
- config/asgi.py +1 -15
- config/settings.py +0 -26
- config/urls.py +0 -1
- core/admin.py +1 -233
- core/apps.py +0 -6
- core/environment.py +29 -10
- core/models.py +8 -77
- core/tests.py +1 -7
- core/views.py +0 -96
- nodes/admin.py +29 -6
- nodes/tests.py +49 -0
- nodes/views.py +60 -1
- ocpp/admin.py +48 -6
- ocpp/models.py +48 -0
- ocpp/tests.py +83 -0
- ocpp/views.py +85 -3
- pages/context_processors.py +0 -12
- pages/tests.py +0 -41
- pages/urls.py +0 -1
- pages/views.py +0 -5
- core/workgroup_urls.py +0 -17
- core/workgroup_views.py +0 -94
- {arthexis-0.1.20.dist-info → arthexis-0.1.21.dist-info}/WHEEL +0 -0
- {arthexis-0.1.20.dist-info → arthexis-0.1.21.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.20.dist-info → arthexis-0.1.21.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arthexis
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.21
|
|
4
4
|
Summary: Power & Energy Infrastructure
|
|
5
5
|
Author-email: "Rafael J. Guillén-Osorio" <tecnologia@gelectriic.com>
|
|
6
6
|
License-Expression: GPL-3.0-only
|
|
@@ -59,7 +59,6 @@ Requires-Dist: kombu==5.5.4
|
|
|
59
59
|
Requires-Dist: libipld==3.1.1
|
|
60
60
|
Requires-Dist: Markdown==3.8.2
|
|
61
61
|
Requires-Dist: mdx_truly_sane_lists==1.3
|
|
62
|
-
Requires-Dist: mcp==1.18.0
|
|
63
62
|
Requires-Dist: mfrc522==0.0.7; sys_platform == "linux"
|
|
64
63
|
Requires-Dist: outcome==1.3.0.post0
|
|
65
64
|
Requires-Dist: packaging==25.0
|
|
@@ -67,7 +66,7 @@ Requires-Dist: pillow==11.3.0
|
|
|
67
66
|
Requires-Dist: prompt_toolkit==3.0.51
|
|
68
67
|
Requires-Dist: psutil==7.1.1
|
|
69
68
|
Requires-Dist: psycopg==3.2.9
|
|
70
|
-
Requires-Dist: psycopg-binary==3.2.
|
|
69
|
+
Requires-Dist: psycopg-binary==3.2.12
|
|
71
70
|
Requires-Dist: pyasn1==0.6.1
|
|
72
71
|
Requires-Dist: pyasn1_modules==0.4.2
|
|
73
72
|
Requires-Dist: pycparser==2.22
|
|
@@ -111,7 +110,7 @@ Requires-Dist: websockets==13.1
|
|
|
111
110
|
Requires-Dist: whitenoise==6.11.0
|
|
112
111
|
Requires-Dist: plyer==2.1.0; sys_platform == "win32"
|
|
113
112
|
Requires-Dist: wsproto==1.2.0
|
|
114
|
-
Requires-Dist: zope.interface==
|
|
113
|
+
Requires-Dist: zope.interface==8.0.1
|
|
115
114
|
Dynamic: license-file
|
|
116
115
|
|
|
117
116
|
# Arthexis Constellation
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
arthexis-0.1.
|
|
1
|
+
arthexis-0.1.21.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
2
2
|
config/__init__.py,sha256=AwpOX7il-DAOmkdJ5dVfVJ3CWWebn1lHyQNmkw1EkDw,103
|
|
3
3
|
config/active_app.py,sha256=KJqYh-o91nPQjVXPEdbiJHzsI6cN9IZsBZ9O3iZ6Hyc,373
|
|
4
|
-
config/asgi.py,sha256=
|
|
4
|
+
config/asgi.py,sha256=Z2HjWrxOxVU9BXcqS7dMEfOGJC48H-WPwFwokRdermY,774
|
|
5
5
|
config/auth_app.py,sha256=cLlKgFYV4VWsMACinKrpP-nZhXXbWZp1aj1RayDszRc,198
|
|
6
6
|
config/celery.py,sha256=c8fPkjfhKw0UQBr6FolzfdvRX2MQAV-dMokSlJ1Afgg,819
|
|
7
7
|
config/context_processors.py,sha256=p74ocuzPRFI9vKSeIaJ42Vu0V2GtGph1t-2DkRo4NMw,2449
|
|
@@ -10,20 +10,20 @@ config/loadenv.py,sha256=CjXx-wBaTt1wixub4GJ5CMSMFqtiK5JURc7cPXpqO7s,287
|
|
|
10
10
|
config/logging.py,sha256=1cIbPgRshHuMKnVEEH0jKpRAlJSpewvLFbYDz7sCBG4,2104
|
|
11
11
|
config/middleware.py,sha256=zF8Cma0n5G8NNdh2LVeNJi7Hgl1G4mF9msRE2eRi1RU,2328
|
|
12
12
|
config/offline.py,sha256=X-yDcyoI4C44Y27lpkUwszY_09GwwFfazEsthKJpQ70,1382
|
|
13
|
-
config/settings.py,sha256=
|
|
13
|
+
config/settings.py,sha256=ILOI8CROVwchUeJUsdqqXwaFCn26YSjyNof_t0VFqFo,20622
|
|
14
14
|
config/settings_helpers.py,sha256=0BdBciUHIkwsWa0vV_RKAd4wDuEzgE7G-42XYiES4YQ,3127
|
|
15
|
-
config/urls.py,sha256=
|
|
15
|
+
config/urls.py,sha256=REDaNai8fhGnQd_3vRrFIPECB3YZ5FX5Y2tO6IpIUPs,5413
|
|
16
16
|
config/wsgi.py,sha256=zU_mKlya6hejQ21PxKacTui3dUWd4ca_-YJNSYAoMX0,433
|
|
17
17
|
core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
-
core/admin.py,sha256=
|
|
18
|
+
core/admin.py,sha256=o2nEhTHLHL03fVVe8uMTcIP5kXg4OPYF-BGipyNeyoo,142635
|
|
19
19
|
core/admin_history.py,sha256=XZ4b0ryufIka-xcwboK3DzmOL-INSx5Y2fJO-aJdV70,1783
|
|
20
20
|
core/admindocs.py,sha256=ycD0bJ_VE6rTGf9ebXTiKdYkD8Y8hD2oQ4HxxoBURCM,6756
|
|
21
|
-
core/apps.py,sha256=
|
|
21
|
+
core/apps.py,sha256=S6fySxtxUzfvz8FI9dii0KI4wSyLhh5API_oeERLIsc,14084
|
|
22
22
|
core/auto_upgrade.py,sha256=1EffHHFylgydWdZM_id6CppV0QqBtdNw7cwBYVdbNdk,1715
|
|
23
23
|
core/backends.py,sha256=okrNW5Z-zDhJB9Al529aTOHGsTLACeQ6v_hMl4cyfnA,10704
|
|
24
24
|
core/changelog.py,sha256=SRn37i5N-qb-RYV4Gpu9fg7Kv8gu4TH8ZwEmDRgN-Vo,12594
|
|
25
25
|
core/entity.py,sha256=o4VteOXePGEsIWJFZ3fpq3DZsdWr3hpQ9A6kFbKosSE,4844
|
|
26
|
-
core/environment.py,sha256=
|
|
26
|
+
core/environment.py,sha256=z8LJW9bfVSaqWJT_4bXqUMSGTXVi3XGdsu9s_GH3_J0,11039
|
|
27
27
|
core/fields.py,sha256=d-qGahdcv4SRcO4fwCJ6_-NnEAP5xW0k3kODdAAAHSA,5412
|
|
28
28
|
core/form_fields.py,sha256=h2xT8sO8EWbznsiARkxukFk69yoW6mQwqpgonA-d6aA,2496
|
|
29
29
|
core/github_helper.py,sha256=fkjoUPwOB19zbGuk39LNLJ5AbIVKFf3rNCtnu-JISIc,5733
|
|
@@ -34,7 +34,7 @@ core/liveupdate.py,sha256=22m0ueQ10-6b-9pQJHY0_5WRYA98fysXKEXOWzIr550,691
|
|
|
34
34
|
core/log_paths.py,sha256=lxvgXPgJtVNZ-kYrqV8VFle4GFQrSxG-yRTglqvclmU,3318
|
|
35
35
|
core/mailer.py,sha256=JpW0RnD9uZ4O-wvlqeW7CMw95IFeCSkdvbankJDwHq0,2886
|
|
36
36
|
core/middleware.py,sha256=j19K9SX-Emkv7BDDtAacR9g6RWsxhKHwCc8w23JFvMM,3388
|
|
37
|
-
core/models.py,sha256=
|
|
37
|
+
core/models.py,sha256=hbI63Rho16lJ6L1AQ3azxQKUrzZhtZ_g6SygFPW_21A,128915
|
|
38
38
|
core/notifications.py,sha256=jNLSuSCrhb8x5cDu_APeDlkrmbMejufk5eJOhssAC4I,3917
|
|
39
39
|
core/public_wifi.py,sha256=yydLgxOo9DmJJbM4X_23wGR3gxL3YzHno54v9GssuFA,7213
|
|
40
40
|
core/reference_utils.py,sha256=tffCoyE1w4_SmYzXVWOsW8aR_ZVVTSPzrGhBq8K2xzA,3631
|
|
@@ -47,16 +47,14 @@ core/system.py,sha256=RyBqooWezM0Li_KCRskchD5Lub0cdqBmrnU6ilC8MPE,39823
|
|
|
47
47
|
core/tasks.py,sha256=d0MQP5fmn5pA2VCFGxDMEX0xppDIIh8IAzPfGdnk8J4,12340
|
|
48
48
|
core/temp_passwords.py,sha256=FieUnIUeQHmA1DoXvfJ5U6-Ayv3oDz-hSln5s_vNbA4,5271
|
|
49
49
|
core/test_system_info.py,sha256=IMPz21KEs6OC5YbL7YaIBdmJVLjRY6MgPuZpldJB5OI,6935
|
|
50
|
-
core/tests.py,sha256=
|
|
50
|
+
core/tests.py,sha256=F-aC_wal9R9vPR_VvaeTzVZzRFtLYXmzNSdob1_1tc8,98456
|
|
51
51
|
core/tests_liveupdate.py,sha256=IquU8ztk6zbzC1bQu3Nrr3RzGzuujtPwDkANJHbxg98,510
|
|
52
52
|
core/urls.py,sha256=YPippON1MAP2KeZZ8jHpcLO6mvbnKn1q7fdMv5Vm9dY,425
|
|
53
53
|
core/user_data.py,sha256=02CfvxayELWSWZJCxWpv1Yz7EGg08yEu5MM31Khsi0U,21083
|
|
54
|
-
core/views.py,sha256=
|
|
54
|
+
core/views.py,sha256=raBaNQIQhEb_4_wm1V3imHG8FuJaiTU2Jh6nIS8gNY4,86768
|
|
55
55
|
core/widgets.py,sha256=vlR9PlFfZGlkHm5X2cqNXuEBZSj8gmWaR6MO1mMy6kg,6904
|
|
56
|
-
core/workgroup_urls.py,sha256=XR9IqwsSBI8epW7_-hHhWFU9wsyJfZehHwNQBhCgmpM,407
|
|
57
|
-
core/workgroup_views.py,sha256=vtumF3-8YaTD-K6nSd8eYvUyq3ftpvWSEwtcp5B-P6o,2889
|
|
58
56
|
nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
59
|
-
nodes/admin.py,sha256=
|
|
57
|
+
nodes/admin.py,sha256=Unv5FSNTnheMQ4Ez7Ww8eSpbFKaqTJ1XpAnlhOvPjGE,67871
|
|
60
58
|
nodes/apps.py,sha256=AxK-sh9JBJZwNOLjqw9omCQGUQWw-45VRdYH07XhVJU,2732
|
|
61
59
|
nodes/backends.py,sha256=dmmbS0X2YIlCDz2KjoDf_L62dy--nuqZF1rEDoi2JHM,5921
|
|
62
60
|
nodes/dns.py,sha256=D5smXD7Rkh6E4MdL6TBL2WY8GgJg7Rx9z88LZrcMbTw,7048
|
|
@@ -67,17 +65,17 @@ nodes/reports.py,sha256=NRYh3Y0SlZFhx31Zh2K03yO12ZrpxEHEY6T-dODA6WE,12059
|
|
|
67
65
|
nodes/rfid_sync.py,sha256=oeblawcp6xeLApdIuhsJS83OAk58Eu7pVVmgpAc0Nt8,6953
|
|
68
66
|
nodes/signals.py,sha256=PtOKdQfb08mV1LgSZvn7ZAcfOyy2c3Xkq4AOpBQyUdE,622
|
|
69
67
|
nodes/tasks.py,sha256=7m9pKO-iI6JDdfPQ-GWRGown4mdyKrcroOnhbiWN7dY,5246
|
|
70
|
-
nodes/tests.py,sha256=
|
|
68
|
+
nodes/tests.py,sha256=54FARHRm4GFT1aP29DYItGsvPVGchoqj7ZpG94PN4t4,178320
|
|
71
69
|
nodes/urls.py,sha256=-o9_pLo6XHerKMQwL0TW80wm6wmtVZqyNWcUhpdq9vk,915
|
|
72
70
|
nodes/utils.py,sha256=wt7UuSXGuq79A-g-B6EW3kK49QWJBb7zhhkw4pun4k8,4474
|
|
73
|
-
nodes/views.py,sha256=
|
|
71
|
+
nodes/views.py,sha256=IaTyEbS0GDlLLpX7SwRwNSY57mJEjQ2-knOvdP5MDdQ,38508
|
|
74
72
|
ocpp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
75
|
-
ocpp/admin.py,sha256=
|
|
73
|
+
ocpp/admin.py,sha256=FZzhq8vfpglZXfdkwwGSTB7gtkShZAvO6PTELl4fkEs,40008
|
|
76
74
|
ocpp/apps.py,sha256=i3NqrmIamNEQBT33CIqh7HOSOPmJXCMKrZ-DUd3whqg,842
|
|
77
75
|
ocpp/consumers.py,sha256=wt9tauYSlpWnGbaGsJKIPt4D13HyloLIHwmlK8iqA0Q,71674
|
|
78
76
|
ocpp/evcs.py,sha256=q1mZrCVSZxXTrtYsDqH6lkeEcJ6tfSC7p9YxkDmpSCw,28883
|
|
79
77
|
ocpp/evcs_discovery.py,sha256=OmrzgaOHwveDRJs8AIhrM3apX8_k2PPXh_oYaYpNW3c,3876
|
|
80
|
-
ocpp/models.py,sha256=
|
|
78
|
+
ocpp/models.py,sha256=J7JiXNyiFZ48l6_QNY3OHG1QPFVye-4u0qxmunwXc-U,35936
|
|
81
79
|
ocpp/reference_utils.py,sha256=_UR82GfE93kv4766mHyVIfdhhyYvrT59660r3H6W55M,1072
|
|
82
80
|
ocpp/routing.py,sha256=3kQya-MdJ00778xDmX0esQLBP05P200V45asg-CGNoo,438
|
|
83
81
|
ocpp/simulator.py,sha256=vnyd59QffT79AaPhmfM_jipni_nqfG57X5tXyx1rBoc,28016
|
|
@@ -86,15 +84,15 @@ ocpp/store.py,sha256=gLCSaP9KKF7li2ALlE3O3RW5eVJtoe-_YHfKhdf0VOM,18943
|
|
|
86
84
|
ocpp/tasks.py,sha256=n2axf1Oo7brZtXRe-uCwt6K0f57ZlHUNvp6c5W0Gdzo,6035
|
|
87
85
|
ocpp/test_export_import.py,sha256=ouQbTCp4mxfqoK6gondlu3PPcyrT9jSbWAX5gqqgaNk,4561
|
|
88
86
|
ocpp/test_rfid.py,sha256=IhFSlvsI8A8D3S32sRE298nYfrmqxbv7GfVErtNU3DQ,39137
|
|
89
|
-
ocpp/tests.py,sha256=
|
|
87
|
+
ocpp/tests.py,sha256=PaQcsX8nJNAaQUixw6JjNiEnPy2LJGeM60qi3DEyhQ4,193151
|
|
90
88
|
ocpp/transactions_io.py,sha256=p2aUsKlCDYnZ4ZBrOM7pxXoW_w3Tbm-tvRFSjnR3x24,7738
|
|
91
89
|
ocpp/urls.py,sha256=5ZomUtznJe3kfs8E-DtVp12eFva5jUuJdpTEczIsQ5w,1730
|
|
92
|
-
ocpp/views.py,sha256=
|
|
90
|
+
ocpp/views.py,sha256=vGWio3Hv4M8ScKhZEBVhCf3IeumTeJdCONExWBQbF24,66218
|
|
93
91
|
pages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
94
92
|
pages/admin.py,sha256=E-jcxUI-89gFgqGI38HMSHbfDw1inpSUiRR83utJ0fc,32512
|
|
95
93
|
pages/apps.py,sha256=o8gQP-VdZOk9LXIEo6IDmOSqX3TP8XypBvKGGWLoQ0k,351
|
|
96
94
|
pages/checks.py,sha256=sM8_hUVM_HOIocvtTb2sY3AaSEvbTnOlO46UchGVd-0,1527
|
|
97
|
-
pages/context_processors.py,sha256=
|
|
95
|
+
pages/context_processors.py,sha256=vrgMu4vYCOonZ8eZ27gQvGU74PBpMi47T512Lu1__sA,5297
|
|
98
96
|
pages/defaults.py,sha256=3tjv3nFPxwpFu6poJ1Ez1MP92Q6ZvyRluftKHlU-zeI,522
|
|
99
97
|
pages/forms.py,sha256=T0atqxdNds3IBP8N-9c5-ACf3iR9FzzmhzK4MOa24e8,7058
|
|
100
98
|
pages/middleware.py,sha256=MYd5Nko4AnFg3orY6MuyvvNg_I6GCIf8mDW8znSOgvQ,7042
|
|
@@ -102,11 +100,11 @@ pages/models.py,sha256=Ms_m_tzzstNghN_JOzyfwsbllDBIl_AKnYdTRTJthqM,22173
|
|
|
102
100
|
pages/module_defaults.py,sha256=rCAY8aTyxYNL0M5zDr393rX-Gi-svXqKtuLXm0rILrQ,5444
|
|
103
101
|
pages/site_config.py,sha256=f1Me0GFdHeGbIeyMlQNzD2e6hym59YHqbz92U_ppffY,4057
|
|
104
102
|
pages/tasks.py,sha256=ivcba_3wSQ1-cku0oDplzw6vLeQ9hBq3R4TG-LmR5gs,1913
|
|
105
|
-
pages/tests.py,sha256=
|
|
106
|
-
pages/urls.py,sha256=
|
|
103
|
+
pages/tests.py,sha256=vMTVK_wCOsf08KmeOiWoYibW5zbfvrl96oWu7Z8D_cM,139914
|
|
104
|
+
pages/urls.py,sha256=neNFYT4fEsiiNAEXao_CklOLnd2FIWFf1hxlM_DVsHo,1231
|
|
107
105
|
pages/utils.py,sha256=CR4D1debgJLGgXsw9kap2ggpe7fIpSoWS_ivbgMNp2k,564
|
|
108
|
-
pages/views.py,sha256=
|
|
109
|
-
arthexis-0.1.
|
|
110
|
-
arthexis-0.1.
|
|
111
|
-
arthexis-0.1.
|
|
112
|
-
arthexis-0.1.
|
|
106
|
+
pages/views.py,sha256=6vriwnTgS-jeH3cNsJyz0ec6F1enZJAUJ7vRVmqILv8,50790
|
|
107
|
+
arthexis-0.1.21.dist-info/METADATA,sha256=rO5YIGveESngYUQQ3mDUM8xAoWRHVjXYFoKlS9XV0uw,11726
|
|
108
|
+
arthexis-0.1.21.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
109
|
+
arthexis-0.1.21.dist-info/top_level.txt,sha256=J2a2q8_BWrCZ8H2WFUNMBfO2jz8j2gax6zZh-_1QDac,29
|
|
110
|
+
arthexis-0.1.21.dist-info/RECORD,,
|
config/asgi.py
CHANGED
|
@@ -9,35 +9,21 @@ https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
|
|
|
9
9
|
|
|
10
10
|
import os
|
|
11
11
|
from config.loadenv import loadenv
|
|
12
|
-
from typing import Any, Awaitable, Callable, Dict, MutableMapping
|
|
13
12
|
from channels.auth import AuthMiddlewareStack
|
|
14
13
|
from channels.routing import ProtocolTypeRouter, URLRouter
|
|
15
14
|
from django.core.asgi import get_asgi_application
|
|
16
15
|
import ocpp.routing
|
|
17
16
|
|
|
18
|
-
from core.mcp.asgi import application as mcp_application
|
|
19
|
-
from core.mcp.asgi import is_mcp_scope
|
|
20
|
-
|
|
21
17
|
loadenv()
|
|
22
18
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
|
23
19
|
|
|
24
20
|
django_asgi_app = get_asgi_application()
|
|
25
21
|
|
|
26
|
-
Scope = MutableMapping[str, Any]
|
|
27
|
-
Receive = Callable[[], Awaitable[Dict[str, Any]]]
|
|
28
|
-
Send = Callable[[Dict[str, Any]], Awaitable[None]]
|
|
29
|
-
|
|
30
22
|
websocket_patterns = ocpp.routing.websocket_urlpatterns
|
|
31
23
|
|
|
32
|
-
async def http_application(scope: Scope, receive: Receive, send: Send) -> None:
|
|
33
|
-
if is_mcp_scope(scope):
|
|
34
|
-
await mcp_application(scope, receive, send)
|
|
35
|
-
else:
|
|
36
|
-
await django_asgi_app(scope, receive, send)
|
|
37
|
-
|
|
38
24
|
application = ProtocolTypeRouter(
|
|
39
25
|
{
|
|
40
|
-
"http":
|
|
26
|
+
"http": django_asgi_app,
|
|
41
27
|
"websocket": AuthMiddlewareStack(URLRouter(websocket_patterns)),
|
|
42
28
|
}
|
|
43
29
|
)
|
config/settings.py
CHANGED
|
@@ -444,32 +444,6 @@ ASGI_APPLICATION = "config.asgi.application"
|
|
|
444
444
|
CHANNEL_LAYERS = {"default": {"BACKEND": "channels.layers.InMemoryChannelLayer"}}
|
|
445
445
|
|
|
446
446
|
|
|
447
|
-
# MCP sigil resolver configuration
|
|
448
|
-
def _env_int(name: str, default: int) -> int:
|
|
449
|
-
try:
|
|
450
|
-
return int(os.environ.get(name, default))
|
|
451
|
-
except (TypeError, ValueError): # pragma: no cover - defensive
|
|
452
|
-
return default
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
def _split_env_list(name: str) -> list[str]:
|
|
456
|
-
raw = os.environ.get(name)
|
|
457
|
-
if not raw:
|
|
458
|
-
return []
|
|
459
|
-
return [item.strip() for item in raw.split(",") if item.strip()]
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
MCP_SIGIL_SERVER = {
|
|
463
|
-
"host": os.environ.get("MCP_SIGIL_HOST", "127.0.0.1"),
|
|
464
|
-
"port": _env_int("MCP_SIGIL_PORT", 8800),
|
|
465
|
-
"api_keys": _split_env_list("MCP_SIGIL_API_KEYS"),
|
|
466
|
-
"required_scopes": ["sigils:read"],
|
|
467
|
-
"issuer_url": os.environ.get("MCP_SIGIL_ISSUER_URL"),
|
|
468
|
-
"resource_server_url": os.environ.get("MCP_SIGIL_RESOURCE_URL"),
|
|
469
|
-
"mount_path": os.environ.get("MCP_SIGIL_MOUNT_PATH", "/mcp"),
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
|
|
473
447
|
# Custom user model
|
|
474
448
|
AUTH_USER_MODEL = "core.User"
|
|
475
449
|
|
config/urls.py
CHANGED
core/admin.py
CHANGED
|
@@ -91,9 +91,7 @@ from .models import (
|
|
|
91
91
|
SecurityGroup,
|
|
92
92
|
InviteLead,
|
|
93
93
|
PublicWifiAccess,
|
|
94
|
-
AssistantProfile,
|
|
95
94
|
Todo,
|
|
96
|
-
hash_key,
|
|
97
95
|
)
|
|
98
96
|
from .user_data import (
|
|
99
97
|
EntityModelAdmin,
|
|
@@ -110,8 +108,6 @@ from .rfid_import_export import (
|
|
|
110
108
|
parse_accounts,
|
|
111
109
|
serialize_accounts,
|
|
112
110
|
)
|
|
113
|
-
from .mcp import process as mcp_process
|
|
114
|
-
from .mcp.server import resolve_base_urls
|
|
115
111
|
from . import release as release_utils
|
|
116
112
|
|
|
117
113
|
logger = logging.getLogger(__name__)
|
|
@@ -1304,46 +1300,6 @@ class ReleaseManagerInlineForm(ProfileFormMixin, forms.ModelForm):
|
|
|
1304
1300
|
}
|
|
1305
1301
|
|
|
1306
1302
|
|
|
1307
|
-
class AssistantProfileInlineForm(ProfileFormMixin, forms.ModelForm):
|
|
1308
|
-
user_key = forms.CharField(
|
|
1309
|
-
required=False,
|
|
1310
|
-
widget=forms.PasswordInput(render_value=True),
|
|
1311
|
-
help_text="Provide a plain key to create or rotate credentials.",
|
|
1312
|
-
)
|
|
1313
|
-
profile_fields = ("assistant_name", "user_key", "scopes", "is_active")
|
|
1314
|
-
|
|
1315
|
-
class Meta:
|
|
1316
|
-
model = AssistantProfile
|
|
1317
|
-
fields = ("assistant_name", "scopes", "is_active")
|
|
1318
|
-
|
|
1319
|
-
def __init__(self, *args, **kwargs):
|
|
1320
|
-
super().__init__(*args, **kwargs)
|
|
1321
|
-
if not self.instance.pk and "is_active" in self.fields:
|
|
1322
|
-
self.fields["is_active"].initial = False
|
|
1323
|
-
|
|
1324
|
-
def clean(self):
|
|
1325
|
-
cleaned = super().clean()
|
|
1326
|
-
if cleaned.get("DELETE"):
|
|
1327
|
-
return cleaned
|
|
1328
|
-
if not self.instance.pk and not cleaned.get("user_key"):
|
|
1329
|
-
if cleaned.get("scopes") or cleaned.get("is_active"):
|
|
1330
|
-
raise forms.ValidationError(
|
|
1331
|
-
"Provide a user key to create an assistant profile."
|
|
1332
|
-
)
|
|
1333
|
-
return cleaned
|
|
1334
|
-
|
|
1335
|
-
def save(self, commit=True):
|
|
1336
|
-
instance = super().save(commit=False)
|
|
1337
|
-
user_key = self.cleaned_data.get("user_key")
|
|
1338
|
-
if user_key:
|
|
1339
|
-
instance.user_key_hash = hash_key(user_key)
|
|
1340
|
-
instance.last_used_at = None
|
|
1341
|
-
if commit:
|
|
1342
|
-
instance.save()
|
|
1343
|
-
self.save_m2m()
|
|
1344
|
-
return instance
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
1303
|
PROFILE_INLINE_CONFIG = {
|
|
1348
1304
|
OdooProfile: {
|
|
1349
1305
|
"form": OdooProfileInlineForm,
|
|
@@ -1474,12 +1430,6 @@ PROFILE_INLINE_CONFIG = {
|
|
|
1474
1430
|
"secondary_pypi_url",
|
|
1475
1431
|
),
|
|
1476
1432
|
},
|
|
1477
|
-
AssistantProfile: {
|
|
1478
|
-
"form": AssistantProfileInlineForm,
|
|
1479
|
-
"fields": ("assistant_name", "user_key", "scopes", "is_active"),
|
|
1480
|
-
"readonly_fields": ("user_key_hash", "created_at", "last_used_at"),
|
|
1481
|
-
"template": "admin/edit_inline/profile_stacked.html",
|
|
1482
|
-
},
|
|
1483
1433
|
}
|
|
1484
1434
|
|
|
1485
1435
|
|
|
@@ -1526,7 +1476,6 @@ PROFILE_MODELS = (
|
|
|
1526
1476
|
EmailOutbox,
|
|
1527
1477
|
SocialProfile,
|
|
1528
1478
|
ReleaseManager,
|
|
1529
|
-
AssistantProfile,
|
|
1530
1479
|
)
|
|
1531
1480
|
USER_PROFILE_INLINES = [
|
|
1532
1481
|
_build_profile_inline(model, "user") for model in PROFILE_MODELS
|
|
@@ -2013,188 +1962,6 @@ class EmailInboxAdmin(ProfileAdminMixin, SaveBeforeChangeAction, EntityModelAdmi
|
|
|
2013
1962
|
return TemplateResponse(request, "admin/core/emailinbox/search.html", context)
|
|
2014
1963
|
|
|
2015
1964
|
|
|
2016
|
-
@admin.register(AssistantProfile)
|
|
2017
|
-
class AssistantProfileAdmin(
|
|
2018
|
-
ProfileAdminMixin, SaveBeforeChangeAction, EntityModelAdmin
|
|
2019
|
-
):
|
|
2020
|
-
list_display = ("assistant_name", "owner", "created_at", "last_used_at", "is_active")
|
|
2021
|
-
readonly_fields = ("user_key_hash", "created_at", "last_used_at")
|
|
2022
|
-
|
|
2023
|
-
change_form_template = "admin/workgroupassistantprofile_change_form.html"
|
|
2024
|
-
change_list_template = "admin/assistantprofile_change_list.html"
|
|
2025
|
-
change_actions = ["my_profile_action"]
|
|
2026
|
-
changelist_actions = ["my_profile"]
|
|
2027
|
-
fieldsets = (
|
|
2028
|
-
("Owner", {"fields": ("user", "group")}),
|
|
2029
|
-
("Credentials", {"fields": ("user_key_hash",)}),
|
|
2030
|
-
(
|
|
2031
|
-
"Configuration",
|
|
2032
|
-
{
|
|
2033
|
-
"fields": (
|
|
2034
|
-
"assistant_name",
|
|
2035
|
-
"scopes",
|
|
2036
|
-
"is_active",
|
|
2037
|
-
"created_at",
|
|
2038
|
-
"last_used_at",
|
|
2039
|
-
)
|
|
2040
|
-
},
|
|
2041
|
-
),
|
|
2042
|
-
)
|
|
2043
|
-
|
|
2044
|
-
def owner(self, obj):
|
|
2045
|
-
return obj.owner_display()
|
|
2046
|
-
|
|
2047
|
-
owner.short_description = "Owner"
|
|
2048
|
-
|
|
2049
|
-
def get_urls(self):
|
|
2050
|
-
urls = super().get_urls()
|
|
2051
|
-
opts = self.model._meta
|
|
2052
|
-
app_label = opts.app_label
|
|
2053
|
-
model_name = opts.model_name
|
|
2054
|
-
custom = [
|
|
2055
|
-
path(
|
|
2056
|
-
"<path:object_id>/generate-key/",
|
|
2057
|
-
self.admin_site.admin_view(self.generate_key),
|
|
2058
|
-
name=f"{app_label}_{model_name}_generate_key",
|
|
2059
|
-
),
|
|
2060
|
-
path(
|
|
2061
|
-
"server/start/",
|
|
2062
|
-
self.admin_site.admin_view(self.start_server),
|
|
2063
|
-
name=f"{app_label}_{model_name}_start_server",
|
|
2064
|
-
),
|
|
2065
|
-
path(
|
|
2066
|
-
"server/stop/",
|
|
2067
|
-
self.admin_site.admin_view(self.stop_server),
|
|
2068
|
-
name=f"{app_label}_{model_name}_stop_server",
|
|
2069
|
-
),
|
|
2070
|
-
path(
|
|
2071
|
-
"server/status/",
|
|
2072
|
-
self.admin_site.admin_view(self.server_status),
|
|
2073
|
-
name=f"{app_label}_{model_name}_status",
|
|
2074
|
-
),
|
|
2075
|
-
]
|
|
2076
|
-
return custom + urls
|
|
2077
|
-
|
|
2078
|
-
def changelist_view(self, request, extra_context=None):
|
|
2079
|
-
extra_context = extra_context or {}
|
|
2080
|
-
status = mcp_process.get_status()
|
|
2081
|
-
opts = self.model._meta
|
|
2082
|
-
app_label = opts.app_label
|
|
2083
|
-
model_name = opts.model_name
|
|
2084
|
-
extra_context.update(
|
|
2085
|
-
{
|
|
2086
|
-
"mcp_status": status,
|
|
2087
|
-
"mcp_server_actions": {
|
|
2088
|
-
"start": reverse(f"admin:{app_label}_{model_name}_start_server"),
|
|
2089
|
-
"stop": reverse(f"admin:{app_label}_{model_name}_stop_server"),
|
|
2090
|
-
"status": reverse(f"admin:{app_label}_{model_name}_status"),
|
|
2091
|
-
},
|
|
2092
|
-
}
|
|
2093
|
-
)
|
|
2094
|
-
return super().changelist_view(request, extra_context=extra_context)
|
|
2095
|
-
|
|
2096
|
-
def _redirect_to_changelist(self):
|
|
2097
|
-
opts = self.model._meta
|
|
2098
|
-
return HttpResponseRedirect(
|
|
2099
|
-
reverse(f"admin:{opts.app_label}_{opts.model_name}_changelist")
|
|
2100
|
-
)
|
|
2101
|
-
|
|
2102
|
-
def generate_key(self, request, object_id, *args, **kwargs):
|
|
2103
|
-
profile = self.get_object(request, object_id)
|
|
2104
|
-
if profile is None:
|
|
2105
|
-
return HttpResponseRedirect("../")
|
|
2106
|
-
if profile.user is None:
|
|
2107
|
-
self.message_user(
|
|
2108
|
-
request,
|
|
2109
|
-
"Assign a user before generating a key.",
|
|
2110
|
-
level=messages.ERROR,
|
|
2111
|
-
)
|
|
2112
|
-
return HttpResponseRedirect("../")
|
|
2113
|
-
profile, key = AssistantProfile.issue_key(profile.user)
|
|
2114
|
-
context = {
|
|
2115
|
-
**self.admin_site.each_context(request),
|
|
2116
|
-
"opts": self.model._meta,
|
|
2117
|
-
"original": profile,
|
|
2118
|
-
"user_key": key,
|
|
2119
|
-
}
|
|
2120
|
-
return TemplateResponse(request, "admin/assistantprofile_key.html", context)
|
|
2121
|
-
|
|
2122
|
-
def render_change_form(
|
|
2123
|
-
self, request, context, add=False, change=False, form_url="", obj=None
|
|
2124
|
-
):
|
|
2125
|
-
response = super().render_change_form(
|
|
2126
|
-
request, context, add=add, change=change, form_url=form_url, obj=obj
|
|
2127
|
-
)
|
|
2128
|
-
config = dict(getattr(settings, "MCP_SIGIL_SERVER", {}))
|
|
2129
|
-
host = config.get("host") or "127.0.0.1"
|
|
2130
|
-
port = config.get("port", 8800)
|
|
2131
|
-
base_url, issuer_url = resolve_base_urls(config)
|
|
2132
|
-
mount_path = config.get("mount_path") or "/"
|
|
2133
|
-
display_base_url = base_url or f"http://{host}:{port}"
|
|
2134
|
-
display_issuer_url = issuer_url or display_base_url
|
|
2135
|
-
chat_endpoint = f"{display_base_url.rstrip('/')}/api/chat/"
|
|
2136
|
-
if isinstance(response, dict):
|
|
2137
|
-
response.setdefault("mcp_server_host", host)
|
|
2138
|
-
response.setdefault("mcp_server_port", port)
|
|
2139
|
-
response.setdefault("mcp_server_base_url", display_base_url)
|
|
2140
|
-
response.setdefault("mcp_server_issuer_url", display_issuer_url)
|
|
2141
|
-
response.setdefault("mcp_server_mount_path", mount_path)
|
|
2142
|
-
response.setdefault("mcp_server_chat_endpoint", chat_endpoint)
|
|
2143
|
-
else:
|
|
2144
|
-
context_data = getattr(response, "context_data", None)
|
|
2145
|
-
if context_data is not None:
|
|
2146
|
-
context_data.setdefault("mcp_server_host", host)
|
|
2147
|
-
context_data.setdefault("mcp_server_port", port)
|
|
2148
|
-
context_data.setdefault("mcp_server_base_url", display_base_url)
|
|
2149
|
-
context_data.setdefault("mcp_server_issuer_url", display_issuer_url)
|
|
2150
|
-
context_data.setdefault("mcp_server_mount_path", mount_path)
|
|
2151
|
-
context_data.setdefault("mcp_server_chat_endpoint", chat_endpoint)
|
|
2152
|
-
return response
|
|
2153
|
-
|
|
2154
|
-
def start_server(self, request):
|
|
2155
|
-
try:
|
|
2156
|
-
pid = mcp_process.start_server()
|
|
2157
|
-
except mcp_process.ServerAlreadyRunningError as exc:
|
|
2158
|
-
self.message_user(request, str(exc), level=messages.WARNING)
|
|
2159
|
-
except mcp_process.ServerStartError as exc:
|
|
2160
|
-
self.message_user(request, str(exc), level=messages.ERROR)
|
|
2161
|
-
else:
|
|
2162
|
-
self.message_user(
|
|
2163
|
-
request,
|
|
2164
|
-
f"Started MCP server (PID {pid}).",
|
|
2165
|
-
level=messages.SUCCESS,
|
|
2166
|
-
)
|
|
2167
|
-
return self._redirect_to_changelist()
|
|
2168
|
-
|
|
2169
|
-
def stop_server(self, request):
|
|
2170
|
-
try:
|
|
2171
|
-
pid = mcp_process.stop_server()
|
|
2172
|
-
except mcp_process.ServerNotRunningError as exc:
|
|
2173
|
-
self.message_user(request, str(exc), level=messages.WARNING)
|
|
2174
|
-
except mcp_process.ServerStopError as exc:
|
|
2175
|
-
self.message_user(request, str(exc), level=messages.ERROR)
|
|
2176
|
-
else:
|
|
2177
|
-
self.message_user(
|
|
2178
|
-
request,
|
|
2179
|
-
f"Stopped MCP server (PID {pid}).",
|
|
2180
|
-
level=messages.SUCCESS,
|
|
2181
|
-
)
|
|
2182
|
-
return self._redirect_to_changelist()
|
|
2183
|
-
|
|
2184
|
-
def server_status(self, request):
|
|
2185
|
-
status = mcp_process.get_status()
|
|
2186
|
-
if status["running"]:
|
|
2187
|
-
msg = f"MCP server is running (PID {status['pid']})."
|
|
2188
|
-
level = messages.INFO
|
|
2189
|
-
else:
|
|
2190
|
-
msg = "MCP server is not running."
|
|
2191
|
-
level = messages.WARNING
|
|
2192
|
-
if status.get("last_error"):
|
|
2193
|
-
msg = f"{msg} {status['last_error']}"
|
|
2194
|
-
self.message_user(request, msg, level=level)
|
|
2195
|
-
return self._redirect_to_changelist()
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
1965
|
class EnergyCreditInline(admin.TabularInline):
|
|
2199
1966
|
model = EnergyCredit
|
|
2200
1967
|
fields = ("amount_kw", "created_by", "created_on")
|
|
@@ -3690,6 +3457,7 @@ class RFIDAdmin(EntityModelAdmin, ImportExportModelAdmin):
|
|
|
3690
3457
|
"toggle_url": toggle_url,
|
|
3691
3458
|
"toggle_label": toggle_label,
|
|
3692
3459
|
"public_view_url": public_view_url,
|
|
3460
|
+
"deep_read_url": reverse("rfid-scan-deep"),
|
|
3693
3461
|
}
|
|
3694
3462
|
)
|
|
3695
3463
|
context["title"] = _("Scan RFIDs")
|
core/apps.py
CHANGED
|
@@ -348,9 +348,3 @@ class CoreConfig(AppConfig):
|
|
|
348
348
|
weak=False,
|
|
349
349
|
)
|
|
350
350
|
|
|
351
|
-
try:
|
|
352
|
-
from .mcp.auto_start import schedule_auto_start
|
|
353
|
-
|
|
354
|
-
schedule_auto_start(check_profiles_immediately=False)
|
|
355
|
-
except Exception: # pragma: no cover - defensive
|
|
356
|
-
logger.exception("Failed to schedule MCP auto-start")
|
core/environment.py
CHANGED
|
@@ -71,7 +71,7 @@ class NetworkSetupForm(forms.Form):
|
|
|
71
71
|
ethernet_subnet = forms.CharField(
|
|
72
72
|
label=_("Ethernet subnet"),
|
|
73
73
|
required=False,
|
|
74
|
-
help_text=_("Provide
|
|
74
|
+
help_text=_("Provide Z, Z/P (prefix 16 or 24), X.Y.Z, or X.Y.Z/P to supply --subnet."),
|
|
75
75
|
)
|
|
76
76
|
update_ap_password_only = forms.BooleanField(
|
|
77
77
|
label=_("Update access point password only"),
|
|
@@ -84,16 +84,35 @@ class NetworkSetupForm(forms.Form):
|
|
|
84
84
|
if not value:
|
|
85
85
|
return ""
|
|
86
86
|
raw = value.strip()
|
|
87
|
-
match = re.fullmatch(
|
|
87
|
+
match = re.fullmatch(
|
|
88
|
+
r"(?P<first>\d{1,3})(?:\.(?P<second>\d{1,3})\.(?P<third>\d{1,3}))?(?:/(?P<prefix>\d{1,2}))?",
|
|
89
|
+
raw,
|
|
90
|
+
)
|
|
88
91
|
if not match:
|
|
89
92
|
raise forms.ValidationError(
|
|
90
|
-
_("Enter a subnet in the form
|
|
91
|
-
)
|
|
92
|
-
subnet = int(match.group("subnet"))
|
|
93
|
-
if subnet < 0 or subnet > 254:
|
|
94
|
-
raise forms.ValidationError(
|
|
95
|
-
_("Subnet value must be between 0 and 254."),
|
|
93
|
+
_("Enter a subnet in the form Z, Z/P, X.Y.Z, or X.Y.Z/P with prefix 16 or 24."),
|
|
96
94
|
)
|
|
95
|
+
first_octet = int(match.group("first"))
|
|
96
|
+
second = match.group("second")
|
|
97
|
+
third = match.group("third")
|
|
98
|
+
if second is not None and third is not None:
|
|
99
|
+
octets = [first_octet, int(second), int(third)]
|
|
100
|
+
for octet in octets:
|
|
101
|
+
if octet < 0 or octet > 255:
|
|
102
|
+
raise forms.ValidationError(
|
|
103
|
+
_("Subnet octets must be between 0 and 255."),
|
|
104
|
+
)
|
|
105
|
+
if octets[2] > 254:
|
|
106
|
+
raise forms.ValidationError(
|
|
107
|
+
_("The third subnet octet must be between 0 and 254."),
|
|
108
|
+
)
|
|
109
|
+
subnet_value = ".".join(str(octet) for octet in octets)
|
|
110
|
+
else:
|
|
111
|
+
if first_octet < 0 or first_octet > 254:
|
|
112
|
+
raise forms.ValidationError(
|
|
113
|
+
_("Subnet value must be between 0 and 254."),
|
|
114
|
+
)
|
|
115
|
+
subnet_value = str(first_octet)
|
|
97
116
|
prefix_value = match.group("prefix")
|
|
98
117
|
if prefix_value:
|
|
99
118
|
prefix = int(prefix_value)
|
|
@@ -101,8 +120,8 @@ class NetworkSetupForm(forms.Form):
|
|
|
101
120
|
raise forms.ValidationError(
|
|
102
121
|
_("Subnet prefix must be 16 or 24."),
|
|
103
122
|
)
|
|
104
|
-
return f"{
|
|
105
|
-
return
|
|
123
|
+
return f"{subnet_value}/{prefix}"
|
|
124
|
+
return subnet_value
|
|
106
125
|
|
|
107
126
|
def clean(self) -> dict:
|
|
108
127
|
cleaned_data = super().clean()
|