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.
- {arthexis-0.1.21.dist-info → arthexis-0.1.23.dist-info}/METADATA +9 -8
- {arthexis-0.1.21.dist-info → arthexis-0.1.23.dist-info}/RECORD +33 -33
- config/settings.py +4 -0
- config/urls.py +5 -0
- core/admin.py +224 -32
- core/environment.py +2 -239
- core/models.py +903 -65
- core/release.py +0 -5
- core/system.py +76 -0
- core/tests.py +181 -9
- core/user_data.py +42 -2
- core/views.py +68 -27
- nodes/admin.py +211 -60
- nodes/apps.py +11 -0
- nodes/models.py +35 -7
- nodes/tests.py +288 -1
- nodes/views.py +101 -48
- ocpp/admin.py +32 -2
- ocpp/consumers.py +1 -0
- ocpp/models.py +52 -3
- ocpp/tasks.py +99 -1
- ocpp/tests.py +350 -2
- ocpp/views.py +300 -6
- pages/admin.py +112 -15
- pages/apps.py +32 -0
- pages/forms.py +31 -8
- pages/models.py +42 -2
- pages/tests.py +386 -28
- pages/urls.py +10 -0
- pages/views.py +347 -18
- {arthexis-0.1.21.dist-info → arthexis-0.1.23.dist-info}/WHEEL +0 -0
- {arthexis-0.1.21.dist-info → arthexis-0.1.23.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.21.dist-info → arthexis-0.1.23.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.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.
|
|
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==
|
|
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.
|
|
60
|
-
Requires-Dist: Markdown==3.
|
|
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.
|
|
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==
|
|
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.
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
54
|
-
core/views.py,sha256=
|
|
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=
|
|
58
|
-
nodes/apps.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
71
|
+
nodes/views.py,sha256=Z-bFzCLaP4w1l2NzqknmwsBMwuLSWRtzsbroNfA2vJU,39794
|
|
72
72
|
ocpp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
73
|
-
ocpp/admin.py,sha256=
|
|
73
|
+
ocpp/admin.py,sha256=O5dqd_bwK8k612ltr4Tt9xR3rs3VxxJXCTxarx0mW0w,40970
|
|
74
74
|
ocpp/apps.py,sha256=i3NqrmIamNEQBT33CIqh7HOSOPmJXCMKrZ-DUd3whqg,842
|
|
75
|
-
ocpp/consumers.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
90
|
+
ocpp/views.py,sha256=iwm2oEMN_d9mmjaD6Hh9axqBIpqw_qSWpMSzyHICrE4,77091
|
|
91
91
|
pages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
92
|
-
pages/admin.py,sha256=
|
|
93
|
-
pages/apps.py,sha256=
|
|
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=
|
|
97
|
+
pages/forms.py,sha256=r3JM5qp3_4RR01-u6XV8WDOaeiRe4OvCN8Y52FcsAwI,7909
|
|
98
98
|
pages/middleware.py,sha256=MYd5Nko4AnFg3orY6MuyvvNg_I6GCIf8mDW8znSOgvQ,7042
|
|
99
|
-
pages/models.py,sha256=
|
|
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=
|
|
104
|
-
pages/urls.py,sha256=
|
|
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=
|
|
107
|
-
arthexis-0.1.
|
|
108
|
-
arthexis-0.1.
|
|
109
|
-
arthexis-0.1.
|
|
110
|
-
arthexis-0.1.
|
|
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
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
3551
|
-
label="
|
|
3679
|
+
enable_emails = forms.BooleanField(
|
|
3680
|
+
label="Enable email delivery",
|
|
3552
3681
|
required=False,
|
|
3553
|
-
help_text="
|
|
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=
|
|
3633
|
-
disable_emails=
|
|
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=
|
|
3643
|
-
disable_emails=
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
3773
|
-
self.message_user(request, "No
|
|
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"
|