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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arthexis
3
- Version: 0.1.20
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.11
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==7.2
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.20.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
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=T-0QSbtieEWKPIDkEcEdd-q6qjK8ZCwwjCaISOBkWdM,1296
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=620NySqqkpp-U-hDRyHp5M-U_JBweqvyxi4FUJ2rc_M,21454
13
+ config/settings.py,sha256=ILOI8CROVwchUeJUsdqqXwaFCn26YSjyNof_t0VFqFo,20622
14
14
  config/settings_helpers.py,sha256=0BdBciUHIkwsWa0vV_RKAd4wDuEzgE7G-42XYiES4YQ,3127
15
- config/urls.py,sha256=zvU4FSMKPlXUrGDjUgJCRFQztWb78wo1urW2DQf8qdI,5463
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=3MhT1TA8QUAgmjlwBmLD6Z1nT19ZUrXLCBSchV1R6LE,151562
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=L_UMYI72-5jTo6nt8mfCbgdLhlP32D-8k76EZw0QyAA,14348
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=DfmVn2HVwX0rru-BSak6n3R3ier0UiHCZUoNszjt6oA,10182
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=U4oyaIuh1VSvpK7xwyZek09hRzVtY75zYXh1at7-_Pk,131276
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=a78FZug8JPbvIMx1MgxmcYGyTpMr3WboTvqjnxI9evs,98715
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=U3hwNqJz_I9ybmmdJvLrljrynJaUzGMeHhsiaBJD4mk,90297
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=20gUgL5clMr_hNugybDqYdhoZh-4cWB2dVp6lgnHo_I,66960
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=OS9yI940avVZNNE9tn0i-Tr-BnyHKiUFKz0O1adNsUQ,176567
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=ZaUlTLQUaRbQsiLCfTuwigxXSpoeUqBkrs938KsjmjA,36962
71
+ nodes/views.py,sha256=IaTyEbS0GDlLLpX7SwRwNSY57mJEjQ2-knOvdP5MDdQ,38508
74
72
  ocpp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
- ocpp/admin.py,sha256=1X2g-ICBJyp2Queh8Bexu8vXex6qU9onWN5yruczZ0s,38318
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=wUGYSh382e_zYXq059GCaWqUCjQTTvYbbt0PdlONMFc,34411
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=8Tzb_agTQdLdHnccAGCTfYbcRQHznz5DJI3Hb94mg5M,190065
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=G2y2g55XJBJ0gzZPUqA4pplKfQIrO3-fjH5CkOYqDsw,63152
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=0Ie3_lPY6-j6f8eXtSzMlWpprMzY0tvX9fcOdpJr6Vs,5788
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=rgMYFiei8ONBNts4vJzukjaWN-WUGLxr6LFwtSHJQ_8,141550
106
- pages/urls.py,sha256=rcK0zEJTSCurYhx77ZK_GVVMSHTS5ZJWoKTJkh1lj-M,1305
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=2ik28HW70a0S84K8hMQ8qL9yzVH1WggXT7S8G5ZaLgA,50940
109
- arthexis-0.1.20.dist-info/METADATA,sha256=RLz9veHKDPM6UzcxnJgZcxj3ri6TuTPVLSK9QBn48mM,11751
110
- arthexis-0.1.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
111
- arthexis-0.1.20.dist-info/top_level.txt,sha256=J2a2q8_BWrCZ8H2WFUNMBfO2jz8j2gax6zZh-_1QDac,29
112
- arthexis-0.1.20.dist-info/RECORD,,
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": http_application,
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
@@ -156,7 +156,6 @@ urlpatterns = [
156
156
  ),
157
157
  path("admin/", admin.site.urls),
158
158
  path("i18n/setlang/", csrf_exempt(set_language), name="set_language"),
159
- path("api/", include("core.workgroup_urls")),
160
159
  path("", include("pages.urls")),
161
160
  ]
162
161
 
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 N or N/P (prefix 16 or 24) to supply --subnet."),
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(r"(?P<subnet>\d{1,3})(?:/(?P<prefix>\d{1,2}))?", raw)
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 N or N/P with prefix 16 or 24."),
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"{subnet}/{prefix}"
105
- return str(subnet)
123
+ return f"{subnet_value}/{prefix}"
124
+ return subnet_value
106
125
 
107
126
  def clean(self) -> dict:
108
127
  cleaned_data = super().clean()