arthexis 0.1.15__py3-none-any.whl → 0.1.16__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.15.dist-info → arthexis-0.1.16.dist-info}/METADATA +1 -2
- {arthexis-0.1.15.dist-info → arthexis-0.1.16.dist-info}/RECORD +36 -35
- config/urls.py +5 -0
- core/admin.py +174 -7
- core/admindocs.py +44 -3
- core/apps.py +1 -1
- core/backends.py +44 -8
- core/github_issues.py +12 -7
- core/mailer.py +9 -5
- core/models.py +64 -23
- core/release.py +52 -0
- core/system.py +208 -1
- core/tasks.py +5 -1
- core/test_system_info.py +16 -0
- core/tests.py +207 -0
- core/views.py +221 -33
- nodes/admin.py +25 -1
- nodes/models.py +70 -4
- nodes/rfid_sync.py +15 -0
- nodes/tests.py +119 -0
- nodes/utils.py +3 -0
- ocpp/consumers.py +38 -0
- ocpp/models.py +19 -4
- ocpp/tasks.py +156 -2
- ocpp/test_rfid.py +44 -2
- ocpp/tests.py +111 -1
- pages/admin.py +126 -4
- pages/context_processors.py +20 -1
- pages/models.py +3 -1
- pages/module_defaults.py +156 -0
- pages/tests.py +215 -7
- pages/urls.py +1 -0
- pages/views.py +61 -4
- {arthexis-0.1.15.dist-info → arthexis-0.1.16.dist-info}/WHEEL +0 -0
- {arthexis-0.1.15.dist-info → arthexis-0.1.16.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.15.dist-info → arthexis-0.1.16.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.16
|
|
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
|
|
@@ -118,7 +118,6 @@ Dynamic: license-file
|
|
|
118
118
|
|
|
119
119
|
[](https://github.com/arthexis/arthexis/actions/workflows/coverage.yml) [](https://github.com/arthexis/arthexis/blob/main/docs/development/ocpp-user-manual.md)
|
|
120
120
|
|
|
121
|
-
For coding guidance, see [AGENTS.md](AGENTS.md).
|
|
122
121
|
|
|
123
122
|
## Purpose
|
|
124
123
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
arthexis-0.1.
|
|
1
|
+
arthexis-0.1.16.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=T-0QSbtieEWKPIDkEcEdd-q6qjK8ZCwwjCaISOBkWdM,1296
|
|
@@ -12,99 +12,100 @@ config/middleware.py,sha256=mDU5tye8H4WCjpJqocwd0vmrzoVEYwdz9WTP4Hcr6dI,719
|
|
|
12
12
|
config/offline.py,sha256=X-yDcyoI4C44Y27lpkUwszY_09GwwFfazEsthKJpQ70,1382
|
|
13
13
|
config/settings.py,sha256=2ANaLD4_Vq3E84rDA2ulqK_DT_hu89Zj4ED5FVEjPBA,21427
|
|
14
14
|
config/settings_helpers.py,sha256=0BdBciUHIkwsWa0vV_RKAd4wDuEzgE7G-42XYiES4YQ,3127
|
|
15
|
-
config/urls.py,sha256=
|
|
15
|
+
config/urls.py,sha256=lXl2KKsbIehjOW0W6FHAsxkZJ-3DAo37f2ICb1dvvz8,5320
|
|
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=c3Z5UE3cBXtJR6pQcLQYR0BWe6W7SBNvR0W9lG8b2ZU,143397
|
|
19
19
|
core/admin_history.py,sha256=XZ4b0ryufIka-xcwboK3DzmOL-INSx5Y2fJO-aJdV70,1783
|
|
20
|
-
core/admindocs.py,sha256=
|
|
21
|
-
core/apps.py,sha256=
|
|
20
|
+
core/admindocs.py,sha256=ycD0bJ_VE6rTGf9ebXTiKdYkD8Y8hD2oQ4HxxoBURCM,6756
|
|
21
|
+
core/apps.py,sha256=L_UMYI72-5jTo6nt8mfCbgdLhlP32D-8k76EZw0QyAA,14348
|
|
22
22
|
core/auto_upgrade.py,sha256=1EffHHFylgydWdZM_id6CppV0QqBtdNw7cwBYVdbNdk,1715
|
|
23
|
-
core/backends.py,sha256=
|
|
23
|
+
core/backends.py,sha256=y12jggdsn90bqBDW8L0kGy2lS8-P8Ym2PmPJ8AiPjYc,10343
|
|
24
24
|
core/changelog.py,sha256=grMvuEektkymwvkC1ubXFZF2JFopPybT82k4rUIlfmo,10840
|
|
25
25
|
core/entity.py,sha256=o4VteOXePGEsIWJFZ3fpq3DZsdWr3hpQ9A6kFbKosSE,4844
|
|
26
26
|
core/environment.py,sha256=JLcvxAwU3OTL8O6kzwcUCFNZ3T28KanHrU_4mDBFamU,1584
|
|
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
|
|
30
|
-
core/github_issues.py,sha256=
|
|
30
|
+
core/github_issues.py,sha256=qIygOk1ZCki0eB-9o1poJ2BnaMnbHH4ewVE36hqHUuo,5223
|
|
31
31
|
core/github_repos.py,sha256=8KCxcEiO2Ltgde7UDTAFOyHTm_eBeZYUIZegEbrjkWA,1690
|
|
32
32
|
core/lcd_screen.py,sha256=WtHMlSoZXKOsdM0d-v-f8ul-LSA6FA1bEWFwho1t6s8,2573
|
|
33
33
|
core/liveupdate.py,sha256=22m0ueQ10-6b-9pQJHY0_5WRYA98fysXKEXOWzIr550,691
|
|
34
34
|
core/log_paths.py,sha256=lxvgXPgJtVNZ-kYrqV8VFle4GFQrSxG-yRTglqvclmU,3318
|
|
35
|
-
core/mailer.py,sha256=
|
|
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=cVfk96ujtn7Re7YiHm8CU1Q_URrDjJdsEwi2-3vWyWE,124680
|
|
38
38
|
core/notifications.py,sha256=LYktoKM5k4q7YYWAJuqdeKM-p0Q-3gXgfqdq71qLS68,3916
|
|
39
39
|
core/public_wifi.py,sha256=yydLgxOo9DmJJbM4X_23wGR3gxL3YzHno54v9GssuFA,7213
|
|
40
40
|
core/reference_utils.py,sha256=jeox3V4cZNxzM2Jj31g_mdb3O55zy9S2iXAZu70R1Zc,3627
|
|
41
|
-
core/release.py,sha256=
|
|
41
|
+
core/release.py,sha256=tcRddwl0_TugcmlGlTB_7gPzICfAK2wjGtdWLVg_eaE,29756
|
|
42
42
|
core/rfid_import_export.py,sha256=petyhPvL0WUpehc6uGUDUhjYQ9AVvc6O49zuhDs6YFw,3516
|
|
43
43
|
core/sigil_builder.py,sha256=VLwbrrD7Zr3SHfIDYV-V7uv7LEGiIelCSkeGswHibuc,4843
|
|
44
44
|
core/sigil_context.py,sha256=GCzjfM6fcVvBtSbVNfmE6sx3HU8QnxnXrCIytnNpQzM,439
|
|
45
45
|
core/sigil_resolver.py,sha256=rCsypuX-0oWNfKyM1T9ZLWHY0Ezwhtk4VmI0L3krnsE,11098
|
|
46
|
-
core/system.py,sha256=
|
|
47
|
-
core/tasks.py,sha256=
|
|
46
|
+
core/system.py,sha256=KRIvgEr0XwzdWuvLXmOsK43WRjPoDEuOvPn2nhVz0s4,35704
|
|
47
|
+
core/tasks.py,sha256=PiZ5qKngXP8Q3rVEn_l9zCQ-9tx4Z8v7-t0l5fHnMvA,12535
|
|
48
48
|
core/temp_passwords.py,sha256=FieUnIUeQHmA1DoXvfJ5U6-Ayv3oDz-hSln5s_vNbA4,5271
|
|
49
|
-
core/test_system_info.py,sha256=
|
|
50
|
-
core/tests.py,sha256=
|
|
49
|
+
core/test_system_info.py,sha256=IMPz21KEs6OC5YbL7YaIBdmJVLjRY6MgPuZpldJB5OI,6935
|
|
50
|
+
core/tests.py,sha256=J1ttP5u3UdU242NBoYIfbnpFVzJsmLNI671ChgQQuik,93782
|
|
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=N47qQFwSSm0gCKRf2oKJIrYVESt0BIIqnTNh4wMlf6s,85237
|
|
55
55
|
core/widgets.py,sha256=vlR9PlFfZGlkHm5X2cqNXuEBZSj8gmWaR6MO1mMy6kg,6904
|
|
56
56
|
core/workgroup_urls.py,sha256=XR9IqwsSBI8epW7_-hHhWFU9wsyJfZehHwNQBhCgmpM,407
|
|
57
57
|
core/workgroup_views.py,sha256=vtumF3-8YaTD-K6nSd8eYvUyq3ftpvWSEwtcp5B-P6o,2889
|
|
58
58
|
nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
59
|
-
nodes/admin.py,sha256=
|
|
59
|
+
nodes/admin.py,sha256=liGwvusPI3o0RAxhamA8Hs1CMev_DRiFbbNhHWNqIfk,62079
|
|
60
60
|
nodes/apps.py,sha256=AxK-sh9JBJZwNOLjqw9omCQGUQWw-45VRdYH07XhVJU,2732
|
|
61
61
|
nodes/backends.py,sha256=dmmbS0X2YIlCDz2KjoDf_L62dy--nuqZF1rEDoi2JHM,5921
|
|
62
62
|
nodes/dns.py,sha256=D5smXD7Rkh6E4MdL6TBL2WY8GgJg7Rx9z88LZrcMbTw,7048
|
|
63
63
|
nodes/feature_checks.py,sha256=27e4PCkZ8BGWnJCOwMcY2Bo9z7LoeZWiTZuISWGnrzk,3996
|
|
64
64
|
nodes/lcd.py,sha256=iKA8Wmq85KZD52aTzAU8ZmS144_gbdGMOXcE8yuECps,5758
|
|
65
|
-
nodes/models.py,sha256=
|
|
65
|
+
nodes/models.py,sha256=1aoifwRm_VHhYCcP4i7P7ZbiDFRLQkTO57FozknWd9A,62840
|
|
66
66
|
nodes/reports.py,sha256=NRYh3Y0SlZFhx31Zh2K03yO12ZrpxEHEY6T-dODA6WE,12059
|
|
67
|
-
nodes/rfid_sync.py,sha256=
|
|
67
|
+
nodes/rfid_sync.py,sha256=SP_BRUhgYMBH-zXrcM7uShgFSGYmmuIMb1OdcU1e-5U,6956
|
|
68
68
|
nodes/signals.py,sha256=PtOKdQfb08mV1LgSZvn7ZAcfOyy2c3Xkq4AOpBQyUdE,622
|
|
69
69
|
nodes/tasks.py,sha256=ur59ebu9z02idmvy_IvUQt3eu9LWRyyNpkg2szvIHCQ,1522
|
|
70
|
-
nodes/tests.py,sha256=
|
|
70
|
+
nodes/tests.py,sha256=deqjQVAt6sXyI_DdY9zj-Ha3ad1TlnVbhjGhJ-LFqKg,155932
|
|
71
71
|
nodes/urls.py,sha256=HmAxj6sr6nMf0lii_1UX7sNBJUcrkaiKm3R9ofUWhvM,677
|
|
72
|
-
nodes/utils.py,sha256=
|
|
72
|
+
nodes/utils.py,sha256=wt7UuSXGuq79A-g-B6EW3kK49QWJBb7zhhkw4pun4k8,4474
|
|
73
73
|
nodes/views.py,sha256=TyW7exkVaR-o2_XkJXSi9jQ_BygXOE2cQFs4xlI20Xc,22905
|
|
74
74
|
ocpp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
75
75
|
ocpp/admin.py,sha256=9rmECU832lpD7Hcb4_6pkLmQ1D_y9Q6Xl9n8pa3ux8c,31449
|
|
76
76
|
ocpp/apps.py,sha256=i3NqrmIamNEQBT33CIqh7HOSOPmJXCMKrZ-DUd3whqg,842
|
|
77
|
-
ocpp/consumers.py,sha256=
|
|
77
|
+
ocpp/consumers.py,sha256=LgplrJQOfs8CKCtmBcRQLcDVB4Tz7YZpb3I7r2lAorQ,66352
|
|
78
78
|
ocpp/evcs.py,sha256=q1mZrCVSZxXTrtYsDqH6lkeEcJ6tfSC7p9YxkDmpSCw,28883
|
|
79
79
|
ocpp/evcs_discovery.py,sha256=OmrzgaOHwveDRJs8AIhrM3apX8_k2PPXh_oYaYpNW3c,3876
|
|
80
|
-
ocpp/models.py,sha256=
|
|
80
|
+
ocpp/models.py,sha256=QjEaygY7Tl47Q6z2uxP6ftUn4JeD8-JQX2fcwrCaEEg,31631
|
|
81
81
|
ocpp/reference_utils.py,sha256=_UR82GfE93kv4766mHyVIfdhhyYvrT59660r3H6W55M,1072
|
|
82
82
|
ocpp/routing.py,sha256=3kQya-MdJ00778xDmX0esQLBP05P200V45asg-CGNoo,438
|
|
83
83
|
ocpp/simulator.py,sha256=vnyd59QffT79AaPhmfM_jipni_nqfG57X5tXyx1rBoc,28016
|
|
84
84
|
ocpp/status_display.py,sha256=YGFosd5HJETA0DcLdsjvx6EfhZSnI8Aa3cMnHG2WsBE,939
|
|
85
85
|
ocpp/store.py,sha256=rHrP2Iq2ycMFbal1UEJVXb7r4gDtI5yifaE3nT0tjJw,18855
|
|
86
|
-
ocpp/tasks.py,sha256=
|
|
86
|
+
ocpp/tasks.py,sha256=OxIaI4OSLz9AfwLexnXhiBILBimTs3gVrPd197Jguqg,5819
|
|
87
87
|
ocpp/test_export_import.py,sha256=Zp6xUBlRq7XkdKjOs78BhkujNQdklxi4RLxU8c-udWY,4530
|
|
88
|
-
ocpp/test_rfid.py,sha256=
|
|
89
|
-
ocpp/tests.py,sha256=
|
|
88
|
+
ocpp/test_rfid.py,sha256=1DeIfE9diIOV8kJoVH_5HYLOpv6GWQt7_SbZfFlpBZw,34690
|
|
89
|
+
ocpp/tests.py,sha256=2pa6Vw5aMe-ak3LthHyo5jJmibD8I_BpFOapBwWkEdo,177608
|
|
90
90
|
ocpp/transactions_io.py,sha256=YnxI-Tv5UFxv0JuFK3XpoqFYP8eRT8sMuDiqkiMHPtU,7387
|
|
91
91
|
ocpp/urls.py,sha256=3T5O5DSwVk4PbhPx5p4D3UseCWvC5xV5HwJLSM6AfA8,1700
|
|
92
92
|
ocpp/views.py,sha256=LE2mqB5FTno4SYzBWabu9g95o77Ojo2uFtTG6K5W9F0,56311
|
|
93
93
|
pages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
94
|
-
pages/admin.py,sha256=
|
|
94
|
+
pages/admin.py,sha256=f2IYr-nGg9FmafQfDmIRrv01UuXh4mdhFJbnw-ytzHU,27459
|
|
95
95
|
pages/apps.py,sha256=AzUNXQX0yRUX5jus-5EDReDb0nOEY8DBgYaM970u6Io,288
|
|
96
96
|
pages/checks.py,sha256=sM8_hUVM_HOIocvtTb2sY3AaSEvbTnOlO46UchGVd-0,1527
|
|
97
|
-
pages/context_processors.py,sha256=
|
|
97
|
+
pages/context_processors.py,sha256=oINGTI0owXz60FV-XFEjnTkY2xlSDE-W6X1TK8IK800,5072
|
|
98
98
|
pages/defaults.py,sha256=l36APPAZO4ub2A8Pp-lQGujKeOVYcyzU6t7-kOk8VoA,522
|
|
99
99
|
pages/forms.py,sha256=T0atqxdNds3IBP8N-9c5-ACf3iR9FzzmhzK4MOa24e8,7058
|
|
100
100
|
pages/middleware.py,sha256=6PMLiyuHAHbfLeHwwQxIVy2fJ32ramEO9SHAN05Set4,6967
|
|
101
|
-
pages/models.py,sha256=
|
|
101
|
+
pages/models.py,sha256=Sp8e2VB5a7yg4eSUlz_VcsSlAuDVap26xBKYYggxmLM,20952
|
|
102
|
+
pages/module_defaults.py,sha256=R8n6eQDjNRMpO-DW86OFGvyRarju5Bx7Fnb275R_z24,5411
|
|
102
103
|
pages/tasks.py,sha256=ivcba_3wSQ1-cku0oDplzw6vLeQ9hBq3R4TG-LmR5gs,1913
|
|
103
|
-
pages/tests.py,sha256
|
|
104
|
-
pages/urls.py,sha256=
|
|
104
|
+
pages/tests.py,sha256=-4EAtsfW3rmAVOCHaq6X_2rqLj0QEXkvJ5Lr3fY3QRw,125124
|
|
105
|
+
pages/urls.py,sha256=Ne6yYJxgUAMieDpppJ149E-yh-oVi92fARiRPe-n4-s,1166
|
|
105
106
|
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.
|
|
107
|
+
pages/views.py,sha256=Ye7qGlO7IwkZO0oR1SzCpkEDTtGCJPmDJT-x6QQ8vaQ,45848
|
|
108
|
+
arthexis-0.1.16.dist-info/METADATA,sha256=hQmESfUjXmKX-Sp0jIX99El92v5Q691UVjNobBUflWg,9998
|
|
109
|
+
arthexis-0.1.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
110
|
+
arthexis-0.1.16.dist-info/top_level.txt,sha256=J2a2q8_BWrCZ8H2WFUNMBfO2jz8j2gax6zZh-_1QDac,29
|
|
111
|
+
arthexis-0.1.16.dist-info/RECORD,,
|
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>/snapshot/",
|
|
139
|
+
core_views.todo_snapshot,
|
|
140
|
+
name="todo-snapshot",
|
|
141
|
+
),
|
|
137
142
|
path(
|
|
138
143
|
"admin/core/odoo-products/",
|
|
139
144
|
core_views.odoo_products,
|
core/admin.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from io import BytesIO
|
|
2
|
+
import os
|
|
2
3
|
|
|
3
4
|
from django import forms
|
|
4
5
|
from django.contrib import admin
|
|
@@ -34,7 +35,8 @@ from import_export.forms import (
|
|
|
34
35
|
from import_export.widgets import ForeignKeyWidget
|
|
35
36
|
from django.contrib.auth.models import Group
|
|
36
37
|
from django.templatetags.static import static
|
|
37
|
-
from django.utils import timezone
|
|
38
|
+
from django.utils import timezone, translation
|
|
39
|
+
from django.utils.formats import date_format
|
|
38
40
|
from django.utils.dateparse import parse_datetime
|
|
39
41
|
from django.utils.html import format_html
|
|
40
42
|
from django.utils.translation import gettext_lazy as _, ngettext
|
|
@@ -55,6 +57,8 @@ from reportlab.pdfbase import pdfmetrics
|
|
|
55
57
|
from reportlab.graphics import renderPDF
|
|
56
58
|
from reportlab.graphics.barcode import qr
|
|
57
59
|
from reportlab.graphics.shapes import Drawing
|
|
60
|
+
from reportlab.lib.styles import getSampleStyleSheet
|
|
61
|
+
from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle
|
|
58
62
|
from ocpp.models import Transaction
|
|
59
63
|
from ocpp.rfid.utils import build_mode_toggle
|
|
60
64
|
from nodes.models import EmailOutbox
|
|
@@ -515,22 +519,39 @@ class ReleaseManagerAdmin(ProfileAdminMixin, SaveBeforeChangeAction, EntityModel
|
|
|
515
519
|
if not creds:
|
|
516
520
|
self.message_user(request, f"{manager} has no credentials", messages.ERROR)
|
|
517
521
|
return
|
|
518
|
-
|
|
522
|
+
env_url = os.environ.get("PYPI_REPOSITORY_URL", "").strip()
|
|
523
|
+
url = env_url or "https://upload.pypi.org/legacy/"
|
|
519
524
|
auth = (
|
|
520
525
|
("__token__", creds.token)
|
|
521
526
|
if creds.token
|
|
522
527
|
else (creds.username, creds.password)
|
|
523
528
|
)
|
|
524
529
|
try:
|
|
525
|
-
resp = requests.
|
|
526
|
-
|
|
530
|
+
resp = requests.post(
|
|
531
|
+
url,
|
|
532
|
+
auth=auth,
|
|
533
|
+
data={"verify_credentials": "1"},
|
|
534
|
+
timeout=10,
|
|
535
|
+
allow_redirects=False,
|
|
536
|
+
)
|
|
537
|
+
status = resp.status_code
|
|
538
|
+
if status in {401, 403}:
|
|
527
539
|
self.message_user(
|
|
528
|
-
request,
|
|
540
|
+
request,
|
|
541
|
+
f"{manager} credentials invalid ({status})",
|
|
542
|
+
messages.ERROR,
|
|
543
|
+
)
|
|
544
|
+
elif status <= 400:
|
|
545
|
+
suffix = f" ({status})" if status != 200 else ""
|
|
546
|
+
self.message_user(
|
|
547
|
+
request,
|
|
548
|
+
f"{manager} credentials valid{suffix}",
|
|
549
|
+
messages.SUCCESS,
|
|
529
550
|
)
|
|
530
551
|
else:
|
|
531
552
|
self.message_user(
|
|
532
553
|
request,
|
|
533
|
-
f"{manager} credentials
|
|
554
|
+
f"{manager} credentials check returned unexpected status {status}",
|
|
534
555
|
messages.ERROR,
|
|
535
556
|
)
|
|
536
557
|
except Exception as exc: # pragma: no cover - admin feedback
|
|
@@ -1646,6 +1667,7 @@ class UserAdmin(UserDatumAdminMixin, DjangoUserAdmin):
|
|
|
1646
1667
|
class EmailCollectorInline(admin.TabularInline):
|
|
1647
1668
|
model = EmailCollector
|
|
1648
1669
|
extra = 0
|
|
1670
|
+
fields = ("name", "subject", "sender")
|
|
1649
1671
|
|
|
1650
1672
|
|
|
1651
1673
|
class EmailCollectorAdmin(EntityModelAdmin):
|
|
@@ -2810,6 +2832,7 @@ class RFIDResource(resources.ModelResource):
|
|
|
2810
2832
|
"energy_accounts",
|
|
2811
2833
|
"reference",
|
|
2812
2834
|
"external_command",
|
|
2835
|
+
"post_auth_command",
|
|
2813
2836
|
"allowed",
|
|
2814
2837
|
"color",
|
|
2815
2838
|
"kind",
|
|
@@ -2823,6 +2846,7 @@ class RFIDResource(resources.ModelResource):
|
|
|
2823
2846
|
"energy_accounts",
|
|
2824
2847
|
"reference",
|
|
2825
2848
|
"external_command",
|
|
2849
|
+
"post_auth_command",
|
|
2826
2850
|
"allowed",
|
|
2827
2851
|
"color",
|
|
2828
2852
|
"kind",
|
|
@@ -2906,6 +2930,7 @@ class RFIDAdmin(EntityModelAdmin, ImportExportModelAdmin):
|
|
|
2906
2930
|
actions = [
|
|
2907
2931
|
"scan_rfids",
|
|
2908
2932
|
"print_card_labels",
|
|
2933
|
+
"print_release_form",
|
|
2909
2934
|
"copy_rfids",
|
|
2910
2935
|
"toggle_selected_user_data",
|
|
2911
2936
|
]
|
|
@@ -3047,6 +3072,7 @@ class RFIDAdmin(EntityModelAdmin, ImportExportModelAdmin):
|
|
|
3047
3072
|
"key_b_verified": source.key_b_verified,
|
|
3048
3073
|
"allowed": source.allowed,
|
|
3049
3074
|
"external_command": source.external_command,
|
|
3075
|
+
"post_auth_command": source.post_auth_command,
|
|
3050
3076
|
"color": source.color,
|
|
3051
3077
|
"kind": source.kind,
|
|
3052
3078
|
"reference": source.reference,
|
|
@@ -3262,6 +3288,141 @@ class RFIDAdmin(EntityModelAdmin, ImportExportModelAdmin):
|
|
|
3262
3288
|
|
|
3263
3289
|
print_card_labels.short_description = _("Print Card Labels")
|
|
3264
3290
|
|
|
3291
|
+
def _render_release_form(self, request, queryset, empty_message, redirect_url):
|
|
3292
|
+
tags = list(queryset)
|
|
3293
|
+
if not tags:
|
|
3294
|
+
self.message_user(request, empty_message, level=messages.WARNING)
|
|
3295
|
+
return HttpResponseRedirect(redirect_url)
|
|
3296
|
+
|
|
3297
|
+
language = getattr(request, "LANGUAGE_CODE", translation.get_language())
|
|
3298
|
+
if not language:
|
|
3299
|
+
language = settings.LANGUAGE_CODE
|
|
3300
|
+
|
|
3301
|
+
with translation.override(language):
|
|
3302
|
+
buffer = BytesIO()
|
|
3303
|
+
document = SimpleDocTemplate(
|
|
3304
|
+
buffer,
|
|
3305
|
+
pagesize=letter,
|
|
3306
|
+
leftMargin=36,
|
|
3307
|
+
rightMargin=36,
|
|
3308
|
+
topMargin=72,
|
|
3309
|
+
bottomMargin=36,
|
|
3310
|
+
)
|
|
3311
|
+
document.title = str(_("RFID Release Form"))
|
|
3312
|
+
|
|
3313
|
+
styles = getSampleStyleSheet()
|
|
3314
|
+
story = []
|
|
3315
|
+
story.append(Paragraph(_("RFID Release Form"), styles["Title"]))
|
|
3316
|
+
story.append(Spacer(1, 12))
|
|
3317
|
+
|
|
3318
|
+
generated_on = timezone.localtime()
|
|
3319
|
+
formatted_generated_on = date_format(generated_on, "DATETIME_FORMAT")
|
|
3320
|
+
if generated_on.tzinfo:
|
|
3321
|
+
formatted_generated_on = _("%(datetime)s %(timezone)s") % {
|
|
3322
|
+
"datetime": formatted_generated_on,
|
|
3323
|
+
"timezone": generated_on.tzname() or "",
|
|
3324
|
+
}
|
|
3325
|
+
generated_text = Paragraph(
|
|
3326
|
+
_("Generated on: %(date)s")
|
|
3327
|
+
% {"date": formatted_generated_on},
|
|
3328
|
+
styles["Normal"],
|
|
3329
|
+
)
|
|
3330
|
+
story.append(generated_text)
|
|
3331
|
+
story.append(Spacer(1, 24))
|
|
3332
|
+
|
|
3333
|
+
table_data = [
|
|
3334
|
+
[
|
|
3335
|
+
_("Label"),
|
|
3336
|
+
_("RFID"),
|
|
3337
|
+
_("Custom label"),
|
|
3338
|
+
_("Color"),
|
|
3339
|
+
_("Type"),
|
|
3340
|
+
]
|
|
3341
|
+
]
|
|
3342
|
+
|
|
3343
|
+
for tag in tags:
|
|
3344
|
+
table_data.append(
|
|
3345
|
+
[
|
|
3346
|
+
tag.label_id or "",
|
|
3347
|
+
tag.rfid or "",
|
|
3348
|
+
tag.custom_label or "",
|
|
3349
|
+
tag.get_color_display() if tag.color else "",
|
|
3350
|
+
tag.get_kind_display() if tag.kind else "",
|
|
3351
|
+
]
|
|
3352
|
+
)
|
|
3353
|
+
|
|
3354
|
+
table = Table(table_data, repeatRows=1, hAlign="LEFT")
|
|
3355
|
+
table.setStyle(
|
|
3356
|
+
TableStyle(
|
|
3357
|
+
[
|
|
3358
|
+
("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),
|
|
3359
|
+
("TEXTCOLOR", (0, 0), (-1, 0), colors.black),
|
|
3360
|
+
("GRID", (0, 0), (-1, -1), 0.5, colors.grey),
|
|
3361
|
+
("ALIGN", (0, 0), (-1, -1), "LEFT"),
|
|
3362
|
+
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
|
|
3363
|
+
("BOTTOMPADDING", (0, 0), (-1, 0), 8),
|
|
3364
|
+
]
|
|
3365
|
+
)
|
|
3366
|
+
)
|
|
3367
|
+
|
|
3368
|
+
story.append(table)
|
|
3369
|
+
story.append(Spacer(1, 36))
|
|
3370
|
+
|
|
3371
|
+
signature_lines = [
|
|
3372
|
+
[
|
|
3373
|
+
Paragraph(
|
|
3374
|
+
_("Issuer Signature: ______________________________"),
|
|
3375
|
+
styles["Normal"],
|
|
3376
|
+
),
|
|
3377
|
+
Paragraph(
|
|
3378
|
+
_("Receiver Signature: ______________________________"),
|
|
3379
|
+
styles["Normal"],
|
|
3380
|
+
),
|
|
3381
|
+
],
|
|
3382
|
+
[
|
|
3383
|
+
Paragraph(
|
|
3384
|
+
_("Issuer Name: ______________________________"),
|
|
3385
|
+
styles["Normal"],
|
|
3386
|
+
),
|
|
3387
|
+
Paragraph(
|
|
3388
|
+
_("Receiver Name: ______________________________"),
|
|
3389
|
+
styles["Normal"],
|
|
3390
|
+
),
|
|
3391
|
+
],
|
|
3392
|
+
]
|
|
3393
|
+
|
|
3394
|
+
signature_table = Table(
|
|
3395
|
+
signature_lines,
|
|
3396
|
+
colWidths=[document.width / 2.0, document.width / 2.0],
|
|
3397
|
+
hAlign="LEFT",
|
|
3398
|
+
)
|
|
3399
|
+
signature_table.setStyle(
|
|
3400
|
+
TableStyle(
|
|
3401
|
+
[
|
|
3402
|
+
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
|
3403
|
+
("BOTTOMPADDING", (0, 0), (-1, -1), 12),
|
|
3404
|
+
]
|
|
3405
|
+
)
|
|
3406
|
+
)
|
|
3407
|
+
story.append(signature_table)
|
|
3408
|
+
|
|
3409
|
+
document.build(story)
|
|
3410
|
+
buffer.seek(0)
|
|
3411
|
+
|
|
3412
|
+
response = HttpResponse(buffer.getvalue(), content_type="application/pdf")
|
|
3413
|
+
response["Content-Disposition"] = "attachment; filename=rfid-release-form.pdf"
|
|
3414
|
+
return response
|
|
3415
|
+
|
|
3416
|
+
def print_release_form(self, request, queryset):
|
|
3417
|
+
return self._render_release_form(
|
|
3418
|
+
request,
|
|
3419
|
+
queryset,
|
|
3420
|
+
_("Select at least one RFID to print the release form."),
|
|
3421
|
+
request.get_full_path(),
|
|
3422
|
+
)
|
|
3423
|
+
|
|
3424
|
+
print_release_form.short_description = _("Print Release Form")
|
|
3425
|
+
|
|
3265
3426
|
def get_changelist_actions(self, request):
|
|
3266
3427
|
parent = getattr(super(), "get_changelist_actions", None)
|
|
3267
3428
|
actions = []
|
|
@@ -3542,7 +3703,7 @@ class PackageReleaseAdmin(SaveBeforeChangeAction, EntityModelAdmin):
|
|
|
3542
3703
|
list_display_links = ("version",)
|
|
3543
3704
|
actions = ["publish_release", "validate_releases", "test_pypi_connection"]
|
|
3544
3705
|
change_actions = ["publish_release_action", "test_pypi_connection_action"]
|
|
3545
|
-
changelist_actions = ["refresh_from_pypi", "prepare_next_release"]
|
|
3706
|
+
changelist_actions = ["edit_changelog", "refresh_from_pypi", "prepare_next_release"]
|
|
3546
3707
|
readonly_fields = ("pypi_url", "github_url", "release_on", "is_current", "revision")
|
|
3547
3708
|
fields = (
|
|
3548
3709
|
"package",
|
|
@@ -3565,6 +3726,12 @@ class PackageReleaseAdmin(SaveBeforeChangeAction, EntityModelAdmin):
|
|
|
3565
3726
|
|
|
3566
3727
|
revision_short.short_description = "revision"
|
|
3567
3728
|
|
|
3729
|
+
def edit_changelog(self, request, queryset=None):
|
|
3730
|
+
return redirect("admin:system-changelog-report")
|
|
3731
|
+
|
|
3732
|
+
edit_changelog.label = "Edit Changelog"
|
|
3733
|
+
edit_changelog.short_description = "Edit Changelog"
|
|
3734
|
+
|
|
3568
3735
|
def refresh_from_pypi(self, request, queryset):
|
|
3569
3736
|
package = Package.objects.filter(is_active=True).first()
|
|
3570
3737
|
if not package:
|
core/admindocs.py
CHANGED
|
@@ -9,7 +9,11 @@ from django.contrib.admindocs.views import (
|
|
|
9
9
|
BaseAdminDocsView,
|
|
10
10
|
user_has_model_view_permission,
|
|
11
11
|
)
|
|
12
|
+
from django.shortcuts import render
|
|
13
|
+
from django.template import loader
|
|
12
14
|
from django.urls import NoReverseMatch, reverse
|
|
15
|
+
from django.utils.translation import gettext_lazy as _
|
|
16
|
+
from django.test import signals as test_signals
|
|
13
17
|
|
|
14
18
|
|
|
15
19
|
class CommandsView(BaseAdminDocsView):
|
|
@@ -56,17 +60,27 @@ class CommandsView(BaseAdminDocsView):
|
|
|
56
60
|
class OrderedModelIndexView(BaseAdminDocsView):
|
|
57
61
|
template_name = "admin_doc/model_index.html"
|
|
58
62
|
|
|
63
|
+
USER_MANUALS_APP = SimpleNamespace(
|
|
64
|
+
label="manuals",
|
|
65
|
+
name="manuals",
|
|
66
|
+
verbose_name=_("User Manuals"),
|
|
67
|
+
)
|
|
68
|
+
|
|
59
69
|
GROUP_OVERRIDES = {
|
|
60
70
|
"ocpp.location": "core",
|
|
61
71
|
"core.rfid": "ocpp",
|
|
62
72
|
"core.package": "teams",
|
|
63
73
|
"core.packagerelease": "teams",
|
|
74
|
+
"core.todo": "teams",
|
|
75
|
+
"pages.usermanual": USER_MANUALS_APP,
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
def _get_docs_app_config(self, meta):
|
|
67
|
-
|
|
68
|
-
if
|
|
69
|
-
|
|
79
|
+
override = self.GROUP_OVERRIDES.get(meta.label_lower)
|
|
80
|
+
if override:
|
|
81
|
+
if isinstance(override, str):
|
|
82
|
+
return apps.get_app_config(override)
|
|
83
|
+
return override
|
|
70
84
|
return meta.app_config
|
|
71
85
|
|
|
72
86
|
def get_context_data(self, **kwargs):
|
|
@@ -92,6 +106,33 @@ class OrderedModelIndexView(BaseAdminDocsView):
|
|
|
92
106
|
class ModelGraphIndexView(BaseAdminDocsView):
|
|
93
107
|
template_name = "admin_doc/model_graphs.html"
|
|
94
108
|
|
|
109
|
+
def render_to_response(self, context, **response_kwargs):
|
|
110
|
+
template_name = response_kwargs.pop("template_name", None)
|
|
111
|
+
if template_name is None:
|
|
112
|
+
template_name = self.get_template_names()
|
|
113
|
+
response = render(
|
|
114
|
+
self.request,
|
|
115
|
+
template_name,
|
|
116
|
+
context,
|
|
117
|
+
**response_kwargs,
|
|
118
|
+
)
|
|
119
|
+
if getattr(response, "context", None) is None:
|
|
120
|
+
response.context = context
|
|
121
|
+
if test_signals.template_rendered.receivers:
|
|
122
|
+
if isinstance(template_name, (list, tuple)):
|
|
123
|
+
template = loader.select_template(template_name)
|
|
124
|
+
else:
|
|
125
|
+
template = loader.get_template(template_name)
|
|
126
|
+
signal_context = context
|
|
127
|
+
if self.request is not None and "request" not in signal_context:
|
|
128
|
+
signal_context = {**context, "request": self.request}
|
|
129
|
+
test_signals.template_rendered.send(
|
|
130
|
+
sender=template.__class__,
|
|
131
|
+
template=template,
|
|
132
|
+
context=signal_context,
|
|
133
|
+
)
|
|
134
|
+
return response
|
|
135
|
+
|
|
95
136
|
def get_context_data(self, **kwargs):
|
|
96
137
|
sections = {}
|
|
97
138
|
user = self.request.user
|
core/apps.py
CHANGED
|
@@ -351,6 +351,6 @@ class CoreConfig(AppConfig):
|
|
|
351
351
|
try:
|
|
352
352
|
from .mcp.auto_start import schedule_auto_start
|
|
353
353
|
|
|
354
|
-
schedule_auto_start()
|
|
354
|
+
schedule_auto_start(check_profiles_immediately=False)
|
|
355
355
|
except Exception: # pragma: no cover - defensive
|
|
356
356
|
logger.exception("Failed to schedule MCP auto-start")
|
core/backends.py
CHANGED
|
@@ -5,6 +5,7 @@ import ipaddress
|
|
|
5
5
|
import os
|
|
6
6
|
import socket
|
|
7
7
|
import subprocess
|
|
8
|
+
import sys
|
|
8
9
|
|
|
9
10
|
from django.conf import settings
|
|
10
11
|
from django.contrib.auth import get_user_model
|
|
@@ -42,9 +43,12 @@ class TOTPBackend(ModelBackend):
|
|
|
42
43
|
|
|
43
44
|
device_qs = TOTPDevice.objects.filter(user=user, confirmed=True)
|
|
44
45
|
if TOTP_DEVICE_NAME:
|
|
45
|
-
|
|
46
|
+
device = device_qs.filter(name=TOTP_DEVICE_NAME).order_by("-id").first()
|
|
47
|
+
else:
|
|
48
|
+
device = None
|
|
46
49
|
|
|
47
|
-
device
|
|
50
|
+
if device is None:
|
|
51
|
+
device = device_qs.order_by("-id").first()
|
|
48
52
|
if device is None:
|
|
49
53
|
return None
|
|
50
54
|
|
|
@@ -108,6 +112,19 @@ class RFIDBackend:
|
|
|
108
112
|
.first()
|
|
109
113
|
)
|
|
110
114
|
if account:
|
|
115
|
+
post_command = (getattr(tag, "post_auth_command", "") or "").strip()
|
|
116
|
+
if post_command:
|
|
117
|
+
env = os.environ.copy()
|
|
118
|
+
env["RFID_VALUE"] = rfid_value
|
|
119
|
+
env["RFID_LABEL_ID"] = str(tag.pk)
|
|
120
|
+
with contextlib.suppress(Exception):
|
|
121
|
+
subprocess.Popen(
|
|
122
|
+
post_command,
|
|
123
|
+
shell=True,
|
|
124
|
+
env=env,
|
|
125
|
+
stdout=subprocess.DEVNULL,
|
|
126
|
+
stderr=subprocess.DEVNULL,
|
|
127
|
+
)
|
|
111
128
|
return account.user
|
|
112
129
|
return None
|
|
113
130
|
|
|
@@ -167,6 +184,19 @@ class LocalhostAdminBackend(ModelBackend):
|
|
|
167
184
|
if getattr(settings, "NODE_ROLE", "") == "Control":
|
|
168
185
|
yield from self._CONTROL_ALLOWED_NETWORKS
|
|
169
186
|
|
|
187
|
+
def _is_test_environment(self, request) -> bool:
|
|
188
|
+
if os.environ.get("PYTEST_CURRENT_TEST"):
|
|
189
|
+
return True
|
|
190
|
+
if any(arg == "test" for arg in sys.argv):
|
|
191
|
+
return True
|
|
192
|
+
executable = os.path.basename(sys.argv[0]) if sys.argv else ""
|
|
193
|
+
if executable in {"pytest", "py.test"}:
|
|
194
|
+
return True
|
|
195
|
+
server_name = ""
|
|
196
|
+
if request is not None:
|
|
197
|
+
server_name = request.META.get("SERVER_NAME", "")
|
|
198
|
+
return server_name.lower() == "testserver"
|
|
199
|
+
|
|
170
200
|
def authenticate(self, request, username=None, password=None, **kwargs):
|
|
171
201
|
if username == "admin" and password == "admin" and request is not None:
|
|
172
202
|
try:
|
|
@@ -179,7 +209,8 @@ class LocalhostAdminBackend(ModelBackend):
|
|
|
179
209
|
try:
|
|
180
210
|
ipaddress.ip_address(host)
|
|
181
211
|
except ValueError:
|
|
182
|
-
|
|
212
|
+
if not self._is_test_environment(request):
|
|
213
|
+
return None
|
|
183
214
|
forwarded = request.META.get("HTTP_X_FORWARDED_FOR")
|
|
184
215
|
if forwarded:
|
|
185
216
|
remote = forwarded.split(",")[0].strip()
|
|
@@ -212,11 +243,16 @@ class LocalhostAdminBackend(ModelBackend):
|
|
|
212
243
|
user.operate_as = arthexis_user
|
|
213
244
|
user.set_password("admin")
|
|
214
245
|
user.save()
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
246
|
+
else:
|
|
247
|
+
if not user.check_password("admin"):
|
|
248
|
+
if not user.password or not user.has_usable_password():
|
|
249
|
+
user.set_password("admin")
|
|
250
|
+
user.save(update_fields=["password"])
|
|
251
|
+
else:
|
|
252
|
+
return None
|
|
253
|
+
if arthexis_user and user.operate_as_id is None:
|
|
254
|
+
user.operate_as = arthexis_user
|
|
255
|
+
user.save(update_fields=["operate_as"])
|
|
220
256
|
return user
|
|
221
257
|
return super().authenticate(request, username, password, **kwargs)
|
|
222
258
|
|
core/github_issues.py
CHANGED
|
@@ -71,13 +71,18 @@ def get_github_token() -> str:
|
|
|
71
71
|
latest_release = PackageRelease.latest()
|
|
72
72
|
if latest_release:
|
|
73
73
|
token = latest_release.get_github_token()
|
|
74
|
-
if token:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
if token is not None:
|
|
75
|
+
cleaned = token.strip() if isinstance(token, str) else str(token).strip()
|
|
76
|
+
if cleaned:
|
|
77
|
+
return cleaned
|
|
78
|
+
|
|
79
|
+
env_token = os.environ.get("GITHUB_TOKEN")
|
|
80
|
+
if env_token is not None:
|
|
81
|
+
cleaned = env_token.strip() if isinstance(env_token, str) else str(env_token).strip()
|
|
82
|
+
if cleaned:
|
|
83
|
+
return cleaned
|
|
84
|
+
|
|
85
|
+
raise RuntimeError("GitHub token is not configured")
|
|
81
86
|
|
|
82
87
|
|
|
83
88
|
def _ensure_lock_dir() -> None:
|