arthexis 0.1.21__py3-none-any.whl → 0.1.23__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.

Potentially problematic release.


This version of arthexis might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arthexis
3
- Version: 0.1.21
3
+ Version: 0.1.23
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
@@ -26,14 +26,14 @@ Requires-Dist: celery==5.5.3
26
26
  Requires-Dist: certifi==2025.7.14
27
27
  Requires-Dist: cffi==2.0.0
28
28
  Requires-Dist: channels==4.1.0
29
- Requires-Dist: charset-normalizer==3.4.2
29
+ Requires-Dist: charset-normalizer==3.4.4
30
30
  Requires-Dist: click==8.2.1
31
31
  Requires-Dist: click-didyoumean==0.3.1
32
32
  Requires-Dist: click-plugins==1.1.1.2
33
33
  Requires-Dist: click-repl==0.3.0
34
34
  Requires-Dist: colorama==0.4.6
35
35
  Requires-Dist: constantly==23.10.4
36
- Requires-Dist: cron-descriptor==1.4.5
36
+ Requires-Dist: cron-descriptor==2.0.6
37
37
  Requires-Dist: cryptography==45.0.5
38
38
  Requires-Dist: daphne==4.2.1
39
39
  Requires-Dist: diff-match-patch==20241021
@@ -56,15 +56,15 @@ Requires-Dist: hyperlink==21.0.0
56
56
  Requires-Dist: idna==3.11
57
57
  Requires-Dist: incremental==24.7.2
58
58
  Requires-Dist: kombu==5.5.4
59
- Requires-Dist: libipld==3.1.1
60
- Requires-Dist: Markdown==3.8.2
59
+ Requires-Dist: libipld==3.2.0
60
+ Requires-Dist: Markdown==3.9
61
61
  Requires-Dist: mdx_truly_sane_lists==1.3
62
62
  Requires-Dist: mfrc522==0.0.7; sys_platform == "linux"
63
63
  Requires-Dist: outcome==1.3.0.post0
64
64
  Requires-Dist: packaging==25.0
65
65
  Requires-Dist: pillow==11.3.0
66
66
  Requires-Dist: prompt_toolkit==3.0.51
67
- Requires-Dist: psutil==7.1.1
67
+ Requires-Dist: psutil==7.1.2
68
68
  Requires-Dist: psycopg==3.2.9
69
69
  Requires-Dist: psycopg-binary==3.2.12
70
70
  Requires-Dist: pyasn1==0.6.1
@@ -79,7 +79,7 @@ Requires-Dist: python-crontab==3.3.0
79
79
  Requires-Dist: python-dateutil==2.9.0.post0
80
80
  Requires-Dist: python-dotenv==1.1.1
81
81
  Requires-Dist: qrcode==8.2
82
- Requires-Dist: redis==5.0.8
82
+ Requires-Dist: redis==7.0.1
83
83
  Requires-Dist: reportlab==4.2.2
84
84
  Requires-Dist: requests==2.32.5
85
85
  Requires-Dist: selenium==4.34.2
@@ -103,7 +103,7 @@ Requires-Dist: typing_extensions==4.14.1
103
103
  Requires-Dist: tzdata==2025.2
104
104
  Requires-Dist: urllib3==2.5.0
105
105
  Requires-Dist: vine==5.1.0
106
- Requires-Dist: wcwidth==0.2.13
106
+ Requires-Dist: wcwidth==0.2.14
107
107
  Requires-Dist: webencodings==0.5.1
108
108
  Requires-Dist: websocket-client==1.8.0
109
109
  Requires-Dist: websockets==13.1
@@ -203,6 +203,7 @@ Terminal nodes can start directly with the scripts below without installing; Con
203
203
  - `--constellation` – enables the multi-user orchestration stack.
204
204
  - Use `./install.sh --help` to list every available flag if you need to customize the node beyond the role defaults.
205
205
  - Upgrade with [`./upgrade.sh`](upgrade.sh).
206
+ - Consult the [Install & Lifecycle Scripts Manual](docs/development/install-lifecycle-scripts-manual.md) for complete flag descriptions and operational notes.
206
207
 
207
208
  - **Windows:**
208
209
  - Run [`install.bat`](install.bat) to install (Terminal role) and [`upgrade.bat`](upgrade.bat) to upgrade.
@@ -1,4 +1,4 @@
1
- arthexis-0.1.21.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
1
+ arthexis-0.1.23.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
4
  config/asgi.py,sha256=Z2HjWrxOxVU9BXcqS7dMEfOGJC48H-WPwFwokRdermY,774
@@ -10,12 +10,12 @@ 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=ILOI8CROVwchUeJUsdqqXwaFCn26YSjyNof_t0VFqFo,20622
13
+ config/settings.py,sha256=b27y7mmQMJmcIFuceNxrX-9yO_R1GqbIf_wFdZNvTWo,20789
14
14
  config/settings_helpers.py,sha256=0BdBciUHIkwsWa0vV_RKAd4wDuEzgE7G-42XYiES4YQ,3127
15
- config/urls.py,sha256=REDaNai8fhGnQd_3vRrFIPECB3YZ5FX5Y2tO6IpIUPs,5413
15
+ config/urls.py,sha256=979WyL05v7nb-Lz_3Qf6Z2QzGtWpXADbIKZ28Zm12ts,5535
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=o2nEhTHLHL03fVVe8uMTcIP5kXg4OPYF-BGipyNeyoo,142635
18
+ core/admin.py,sha256=DhDHDYQM2Za6FbgWoJs31wsVi6zUo0GS0CawOgONOPw,149468
19
19
  core/admin_history.py,sha256=XZ4b0ryufIka-xcwboK3DzmOL-INSx5Y2fJO-aJdV70,1783
20
20
  core/admindocs.py,sha256=ycD0bJ_VE6rTGf9ebXTiKdYkD8Y8hD2oQ4HxxoBURCM,6756
21
21
  core/apps.py,sha256=S6fySxtxUzfvz8FI9dii0KI4wSyLhh5API_oeERLIsc,14084
@@ -23,7 +23,7 @@ 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=z8LJW9bfVSaqWJT_4bXqUMSGTXVi3XGdsu9s_GH3_J0,11039
26
+ core/environment.py,sha256=4beGOYE1BC8q9vBmTLZzFo75nj_3tBekzMqhsNEZVbU,1653
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,77 +34,77 @@ 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=hbI63Rho16lJ6L1AQ3azxQKUrzZhtZ_g6SygFPW_21A,128915
37
+ core/models.py,sha256=A2QOFW6mDp2aDDOkZh9GiAGxGfjzrWD4gdDEaM60fCU,159883
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
41
- core/release.py,sha256=y5NRs0XwB7RQVvMEZoNWYjTBxuG68dOMizUXLRx7-x8,31561
41
+ core/release.py,sha256=tEMcM7qubmFGmER3TD_MG5j4RceHcwtBjczArZWXEWE,31357
42
42
  core/rfid_import_export.py,sha256=petyhPvL0WUpehc6uGUDUhjYQ9AVvc6O49zuhDs6YFw,3516
43
43
  core/sigil_builder.py,sha256=nMuhYlw3j3LosrK85Q0pYsMcfGWCmrmdnv8UG7GTq_o,4856
44
44
  core/sigil_context.py,sha256=GCzjfM6fcVvBtSbVNfmE6sx3HU8QnxnXrCIytnNpQzM,439
45
45
  core/sigil_resolver.py,sha256=rCsypuX-0oWNfKyM1T9ZLWHY0Ezwhtk4VmI0L3krnsE,11098
46
- core/system.py,sha256=RyBqooWezM0Li_KCRskchD5Lub0cdqBmrnU6ilC8MPE,39823
46
+ core/system.py,sha256=6ndxYDPswKkC3ySTwbgXzH0CdQYCZJytfA-99smyv_Q,42249
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=F-aC_wal9R9vPR_VvaeTzVZzRFtLYXmzNSdob1_1tc8,98456
50
+ core/tests.py,sha256=PuxoarDS4reHNV4EDIyVRW7xIOFxZJYou1K_LI9ZNHY,105265
51
51
  core/tests_liveupdate.py,sha256=IquU8ztk6zbzC1bQu3Nrr3RzGzuujtPwDkANJHbxg98,510
52
52
  core/urls.py,sha256=YPippON1MAP2KeZZ8jHpcLO6mvbnKn1q7fdMv5Vm9dY,425
53
- core/user_data.py,sha256=02CfvxayELWSWZJCxWpv1Yz7EGg08yEu5MM31Khsi0U,21083
54
- core/views.py,sha256=raBaNQIQhEb_4_wm1V3imHG8FuJaiTU2Jh6nIS8gNY4,86768
53
+ core/user_data.py,sha256=4pheHB5RqLJtmWMql30CLaCpuVqSyShXb7Sy-crRk_4,22400
54
+ core/views.py,sha256=QIYcBDjzn3YQzP53ub99wVR79d8SCNXRfSX_ENW3snE,88310
55
55
  core/widgets.py,sha256=vlR9PlFfZGlkHm5X2cqNXuEBZSj8gmWaR6MO1mMy6kg,6904
56
56
  nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
- nodes/admin.py,sha256=Unv5FSNTnheMQ4Ez7Ww8eSpbFKaqTJ1XpAnlhOvPjGE,67871
58
- nodes/apps.py,sha256=AxK-sh9JBJZwNOLjqw9omCQGUQWw-45VRdYH07XhVJU,2732
57
+ nodes/admin.py,sha256=2lkeWBCgKuHQQs0olHeE10MvOqhdS8eO9M82EDWhmFc,73679
58
+ nodes/apps.py,sha256=oi_M2Ya8CAR8N_MoYU68u7_9u-9SlIMelzLOgYM9tDs,3059
59
59
  nodes/backends.py,sha256=dmmbS0X2YIlCDz2KjoDf_L62dy--nuqZF1rEDoi2JHM,5921
60
60
  nodes/dns.py,sha256=D5smXD7Rkh6E4MdL6TBL2WY8GgJg7Rx9z88LZrcMbTw,7048
61
61
  nodes/feature_checks.py,sha256=27e4PCkZ8BGWnJCOwMcY2Bo9z7LoeZWiTZuISWGnrzk,3996
62
62
  nodes/lcd.py,sha256=iKA8Wmq85KZD52aTzAU8ZmS144_gbdGMOXcE8yuECps,5758
63
- nodes/models.py,sha256=S2Gc68Q2_8r8RO1ZKxASN5mEOj1q4UWtxdum5A-p0-4,71693
63
+ nodes/models.py,sha256=zIeEfUGduaRJ6CYsIiHlba55wgT6pCtyhiP_2w2ssGE,72890
64
64
  nodes/reports.py,sha256=NRYh3Y0SlZFhx31Zh2K03yO12ZrpxEHEY6T-dODA6WE,12059
65
65
  nodes/rfid_sync.py,sha256=oeblawcp6xeLApdIuhsJS83OAk58Eu7pVVmgpAc0Nt8,6953
66
66
  nodes/signals.py,sha256=PtOKdQfb08mV1LgSZvn7ZAcfOyy2c3Xkq4AOpBQyUdE,622
67
67
  nodes/tasks.py,sha256=7m9pKO-iI6JDdfPQ-GWRGown4mdyKrcroOnhbiWN7dY,5246
68
- nodes/tests.py,sha256=54FARHRm4GFT1aP29DYItGsvPVGchoqj7ZpG94PN4t4,178320
68
+ nodes/tests.py,sha256=22fCi_o6lDf-hZmAgQuCNa8i2u4kjZUvN90unCbKi_E,188926
69
69
  nodes/urls.py,sha256=-o9_pLo6XHerKMQwL0TW80wm6wmtVZqyNWcUhpdq9vk,915
70
70
  nodes/utils.py,sha256=wt7UuSXGuq79A-g-B6EW3kK49QWJBb7zhhkw4pun4k8,4474
71
- nodes/views.py,sha256=IaTyEbS0GDlLLpX7SwRwNSY57mJEjQ2-knOvdP5MDdQ,38508
71
+ nodes/views.py,sha256=Z-bFzCLaP4w1l2NzqknmwsBMwuLSWRtzsbroNfA2vJU,39794
72
72
  ocpp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
- ocpp/admin.py,sha256=FZzhq8vfpglZXfdkwwGSTB7gtkShZAvO6PTELl4fkEs,40008
73
+ ocpp/admin.py,sha256=O5dqd_bwK8k612ltr4Tt9xR3rs3VxxJXCTxarx0mW0w,40970
74
74
  ocpp/apps.py,sha256=i3NqrmIamNEQBT33CIqh7HOSOPmJXCMKrZ-DUd3whqg,842
75
- ocpp/consumers.py,sha256=wt9tauYSlpWnGbaGsJKIPt4D13HyloLIHwmlK8iqA0Q,71674
75
+ ocpp/consumers.py,sha256=7PYlOkSlHSlIz2FUcBjui4uLFEIHOBWIHfnYpvITrMY,71719
76
76
  ocpp/evcs.py,sha256=q1mZrCVSZxXTrtYsDqH6lkeEcJ6tfSC7p9YxkDmpSCw,28883
77
77
  ocpp/evcs_discovery.py,sha256=OmrzgaOHwveDRJs8AIhrM3apX8_k2PPXh_oYaYpNW3c,3876
78
- ocpp/models.py,sha256=J7JiXNyiFZ48l6_QNY3OHG1QPFVye-4u0qxmunwXc-U,35936
78
+ ocpp/models.py,sha256=1m3kVeuyGoJXLeaR__t_DgcVhAK3MHbCdnv3hF0WZMw,37587
79
79
  ocpp/reference_utils.py,sha256=_UR82GfE93kv4766mHyVIfdhhyYvrT59660r3H6W55M,1072
80
80
  ocpp/routing.py,sha256=3kQya-MdJ00778xDmX0esQLBP05P200V45asg-CGNoo,438
81
81
  ocpp/simulator.py,sha256=vnyd59QffT79AaPhmfM_jipni_nqfG57X5tXyx1rBoc,28016
82
82
  ocpp/status_display.py,sha256=YGFosd5HJETA0DcLdsjvx6EfhZSnI8Aa3cMnHG2WsBE,939
83
83
  ocpp/store.py,sha256=gLCSaP9KKF7li2ALlE3O3RW5eVJtoe-_YHfKhdf0VOM,18943
84
- ocpp/tasks.py,sha256=n2axf1Oo7brZtXRe-uCwt6K0f57ZlHUNvp6c5W0Gdzo,6035
84
+ ocpp/tasks.py,sha256=VSna6Mi1jp3zjYTo4y0c5N-n-4efIq5FRKu3z2y8Oio,8950
85
85
  ocpp/test_export_import.py,sha256=ouQbTCp4mxfqoK6gondlu3PPcyrT9jSbWAX5gqqgaNk,4561
86
86
  ocpp/test_rfid.py,sha256=IhFSlvsI8A8D3S32sRE298nYfrmqxbv7GfVErtNU3DQ,39137
87
- ocpp/tests.py,sha256=PaQcsX8nJNAaQUixw6JjNiEnPy2LJGeM60qi3DEyhQ4,193151
87
+ ocpp/tests.py,sha256=0AsSR7UKZQUhWFPEwCE4FJHH1Ykl2UpooSGlBIPJOeU,207815
88
88
  ocpp/transactions_io.py,sha256=p2aUsKlCDYnZ4ZBrOM7pxXoW_w3Tbm-tvRFSjnR3x24,7738
89
89
  ocpp/urls.py,sha256=5ZomUtznJe3kfs8E-DtVp12eFva5jUuJdpTEczIsQ5w,1730
90
- ocpp/views.py,sha256=vGWio3Hv4M8ScKhZEBVhCf3IeumTeJdCONExWBQbF24,66218
90
+ ocpp/views.py,sha256=iwm2oEMN_d9mmjaD6Hh9axqBIpqw_qSWpMSzyHICrE4,77091
91
91
  pages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
92
- pages/admin.py,sha256=E-jcxUI-89gFgqGI38HMSHbfDw1inpSUiRR83utJ0fc,32512
93
- pages/apps.py,sha256=o8gQP-VdZOk9LXIEo6IDmOSqX3TP8XypBvKGGWLoQ0k,351
92
+ pages/admin.py,sha256=VbxkwgjrFx7lXVhwbZuPSvCkS7EC-flBpwOztHejWtE,35834
93
+ pages/apps.py,sha256=0qcTFKVX9_QgqexJtGeph1sHRqq7khJf4x5ZtkWwblg,1424
94
94
  pages/checks.py,sha256=sM8_hUVM_HOIocvtTb2sY3AaSEvbTnOlO46UchGVd-0,1527
95
95
  pages/context_processors.py,sha256=vrgMu4vYCOonZ8eZ27gQvGU74PBpMi47T512Lu1__sA,5297
96
96
  pages/defaults.py,sha256=3tjv3nFPxwpFu6poJ1Ez1MP92Q6ZvyRluftKHlU-zeI,522
97
- pages/forms.py,sha256=T0atqxdNds3IBP8N-9c5-ACf3iR9FzzmhzK4MOa24e8,7058
97
+ pages/forms.py,sha256=r3JM5qp3_4RR01-u6XV8WDOaeiRe4OvCN8Y52FcsAwI,7909
98
98
  pages/middleware.py,sha256=MYd5Nko4AnFg3orY6MuyvvNg_I6GCIf8mDW8znSOgvQ,7042
99
- pages/models.py,sha256=Ms_m_tzzstNghN_JOzyfwsbllDBIl_AKnYdTRTJthqM,22173
99
+ pages/models.py,sha256=9LdIoIK2Epp3YDUk8LUWyhLW5pJ-NiuYTzO_-xKjg0c,23636
100
100
  pages/module_defaults.py,sha256=rCAY8aTyxYNL0M5zDr393rX-Gi-svXqKtuLXm0rILrQ,5444
101
101
  pages/site_config.py,sha256=f1Me0GFdHeGbIeyMlQNzD2e6hym59YHqbz92U_ppffY,4057
102
102
  pages/tasks.py,sha256=ivcba_3wSQ1-cku0oDplzw6vLeQ9hBq3R4TG-LmR5gs,1913
103
- pages/tests.py,sha256=vMTVK_wCOsf08KmeOiWoYibW5zbfvrl96oWu7Z8D_cM,139914
104
- pages/urls.py,sha256=neNFYT4fEsiiNAEXao_CklOLnd2FIWFf1hxlM_DVsHo,1231
103
+ pages/tests.py,sha256=akLS7p62PeUCaSXTAIsjAfDyv4KWOMJ2MkAGjPuX7AE,154350
104
+ pages/urls.py,sha256=Oe88tm67iVHRFcGJLSBidZ0rkRQPRZ_vRt6ahxNqPek,1499
105
105
  pages/utils.py,sha256=CR4D1debgJLGgXsw9kap2ggpe7fIpSoWS_ivbgMNp2k,564
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,,
106
+ pages/views.py,sha256=48I-XlW5Dop5ZEZPpm8iZmkRlGM20XP9-wEC4HtC3pg,63633
107
+ arthexis-0.1.23.dist-info/METADATA,sha256=riViVTMwAbUo-CDK3doWMDCV_wyznoApGZE_AZJxEhc,11886
108
+ arthexis-0.1.23.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
109
+ arthexis-0.1.23.dist-info/top_level.txt,sha256=J2a2q8_BWrCZ8H2WFUNMBfO2jz8j2gax6zZh-_1QDac,29
110
+ arthexis-0.1.23.dist-info/RECORD,,
config/settings.py CHANGED
@@ -662,4 +662,8 @@ CELERY_BEAT_SCHEDULE = {
662
662
  "task": "core.tasks.heartbeat",
663
663
  "schedule": crontab(minute="*/5"),
664
664
  },
665
+ "ocpp_configuration_check": {
666
+ "task": "ocpp.tasks.schedule_daily_charge_point_configuration_checks",
667
+ "schedule": crontab(minute=0, hour=0),
668
+ },
665
669
  }
config/urls.py CHANGED
@@ -134,6 +134,11 @@ urlpatterns = [
134
134
  core_views.todo_done,
135
135
  name="todo-done",
136
136
  ),
137
+ path(
138
+ "admin/core/todos/<int:pk>/delete/",
139
+ core_views.todo_delete,
140
+ name="todo-delete",
141
+ ),
137
142
  path(
138
143
  "admin/core/todos/<int:pk>/snapshot/",
139
144
  core_views.todo_snapshot,
core/admin.py CHANGED
@@ -9,10 +9,13 @@ from django.urls import NoReverseMatch, path, reverse
9
9
  from urllib.parse import urlencode, urlparse
10
10
  from django.shortcuts import get_object_or_404, redirect, render
11
11
  from django.http import (
12
+ FileResponse,
13
+ Http404,
12
14
  HttpResponse,
13
15
  JsonResponse,
14
16
  HttpResponseBase,
15
17
  HttpResponseRedirect,
18
+ HttpResponseNotAllowed,
16
19
  )
17
20
  from django.template.response import TemplateResponse
18
21
  from django.conf import settings
@@ -83,6 +86,7 @@ from .models import (
83
86
  OdooProfile,
84
87
  OpenPayProfile,
85
88
  EmailInbox,
89
+ GoogleCalendarProfile,
86
90
  SocialProfile,
87
91
  EmailCollector,
88
92
  Package,
@@ -605,15 +609,18 @@ class PackageAdmin(SaveBeforeChangeAction, EntityModelAdmin):
605
609
  change_actions = ["create_repository_action", "prepare_next_release_action"]
606
610
 
607
611
  def _prepare(self, request, package):
612
+ if request.method not in {"POST", "GET"}:
613
+ return HttpResponseNotAllowed(["GET", "POST"])
608
614
  from pathlib import Path
609
615
  from packaging.version import Version
610
616
 
611
617
  ver_file = Path("VERSION")
612
- repo_version = (
613
- Version(ver_file.read_text().strip())
614
- if ver_file.exists()
615
- else Version("0.0.0")
616
- )
618
+ if ver_file.exists():
619
+ raw_version = ver_file.read_text().strip()
620
+ cleaned_version = raw_version.rstrip("+") or "0.0.0"
621
+ repo_version = Version(cleaned_version)
622
+ else:
623
+ repo_version = Version("0.0.0")
617
624
 
618
625
  pypi_latest = Version("0.0.0")
619
626
  try:
@@ -1005,6 +1012,41 @@ class OpenPayProfileAdminForm(forms.ModelForm):
1005
1012
  )
1006
1013
 
1007
1014
 
1015
+ class GoogleCalendarProfileAdminForm(forms.ModelForm):
1016
+ """Admin form for :class:`core.models.GoogleCalendarProfile`."""
1017
+
1018
+ api_key = forms.CharField(
1019
+ widget=forms.PasswordInput(render_value=True),
1020
+ required=False,
1021
+ help_text="Leave blank to keep the current key.",
1022
+ )
1023
+
1024
+ class Meta:
1025
+ model = GoogleCalendarProfile
1026
+ fields = "__all__"
1027
+
1028
+ def __init__(self, *args, **kwargs):
1029
+ super().__init__(*args, **kwargs)
1030
+ if self.instance.pk:
1031
+ self.fields["api_key"].initial = ""
1032
+ self.initial["api_key"] = ""
1033
+ else:
1034
+ self.fields["api_key"].required = True
1035
+
1036
+ def clean_api_key(self):
1037
+ key = self.cleaned_data.get("api_key")
1038
+ if not key and self.instance.pk:
1039
+ return keep_existing("api_key")
1040
+ return key
1041
+
1042
+ def _post_clean(self):
1043
+ super()._post_clean()
1044
+ _restore_sigil_values(
1045
+ self,
1046
+ ["calendar_id", "api_key", "display_name", "timezone"],
1047
+ )
1048
+
1049
+
1008
1050
  class MaskedPasswordFormMixin:
1009
1051
  """Mixin that hides stored passwords while allowing updates."""
1010
1052
 
@@ -1222,6 +1264,15 @@ class OpenPayProfileInlineForm(ProfileFormMixin, OpenPayProfileAdminForm):
1222
1264
  return cleaned
1223
1265
 
1224
1266
 
1267
+ class GoogleCalendarProfileInlineForm(
1268
+ ProfileFormMixin, GoogleCalendarProfileAdminForm
1269
+ ):
1270
+ profile_fields = GoogleCalendarProfile.profile_fields
1271
+
1272
+ class Meta(GoogleCalendarProfileAdminForm.Meta):
1273
+ exclude = ("user", "group")
1274
+
1275
+
1225
1276
  class EmailInboxInlineForm(ProfileFormMixin, EmailInboxAdminForm):
1226
1277
  profile_fields = EmailInbox.profile_fields
1227
1278
 
@@ -1346,6 +1397,16 @@ PROFILE_INLINE_CONFIG = {
1346
1397
  ),
1347
1398
  "readonly_fields": ("verified_on", "verification_reference"),
1348
1399
  },
1400
+ GoogleCalendarProfile: {
1401
+ "form": GoogleCalendarProfileInlineForm,
1402
+ "fields": (
1403
+ "display_name",
1404
+ "calendar_id",
1405
+ "api_key",
1406
+ "max_events",
1407
+ "timezone",
1408
+ ),
1409
+ },
1349
1410
  EmailInbox: {
1350
1411
  "form": EmailInboxInlineForm,
1351
1412
  "fields": (
@@ -1817,6 +1878,44 @@ class OpenPayProfileAdmin(ProfileAdminMixin, SaveBeforeChangeAction, EntityModel
1817
1878
  verify_credentials_action.short_description = _("Test credentials")
1818
1879
 
1819
1880
 
1881
+ class GoogleCalendarProfileAdmin(
1882
+ ProfileAdminMixin, SaveBeforeChangeAction, EntityModelAdmin
1883
+ ):
1884
+ form = GoogleCalendarProfileAdminForm
1885
+ list_display = ("owner", "calendar_identifier", "max_events")
1886
+ search_fields = (
1887
+ "display_name",
1888
+ "calendar_id",
1889
+ "user__username",
1890
+ "group__name",
1891
+ )
1892
+ changelist_actions = ["my_profile"]
1893
+ change_actions = ["my_profile_action"]
1894
+ fieldsets = (
1895
+ (_("Owner"), {"fields": ("user", "group")}),
1896
+ (
1897
+ _("Calendar"),
1898
+ {
1899
+ "fields": (
1900
+ "display_name",
1901
+ "calendar_id",
1902
+ "api_key",
1903
+ "max_events",
1904
+ "timezone",
1905
+ )
1906
+ },
1907
+ ),
1908
+ )
1909
+
1910
+ @admin.display(description=_("Owner"))
1911
+ def owner(self, obj):
1912
+ return obj.owner_display()
1913
+
1914
+ @admin.display(description=_("Calendar"))
1915
+ def calendar_identifier(self, obj):
1916
+ display = obj.get_display_name()
1917
+ return display or obj.resolved_calendar_id()
1918
+
1820
1919
  class EmailSearchForm(forms.Form):
1821
1920
  subject = forms.CharField(
1822
1921
  required=False, widget=forms.TextInput(attrs={"style": "width: 40em;"})
@@ -2416,8 +2515,30 @@ class ProductAdmin(EntityModelAdmin):
2416
2515
  ],
2417
2516
  limit=0,
2418
2517
  )
2419
- except Exception:
2518
+ except Exception as exc:
2519
+ logger.exception(
2520
+ "Failed to fetch Odoo products for user %s (profile_id=%s, host=%s, database=%s)",
2521
+ getattr(getattr(request, "user", None), "pk", None),
2522
+ getattr(profile, "pk", None),
2523
+ getattr(profile, "host", None),
2524
+ getattr(profile, "database", None),
2525
+ )
2420
2526
  context["error"] = _("Unable to fetch products from Odoo.")
2527
+ if getattr(request.user, "is_superuser", False):
2528
+ fault = getattr(exc, "faultString", "")
2529
+ message = str(exc)
2530
+ details = [
2531
+ f"Host: {getattr(profile, 'host', '')}",
2532
+ f"Database: {getattr(profile, 'database', '')}",
2533
+ f"User ID: {getattr(profile, 'odoo_uid', '')}",
2534
+ ]
2535
+ if fault and fault != message:
2536
+ details.append(f"Fault: {fault}")
2537
+ if message:
2538
+ details.append(f"Exception: {type(exc).__name__}: {message}")
2539
+ else:
2540
+ details.append(f"Exception type: {type(exc).__name__}")
2541
+ context["debug_error"] = "\n".join(details)
2421
2542
  return context, []
2422
2543
 
2423
2544
  context["has_credentials"] = True
@@ -2697,7 +2818,7 @@ class RFIDAdmin(EntityModelAdmin, ImportExportModelAdmin):
2697
2818
  "user_data_flag",
2698
2819
  "color",
2699
2820
  "kind",
2700
- "endianness",
2821
+ "endianness_short",
2701
2822
  "released",
2702
2823
  "allowed",
2703
2824
  "last_seen_on",
@@ -2762,6 +2883,14 @@ class RFIDAdmin(EntityModelAdmin, ImportExportModelAdmin):
2762
2883
  def user_data_flag(self, obj):
2763
2884
  return getattr(obj, "is_user_data", False)
2764
2885
 
2886
+ @admin.display(description=_("End"), ordering="endianness")
2887
+ def endianness_short(self, obj):
2888
+ labels = {
2889
+ RFID.BIG_ENDIAN: _("Big"),
2890
+ RFID.LITTLE_ENDIAN: _("Little"),
2891
+ }
2892
+ return labels.get(obj.endianness, obj.get_endianness_display())
2893
+
2765
2894
  def scan_rfids(self, request, queryset):
2766
2895
  return redirect("admin:core_rfid_scan")
2767
2896
 
@@ -3547,10 +3676,10 @@ class ClientReportAdmin(EntityModelAdmin):
3547
3676
  initial=ClientReportSchedule.PERIODICITY_NONE,
3548
3677
  help_text="Defines how often the report should be generated automatically.",
3549
3678
  )
3550
- disable_emails = forms.BooleanField(
3551
- label="Disable email delivery",
3679
+ enable_emails = forms.BooleanField(
3680
+ label="Enable email delivery",
3552
3681
  required=False,
3553
- help_text="Generate files without sending emails.",
3682
+ help_text="Send the report via email to the recipients listed above.",
3554
3683
  )
3555
3684
 
3556
3685
  def __init__(self, *args, request=None, **kwargs):
@@ -3614,6 +3743,11 @@ class ClientReportAdmin(EntityModelAdmin):
3614
3743
  self.admin_site.admin_view(self.generate_view),
3615
3744
  name="core_clientreport_generate",
3616
3745
  ),
3746
+ path(
3747
+ "download/<int:report_id>/",
3748
+ self.admin_site.admin_view(self.download_view),
3749
+ name="core_clientreport_download",
3750
+ ),
3617
3751
  ]
3618
3752
  return custom + urls
3619
3753
 
@@ -3621,16 +3755,20 @@ class ClientReportAdmin(EntityModelAdmin):
3621
3755
  form = self.ClientReportForm(request.POST or None, request=request)
3622
3756
  report = None
3623
3757
  schedule = None
3758
+ download_url = None
3624
3759
  if request.method == "POST" and form.is_valid():
3625
3760
  owner = form.cleaned_data.get("owner")
3626
3761
  if not owner and request.user.is_authenticated:
3627
3762
  owner = request.user
3763
+ enable_emails = form.cleaned_data.get("enable_emails", False)
3764
+ disable_emails = not enable_emails
3765
+ recipients = form.cleaned_data.get("destinations") if enable_emails else []
3628
3766
  report = ClientReport.generate(
3629
3767
  form.cleaned_data["start"],
3630
3768
  form.cleaned_data["end"],
3631
3769
  owner=owner,
3632
- recipients=form.cleaned_data.get("destinations"),
3633
- disable_emails=form.cleaned_data.get("disable_emails", False),
3770
+ recipients=recipients,
3771
+ disable_emails=disable_emails,
3634
3772
  )
3635
3773
  report.store_local_copy()
3636
3774
  recurrence = form.cleaned_data.get("recurrence")
@@ -3639,8 +3777,8 @@ class ClientReportAdmin(EntityModelAdmin):
3639
3777
  owner=owner,
3640
3778
  created_by=request.user if request.user.is_authenticated else None,
3641
3779
  periodicity=recurrence,
3642
- email_recipients=form.cleaned_data.get("destinations", []),
3643
- disable_emails=form.cleaned_data.get("disable_emails", False),
3780
+ email_recipients=recipients,
3781
+ disable_emails=disable_emails,
3644
3782
  )
3645
3783
  report.schedule = schedule
3646
3784
  report.save(update_fields=["schedule"])
@@ -3649,12 +3787,69 @@ class ClientReportAdmin(EntityModelAdmin):
3649
3787
  "Client report schedule created; future reports will be generated automatically.",
3650
3788
  messages.SUCCESS,
3651
3789
  )
3790
+ if disable_emails:
3791
+ self.message_user(
3792
+ request,
3793
+ "Consumer report generated. The download will begin automatically.",
3794
+ messages.SUCCESS,
3795
+ )
3796
+ redirect_url = f"{reverse('admin:core_clientreport_generate')}?download={report.pk}"
3797
+ return HttpResponseRedirect(redirect_url)
3798
+ download_param = request.GET.get("download")
3799
+ if download_param:
3800
+ try:
3801
+ download_report = ClientReport.objects.get(pk=download_param)
3802
+ except ClientReport.DoesNotExist:
3803
+ pass
3804
+ else:
3805
+ download_url = reverse(
3806
+ "admin:core_clientreport_download", args=[download_report.pk]
3807
+ )
3652
3808
  context = self.admin_site.each_context(request)
3653
- context.update({"form": form, "report": report, "schedule": schedule})
3809
+ context.update(
3810
+ {
3811
+ "form": form,
3812
+ "report": report,
3813
+ "schedule": schedule,
3814
+ "download_url": download_url,
3815
+ "previous_reports": self._build_report_history(request),
3816
+ }
3817
+ )
3654
3818
  return TemplateResponse(
3655
3819
  request, "admin/core/clientreport/generate.html", context
3656
3820
  )
3657
3821
 
3822
+ def download_view(self, request, report_id: int):
3823
+ report = get_object_or_404(ClientReport, pk=report_id)
3824
+ pdf_path = report.ensure_pdf()
3825
+ if not pdf_path.exists():
3826
+ raise Http404("Report file unavailable")
3827
+ filename = f"consumer-report-{report.start_date}-{report.end_date}.pdf"
3828
+ response = FileResponse(pdf_path.open("rb"), content_type="application/pdf")
3829
+ response["Content-Disposition"] = f'attachment; filename="{filename}"'
3830
+ return response
3831
+
3832
+ def _build_report_history(self, request):
3833
+ queryset = ClientReport.objects.order_by("-created_on")[:20]
3834
+ history = []
3835
+ for item in queryset:
3836
+ totals = item.rows_for_display.get("totals", {})
3837
+ history.append(
3838
+ {
3839
+ "instance": item,
3840
+ "download_url": reverse(
3841
+ "admin:core_clientreport_download", args=[item.pk]
3842
+ ),
3843
+ "email_enabled": not item.disable_emails,
3844
+ "recipients": item.recipients or [],
3845
+ "totals": {
3846
+ "total_kw": totals.get("total_kw", 0.0),
3847
+ "total_kw_period": totals.get("total_kw_period", 0.0),
3848
+ },
3849
+ }
3850
+ )
3851
+ return history
3852
+
3658
3853
 
3659
3854
  @admin.register(PackageRelease)
3660
3855
  class PackageReleaseAdmin(SaveBeforeChangeAction, EntityModelAdmin):
@@ -3715,9 +3910,9 @@ class PackageReleaseAdmin(SaveBeforeChangeAction, EntityModelAdmin):
3715
3910
  self.message_user(request, str(exc), messages.ERROR)
3716
3911
  return
3717
3912
  releases = resp.json().get("releases", {})
3718
- created = 0
3719
3913
  updated = 0
3720
3914
  restored = 0
3915
+ missing: list[str] = []
3721
3916
 
3722
3917
  for version, files in releases.items():
3723
3918
  release_on = self._release_on_from_files(files)
@@ -3742,23 +3937,11 @@ class PackageReleaseAdmin(SaveBeforeChangeAction, EntityModelAdmin):
3742
3937
  if update_fields:
3743
3938
  release.save(update_fields=update_fields)
3744
3939
  continue
3745
- PackageRelease.objects.create(
3746
- package=package,
3747
- release_manager=package.release_manager,
3748
- version=version,
3749
- revision="",
3750
- pypi_url=f"https://pypi.org/project/{package.name}/{version}/",
3751
- release_on=release_on,
3752
- )
3753
- created += 1
3940
+ missing.append(version)
3754
3941
 
3755
- if created or updated or restored:
3942
+ if updated or restored:
3756
3943
  PackageRelease.dump_fixture()
3757
3944
  message_parts = []
3758
- if created:
3759
- message_parts.append(
3760
- f"Created {created} release{'s' if created != 1 else ''} from PyPI"
3761
- )
3762
3945
  if updated:
3763
3946
  message_parts.append(
3764
3947
  f"Updated release date for {updated} release"
@@ -3769,8 +3952,17 @@ class PackageReleaseAdmin(SaveBeforeChangeAction, EntityModelAdmin):
3769
3952
  f"Restored {restored} release{'s' if restored != 1 else ''}"
3770
3953
  )
3771
3954
  self.message_user(request, "; ".join(message_parts), messages.SUCCESS)
3772
- else:
3773
- self.message_user(request, "No new releases found", messages.INFO)
3955
+ elif not missing:
3956
+ self.message_user(request, "No matching releases found", messages.INFO)
3957
+
3958
+ if missing:
3959
+ versions = ", ".join(sorted(missing))
3960
+ count = len(missing)
3961
+ message = (
3962
+ "Manual creation required for "
3963
+ f"{count} release{'s' if count != 1 else ''}: {versions}"
3964
+ )
3965
+ self.message_user(request, message, messages.WARNING)
3774
3966
 
3775
3967
  refresh_from_pypi.label = "Refresh from PyPI"
3776
3968
  refresh_from_pypi.short_description = "Refresh from PyPI"