arthexis 0.1.11__py3-none-any.whl → 0.1.12__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.11.dist-info → arthexis-0.1.12.dist-info}/METADATA +2 -2
- {arthexis-0.1.11.dist-info → arthexis-0.1.12.dist-info}/RECORD +38 -35
- config/settings.py +7 -2
- core/admin.py +246 -68
- core/apps.py +21 -0
- core/models.py +41 -8
- core/reference_utils.py +1 -1
- core/release.py +4 -0
- core/system.py +6 -3
- core/tasks.py +92 -40
- core/tests.py +64 -0
- core/views.py +131 -17
- nodes/admin.py +316 -6
- nodes/feature_checks.py +133 -0
- nodes/models.py +83 -26
- nodes/reports.py +411 -0
- nodes/tests.py +365 -36
- nodes/utils.py +32 -0
- ocpp/admin.py +278 -15
- ocpp/consumers.py +506 -8
- ocpp/evcs_discovery.py +158 -0
- ocpp/models.py +234 -4
- ocpp/simulator.py +321 -22
- ocpp/store.py +110 -2
- ocpp/tests.py +789 -6
- ocpp/transactions_io.py +17 -3
- ocpp/views.py +225 -19
- pages/admin.py +135 -3
- pages/context_processors.py +15 -1
- pages/defaults.py +1 -2
- pages/forms.py +38 -0
- pages/models.py +136 -1
- pages/tests.py +262 -4
- pages/urls.py +1 -0
- pages/views.py +52 -3
- {arthexis-0.1.11.dist-info → arthexis-0.1.12.dist-info}/WHEEL +0 -0
- {arthexis-0.1.11.dist-info → arthexis-0.1.12.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.11.dist-info → arthexis-0.1.12.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.12
|
|
4
4
|
Summary: Django-based MESH system
|
|
5
5
|
Author-email: "Rafael J. Guillén-Osorio" <tecnologia@gelectriic.com>
|
|
6
6
|
License-Expression: GPL-3.0-only
|
|
@@ -115,7 +115,7 @@ Dynamic: license-file
|
|
|
115
115
|
|
|
116
116
|
# Arthexis Constellation
|
|
117
117
|
|
|
118
|
-
[](https://github.com/arthexis/arthexis/actions/workflows/coverage.yml) [](
|
|
118
|
+
[](https://github.com/arthexis/arthexis/actions/workflows/coverage.yml) [](docs/development/ocpp-user-manual.md)
|
|
119
119
|
|
|
120
120
|
## Purpose
|
|
121
121
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
arthexis-0.1.
|
|
1
|
+
arthexis-0.1.12.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
2
2
|
config/__init__.py,sha256=8_b7rx_-Xcuzu3Z7mSR94q3PAhjyYqLFQi3IOEz6hcI,108
|
|
3
3
|
config/active_app.py,sha256=MET_G7oHL7GkoSo3VkkMzymM-PwsSZazMLZxpgjFLTo,388
|
|
4
4
|
config/asgi.py,sha256=n09URedOmQ_59II3UCl3iodGSDWOuN_A8DFyfLjuylA,803
|
|
@@ -10,14 +10,14 @@ config/loadenv.py,sha256=bhFbHTbRJSkSwrFk3UInKEKQ8ZY-poatOGi7rC57YAI,298
|
|
|
10
10
|
config/logging.py,sha256=334jADN4dM5GNHaCWlYPOKYa5BhfxbsuejH_QDALG6g,1793
|
|
11
11
|
config/middleware.py,sha256=EvraDumepnKwCDswHGXb1mK7vud_dEEoZ4eh0IQ7fhQ,744
|
|
12
12
|
config/offline.py,sha256=mhQjCUzdOwSzZ6oLgPDJR48xaPIDzOi34ARUEz43seE,1431
|
|
13
|
-
config/settings.py,sha256=
|
|
13
|
+
config/settings.py,sha256=SVsBKHoGYix3oHKlFAB4zjYRSQ1r-E9NREFYRvmcc1o,22710
|
|
14
14
|
config/urls.py,sha256=MmbES50lTHyMZ6risgXAGfevncN7j4HC74jR4PX_5xY,5228
|
|
15
15
|
config/wsgi.py,sha256=Fu-ONO2SgYeU6rhmy909P-uLX-n8ALJQObdm9MHPS-k,450
|
|
16
16
|
core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
core/admin.py,sha256=
|
|
17
|
+
core/admin.py,sha256=P7Mbh_POpxrqzLCMf6YH3axhDbJPMQWQyTOKV9Z88UA,101803
|
|
18
18
|
core/admin_history.py,sha256=NIDWkosJoHMaucBvUjq8VmmL-0e8ngJ4l4XA89d4jwQ,1833
|
|
19
19
|
core/admindocs.py,sha256=GufdugiNEG87xGSDYVq4CBMhGRubsQCzgz-FqDIqzpM,5367
|
|
20
|
-
core/apps.py,sha256=
|
|
20
|
+
core/apps.py,sha256=Kyv7vnYPBWDg21uYyUo1aOki0Rb5zC3_3LRJTgEHE3s,11236
|
|
21
21
|
core/auto_upgrade.py,sha256=BkoE7rJuYAmwoMux22NqujWZYjYXtN40GBloC0sNMY4,1799
|
|
22
22
|
core/backends.py,sha256=VsZZwskII6QLnxP6Ff593V7o9lqXXfN2_bIfZXrvjyI,8222
|
|
23
23
|
core/entity.py,sha256=dkPywTVk981fV8bOEoZw-1SMrdh8T0jVAUZnRxg3dDg,4505
|
|
@@ -30,70 +30,73 @@ core/liveupdate.py,sha256=kTgbE2gnU3PPIV-88Bw2swSl1aGp6stSdBYqBFbLvx0,716
|
|
|
30
30
|
core/log_paths.py,sha256=6UXYk6QIUmRO3ecFaFH3dgJ_pf4C_wUN6b0JqUhLVBY,3045
|
|
31
31
|
core/mailer.py,sha256=OF3UgrTVs2St60tQG3ORV7_N6AWK6EtuB04KQXDop_Q,2829
|
|
32
32
|
core/middleware.py,sha256=a4XL0pld4YiG-vanHrzYbJNHv64s75lvmG9inoG1ln0,3479
|
|
33
|
-
core/models.py,sha256=
|
|
33
|
+
core/models.py,sha256=t38b5raQFARB8MsjFkWm-vng65ITX3Ogtb_HuYZ3vqA,91507
|
|
34
34
|
core/notifications.py,sha256=YtNDGxNveZ6t3tlMXJ7wIaZZTWIZfOKy6vN9mXkeYnA,4021
|
|
35
35
|
core/public_wifi.py,sha256=A08IPJqcdgUKSWbktyqsV4ol8C0uxDxZy1s1ECuPdBE,6526
|
|
36
|
-
core/reference_utils.py,sha256=
|
|
37
|
-
core/release.py,sha256=
|
|
36
|
+
core/reference_utils.py,sha256=2AQxtUkEbArieu1FMz3rTTHKPCDTJhHp_7bhzQCkN3U,3735
|
|
37
|
+
core/release.py,sha256=zVDcN2k4NsIydQqSq33jmMdSSYgR-1xocFWBnzaIZhU,11179
|
|
38
38
|
core/sigil_builder.py,sha256=63KSTh7AohG0szr_amBov0zoZuTE8bWqmgsEfPsZHCg,4992
|
|
39
39
|
core/sigil_context.py,sha256=8xrGiB2L1dFfSTrVLsFPLKfkhRwCXXZ0-EXvZdPeTMU,459
|
|
40
40
|
core/sigil_resolver.py,sha256=PAGwF3ugsqN85tKyIvT0YCDCM2MU_Mct2mCJz8XY1s4,11413
|
|
41
|
-
core/system.py,sha256=
|
|
42
|
-
core/tasks.py,sha256=
|
|
41
|
+
core/system.py,sha256=iH3-Ymonn_wVAvTmg2fJBI81F0nDCcrSJsAwIlD4sR0,15212
|
|
42
|
+
core/tasks.py,sha256=9njUdJUZNtu23XfWhSxk5lX1kKuvckX0qamxlxTHFxA,12328
|
|
43
43
|
core/temp_passwords.py,sha256=Kp4C-y1Hh4GeV1vSOCw3ZyIjcRMts186lUgtEkd3rxY,5452
|
|
44
44
|
core/test_system_info.py,sha256=sHCuo-qyw0BKacbGXFhTWxkuPgywdMGiQ_6YK022n-E,4803
|
|
45
|
-
core/tests.py,sha256=
|
|
45
|
+
core/tests.py,sha256=wCzvp3HO2bewvYPN9OstYjqtCLLWhK1msYuZxBg-lQ4,61577
|
|
46
46
|
core/tests_liveupdate.py,sha256=D1o2gPopnK7wDCeDQlJ-tfitWh4umZQFRxSTCFY-puc,527
|
|
47
47
|
core/urls.py,sha256=x-LNCxCgrLINdBsJTUUcAuMS5EK5Sh1ybvsVuUnJfLw,436
|
|
48
48
|
core/user_data.py,sha256=kOzcWJcR-d05E7QYoNzKqQSm9bnKJvtG6zq0zRf-tDI,21371
|
|
49
|
-
core/views.py,sha256=
|
|
49
|
+
core/views.py,sha256=o8gjDuBzo_7YXEdFt4_OJpuz1Zg4Y7srcesNr-DIYYs,49169
|
|
50
50
|
core/widgets.py,sha256=ihah_-NtFJ3oRCS3TdcT6iHCUTlg1tUULJsVCu05C0o,1379
|
|
51
51
|
core/workgroup_urls.py,sha256=2bC8mOMkxIj04WsNis0Td6AmmJFr6z27Ol5epIvhO28,424
|
|
52
52
|
core/workgroup_views.py,sha256=pFJ4PIRN3WWpRyombpWDKKteYQYoI7lu7ddSEKojD7I,2983
|
|
53
53
|
nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
54
54
|
nodes/actions.py,sha256=HHnwByTBc3guOORvrKOuvUFID-_BpBq6OENE_PDgk9s,2335
|
|
55
|
-
nodes/admin.py,sha256=
|
|
55
|
+
nodes/admin.py,sha256=VhTQawEOFnosSl5cQo_EnXYmeyHBjHCwSS44bMTbaeA,32766
|
|
56
56
|
nodes/apps.py,sha256=KijGydsQBS-8Q8uBc0ahDZXSSCMzP3tQcYIEigJxdCE,2077
|
|
57
57
|
nodes/backends.py,sha256=-g7PIbGtIn_3kGoi2w_mJ6zVjvUKTRRWhHABPnnf29g,6081
|
|
58
58
|
nodes/dns.py,sha256=X8PML9rPR7i6NwCSqqxnlO1qpHHRo8uA-XePDTQZYrY,7251
|
|
59
|
+
nodes/feature_checks.py,sha256=ZK9PufvF2pFWHUm4khg8lfHVNncA6zrMBxu-WZEw7-E,4129
|
|
59
60
|
nodes/lcd.py,sha256=7MqS3jV_De2ysSmTtXQfYTWaxdfbmm6YfNdb_7qIVZc,5923
|
|
60
|
-
nodes/models.py,sha256=
|
|
61
|
+
nodes/models.py,sha256=aF_Bhbi54H2A3HuQiwsvTEwk6JAsImJjdAdL8OcHeTg,49040
|
|
62
|
+
nodes/reports.py,sha256=pQwwTa5E-JhUwQIbKKQvgD_Cw9556CH0ieRgRjBMnMQ,12470
|
|
61
63
|
nodes/tasks.py,sha256=jorbN4h0PqWMBRMFdkGgJtvG8GPFwGr5Hxlm5In3EeY,1568
|
|
62
|
-
nodes/tests.py,sha256=
|
|
64
|
+
nodes/tests.py,sha256=28oh6oAINpq8a-EknsB-GKU-4RYfnNSdedqV411W5Jg,100131
|
|
63
65
|
nodes/urls.py,sha256=20yZDZf4DNgIZ9hQWsUzjp8k5Fryg9ukk761_KGQt9k,548
|
|
64
|
-
nodes/utils.py,sha256=
|
|
66
|
+
nodes/utils.py,sha256=h3qVmCBBLyrh06FmDldprxfDQG7dgLw82Tf5PrU-5Qc,4120
|
|
65
67
|
nodes/views.py,sha256=lrT90AsOXeqpvkZnlCPv78mXILGJ8FwJzgsjU-wAZ4Y,15611
|
|
66
68
|
ocpp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
67
|
-
ocpp/admin.py,sha256=
|
|
69
|
+
ocpp/admin.py,sha256=LUJw7fk5q336oAoftfGHs5_pbvEozjMPF69-WCTlAiM,26675
|
|
68
70
|
ocpp/apps.py,sha256=mCZ5Z0ei7z7c62luIcIhbEuwL1N4czQFSToOkGvRmms,867
|
|
69
|
-
ocpp/consumers.py,sha256=
|
|
71
|
+
ocpp/consumers.py,sha256=drfud7XSCP3zhrAc6ljzyrqM8K6VDPNFRs4Z_NeHfLk,57896
|
|
70
72
|
ocpp/evcs.py,sha256=O53rCHdxKcgPsj7o57rDiNHTVvEii3DTtQ3djWFTohw,34065
|
|
71
|
-
ocpp/
|
|
73
|
+
ocpp/evcs_discovery.py,sha256=47pWgqX1_JOT5BGSrIdEyuoPTGWN4b6rNjJr8kWeQCg,4034
|
|
74
|
+
ocpp/models.py,sha256=fZlqwPCq3LNHSE-RnGUqgFWI5FT_QyRandxujbxNZZs,32177
|
|
72
75
|
ocpp/reference_utils.py,sha256=sTgbXfmz00f23LBBkpO-sBGoJf1qaEshHeSofLReYCc,1114
|
|
73
76
|
ocpp/routing.py,sha256=g9vPnLw-D1N8L_mW0_oCe-nTDibjC0Et-SFxe8NFAOI,308
|
|
74
|
-
ocpp/simulator.py,sha256=
|
|
75
|
-
ocpp/store.py,sha256=
|
|
77
|
+
ocpp/simulator.py,sha256=hFwXuX8SaSJKEXWVWakyBxGa6ZbPvwa0PVTnKrXR1nw,28049
|
|
78
|
+
ocpp/store.py,sha256=OjGWT3WkpoahGpUwn_ZpIOsxgOnryoXXSwPCvFsLkzM,17410
|
|
76
79
|
ocpp/tasks.py,sha256=cOcJBshckFKs8GnACvmYZUBG116amtLRAzEP-JNqlZ0,905
|
|
77
80
|
ocpp/test_export_import.py,sha256=TK1__E4K5WrLYPIx-1iJWoIhuRCnOtXc2cYEpGigd3U,4660
|
|
78
81
|
ocpp/test_rfid.py,sha256=hX8VZ2HRyBGjtQLClZ1gy28dOj25RT2r9eK3l9XkmJg,25877
|
|
79
|
-
ocpp/tests.py,sha256=
|
|
80
|
-
ocpp/transactions_io.py,sha256=
|
|
82
|
+
ocpp/tests.py,sha256=aQZBGmGzhzMh2GrEsunN50gRKUgtIqdD4Y4NbjsPt7E,138325
|
|
83
|
+
ocpp/transactions_io.py,sha256=sFCJvAU5IqWZ4rLvagy4gdcGVvH2rBZvNCfHTXDYJAs,7044
|
|
81
84
|
ocpp/urls.py,sha256=jl6AQLtmLMvrNXP3dQKgPe9Kvaul4oaYd80DskpzVBc,1750
|
|
82
|
-
ocpp/views.py,sha256=
|
|
85
|
+
ocpp/views.py,sha256=FyLt7_67IeteBDADoRkqO3WJGit9noiMs_5RVapwUfo,45669
|
|
83
86
|
pages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
84
|
-
pages/admin.py,sha256=
|
|
87
|
+
pages/admin.py,sha256=0D2ZGqOhMjk8LfXnPr0g0uMUPJl2qCgvDJz_2IS3WFM,16904
|
|
85
88
|
pages/apps.py,sha256=mfCxegmnRqKcszyEwpQhZpW9JWOuEYdVereU_w49BXg,298
|
|
86
89
|
pages/checks.py,sha256=an-MlMCIG-FSKmdgOhYV0VOoB_wDQD7dxKD03Hjrzts,1567
|
|
87
|
-
pages/context_processors.py,sha256=
|
|
88
|
-
pages/defaults.py,sha256=
|
|
89
|
-
pages/forms.py,sha256=
|
|
90
|
+
pages/context_processors.py,sha256=4lkk2mTzmsOKhDhQanWXuU2bbwow0h-1Zn5GWOrL2z4,4132
|
|
91
|
+
pages/defaults.py,sha256=cOhb5p_SneIsK6Qfn9SV1fyq7L3gDheWYo1xp7SplF8,535
|
|
92
|
+
pages/forms.py,sha256=xwQ1uKqZBrIjn9DJQIqZtn0u_xO46w9vSjKVX9Mt6pc,6349
|
|
90
93
|
pages/middleware.py,sha256=fUdAscLa6h2EqJTFUjhVijfSf82gBfm6XUZTVygWAnk,5227
|
|
91
|
-
pages/models.py,sha256=
|
|
92
|
-
pages/tests.py,sha256=
|
|
93
|
-
pages/urls.py,sha256=
|
|
94
|
+
pages/models.py,sha256=O4wtjD8jkZlguxEQFcvijSPM55KBjRBdhzvg0FHnxt4,14577
|
|
95
|
+
pages/tests.py,sha256=Pk0waQkWxi-5EZq44XOUGdhvZC1oXh8dy4j0FqRb6ns,84353
|
|
96
|
+
pages/urls.py,sha256=iwe4hvcr_ko-n4h24t6t2882eZp2qyLXeEYkrIahoGU,1067
|
|
94
97
|
pages/utils.py,sha256=7kik1W0Gk6SFxYGhg6shfI2W9Xdcv1sCpkRCQ883a88,311
|
|
95
|
-
pages/views.py,sha256=
|
|
96
|
-
arthexis-0.1.
|
|
97
|
-
arthexis-0.1.
|
|
98
|
-
arthexis-0.1.
|
|
99
|
-
arthexis-0.1.
|
|
98
|
+
pages/views.py,sha256=_7NqHt0omhAge63Fz6ukbbBE06oQzKgVoO5e3WtSMvk,43044
|
|
99
|
+
arthexis-0.1.12.dist-info/METADATA,sha256=nErwfvr1FpIptkmRFT7yLGLigaHdMcXaLL4iFUTEhXc,10137
|
|
100
|
+
arthexis-0.1.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
101
|
+
arthexis-0.1.12.dist-info/top_level.txt,sha256=J2a2q8_BWrCZ8H2WFUNMBfO2jz8j2gax6zZh-_1QDac,29
|
|
102
|
+
arthexis-0.1.12.dist-info/RECORD,,
|
config/settings.py
CHANGED
|
@@ -685,10 +685,15 @@ LOGGING = {
|
|
|
685
685
|
"backupCount": 7,
|
|
686
686
|
"encoding": "utf-8",
|
|
687
687
|
"formatter": "standard",
|
|
688
|
-
}
|
|
688
|
+
},
|
|
689
|
+
"console": {
|
|
690
|
+
"class": "logging.StreamHandler",
|
|
691
|
+
"level": "ERROR",
|
|
692
|
+
"formatter": "standard",
|
|
693
|
+
},
|
|
689
694
|
},
|
|
690
695
|
"root": {
|
|
691
|
-
"handlers": ["file"],
|
|
696
|
+
"handlers": ["file", "console"],
|
|
692
697
|
"level": "DEBUG",
|
|
693
698
|
},
|
|
694
699
|
}
|
core/admin.py
CHANGED
|
@@ -76,6 +76,7 @@ from .user_data import (
|
|
|
76
76
|
)
|
|
77
77
|
from .widgets import OdooProductWidget
|
|
78
78
|
from .mcp import process as mcp_process
|
|
79
|
+
from .mcp.server import resolve_base_urls
|
|
79
80
|
|
|
80
81
|
|
|
81
82
|
admin.site.unregister(Group)
|
|
@@ -371,6 +372,29 @@ class ReleaseManagerAdminForm(forms.ModelForm):
|
|
|
371
372
|
"github_token": forms.Textarea(attrs={"rows": 3, "style": "width: 40em;"}),
|
|
372
373
|
}
|
|
373
374
|
|
|
375
|
+
def __init__(self, *args, **kwargs):
|
|
376
|
+
super().__init__(*args, **kwargs)
|
|
377
|
+
self.fields["pypi_token"].help_text = format_html(
|
|
378
|
+
"{} <a href=\"{}\" target=\"_blank\" rel=\"noopener noreferrer\">{}</a>{}",
|
|
379
|
+
"Generate an API token from your PyPI account settings.",
|
|
380
|
+
"https://pypi.org/manage/account/token/",
|
|
381
|
+
"pypi.org/manage/account/token/",
|
|
382
|
+
(
|
|
383
|
+
" by clicking “Add API token”, optionally scoping it to the package, "
|
|
384
|
+
"and paste the full `pypi-***` value here."
|
|
385
|
+
),
|
|
386
|
+
)
|
|
387
|
+
self.fields["github_token"].help_text = format_html(
|
|
388
|
+
"{} <a href=\"{}\" target=\"_blank\" rel=\"noopener noreferrer\">{}</a>{}",
|
|
389
|
+
"Create a personal access token at GitHub → Settings → Developer settings →",
|
|
390
|
+
"https://github.com/settings/tokens",
|
|
391
|
+
"github.com/settings/tokens",
|
|
392
|
+
(
|
|
393
|
+
" with the repository access needed for releases (repo scope for classic tokens "
|
|
394
|
+
"or an equivalent fine-grained token) and paste it here."
|
|
395
|
+
),
|
|
396
|
+
)
|
|
397
|
+
|
|
374
398
|
|
|
375
399
|
@admin.register(ReleaseManager)
|
|
376
400
|
class ReleaseManagerAdmin(ProfileAdminMixin, SaveBeforeChangeAction, EntityModelAdmin):
|
|
@@ -690,28 +714,30 @@ class OdooProfileAdminForm(forms.ModelForm):
|
|
|
690
714
|
)
|
|
691
715
|
|
|
692
716
|
|
|
693
|
-
class
|
|
694
|
-
"""
|
|
695
|
-
|
|
696
|
-
password = forms.CharField(
|
|
697
|
-
widget=forms.PasswordInput(render_value=True),
|
|
698
|
-
required=False,
|
|
699
|
-
help_text="Leave blank to keep the current password.",
|
|
700
|
-
)
|
|
717
|
+
class MaskedPasswordFormMixin:
|
|
718
|
+
"""Mixin that hides stored passwords while allowing updates."""
|
|
701
719
|
|
|
702
|
-
|
|
703
|
-
model = EmailInbox
|
|
704
|
-
fields = "__all__"
|
|
720
|
+
password_sigil_fields: tuple[str, ...] = ()
|
|
705
721
|
|
|
706
722
|
def __init__(self, *args, **kwargs):
|
|
707
723
|
super().__init__(*args, **kwargs)
|
|
724
|
+
field = self.fields.get("password")
|
|
725
|
+
if field is None:
|
|
726
|
+
return
|
|
727
|
+
if not isinstance(field.widget, forms.PasswordInput):
|
|
728
|
+
field.widget = forms.PasswordInput()
|
|
729
|
+
field.widget.attrs.setdefault("autocomplete", "new-password")
|
|
730
|
+
field.help_text = field.help_text or "Leave blank to keep the current password."
|
|
708
731
|
if self.instance.pk:
|
|
709
|
-
|
|
732
|
+
field.initial = ""
|
|
710
733
|
self.initial["password"] = ""
|
|
711
734
|
else:
|
|
712
|
-
|
|
735
|
+
field.required = True
|
|
713
736
|
|
|
714
737
|
def clean_password(self):
|
|
738
|
+
field = self.fields.get("password")
|
|
739
|
+
if field is None:
|
|
740
|
+
return self.cleaned_data.get("password")
|
|
715
741
|
pwd = self.cleaned_data.get("password")
|
|
716
742
|
if not pwd and self.instance.pk:
|
|
717
743
|
return keep_existing("password")
|
|
@@ -719,10 +745,23 @@ class EmailInboxAdminForm(forms.ModelForm):
|
|
|
719
745
|
|
|
720
746
|
def _post_clean(self):
|
|
721
747
|
super()._post_clean()
|
|
722
|
-
|
|
723
|
-
self,
|
|
724
|
-
|
|
725
|
-
|
|
748
|
+
if self.password_sigil_fields:
|
|
749
|
+
_restore_sigil_values(self, self.password_sigil_fields)
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
class EmailInboxAdminForm(MaskedPasswordFormMixin, forms.ModelForm):
|
|
753
|
+
"""Admin form for :class:`core.models.EmailInbox` with hidden password."""
|
|
754
|
+
|
|
755
|
+
password = forms.CharField(
|
|
756
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
|
|
757
|
+
required=False,
|
|
758
|
+
help_text="Leave blank to keep the current password.",
|
|
759
|
+
)
|
|
760
|
+
password_sigil_fields = ("username", "host", "password", "protocol")
|
|
761
|
+
|
|
762
|
+
class Meta:
|
|
763
|
+
model = EmailInbox
|
|
764
|
+
fields = "__all__"
|
|
726
765
|
|
|
727
766
|
|
|
728
767
|
class ProfileInlineFormSet(BaseInlineFormSet):
|
|
@@ -880,16 +919,25 @@ class SocialProfileInlineForm(ProfileFormMixin, forms.ModelForm):
|
|
|
880
919
|
fields = ("network", "handle", "domain", "did")
|
|
881
920
|
|
|
882
921
|
|
|
883
|
-
class
|
|
884
|
-
|
|
922
|
+
class EmailOutboxAdminForm(MaskedPasswordFormMixin, forms.ModelForm):
|
|
923
|
+
"""Admin form for :class:`nodes.models.EmailOutbox` with hidden password."""
|
|
924
|
+
|
|
885
925
|
password = forms.CharField(
|
|
886
|
-
widget=forms.PasswordInput(
|
|
926
|
+
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
|
|
887
927
|
required=False,
|
|
888
928
|
help_text="Leave blank to keep the current password.",
|
|
889
929
|
)
|
|
930
|
+
password_sigil_fields = ("password", "host", "username", "from_email")
|
|
890
931
|
|
|
891
932
|
class Meta:
|
|
892
933
|
model = EmailOutbox
|
|
934
|
+
fields = "__all__"
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
class EmailOutboxInlineForm(ProfileFormMixin, EmailOutboxAdminForm):
|
|
938
|
+
profile_fields = EmailOutbox.profile_fields
|
|
939
|
+
|
|
940
|
+
class Meta(EmailOutboxAdminForm.Meta):
|
|
893
941
|
fields = (
|
|
894
942
|
"password",
|
|
895
943
|
"host",
|
|
@@ -901,27 +949,6 @@ class EmailOutboxInlineForm(ProfileFormMixin, forms.ModelForm):
|
|
|
901
949
|
"is_enabled",
|
|
902
950
|
)
|
|
903
951
|
|
|
904
|
-
def __init__(self, *args, **kwargs):
|
|
905
|
-
super().__init__(*args, **kwargs)
|
|
906
|
-
if self.instance.pk:
|
|
907
|
-
self.fields["password"].initial = ""
|
|
908
|
-
self.initial["password"] = ""
|
|
909
|
-
else:
|
|
910
|
-
self.fields["password"].required = True
|
|
911
|
-
|
|
912
|
-
def clean_password(self):
|
|
913
|
-
pwd = self.cleaned_data.get("password")
|
|
914
|
-
if not pwd and self.instance.pk:
|
|
915
|
-
return keep_existing("password")
|
|
916
|
-
return pwd
|
|
917
|
-
|
|
918
|
-
def _post_clean(self):
|
|
919
|
-
super()._post_clean()
|
|
920
|
-
_restore_sigil_values(
|
|
921
|
-
self,
|
|
922
|
-
["password", "host", "username", "from_email"],
|
|
923
|
-
)
|
|
924
|
-
|
|
925
952
|
|
|
926
953
|
class ReleaseManagerInlineForm(ProfileFormMixin, forms.ModelForm):
|
|
927
954
|
profile_fields = ReleaseManager.profile_fields
|
|
@@ -1324,10 +1351,8 @@ class OdooProfileAdmin(ProfileAdminMixin, SaveBeforeChangeAction, EntityModelAdm
|
|
|
1324
1351
|
changelist_actions = ["my_profile"]
|
|
1325
1352
|
fieldsets = (
|
|
1326
1353
|
("Owner", {"fields": ("user", "group")}),
|
|
1327
|
-
(
|
|
1328
|
-
|
|
1329
|
-
{"fields": ("host", "database", "username", "password")},
|
|
1330
|
-
),
|
|
1354
|
+
("Configuration", {"fields": ("host", "database")}),
|
|
1355
|
+
("Credentials", {"fields": ("username", "password")}),
|
|
1331
1356
|
(
|
|
1332
1357
|
"Odoo Employee",
|
|
1333
1358
|
{"fields": ("verified_on", "odoo_uid", "name", "email")},
|
|
@@ -1417,18 +1442,10 @@ class EmailInboxAdmin(ProfileAdminMixin, SaveBeforeChangeAction, EntityModelAdmi
|
|
|
1417
1442
|
|
|
1418
1443
|
fieldsets = (
|
|
1419
1444
|
("Owner", {"fields": ("user", "group")}),
|
|
1445
|
+
("Credentials", {"fields": ("username", "password")}),
|
|
1420
1446
|
(
|
|
1421
|
-
|
|
1422
|
-
{
|
|
1423
|
-
"fields": (
|
|
1424
|
-
"username",
|
|
1425
|
-
"host",
|
|
1426
|
-
"port",
|
|
1427
|
-
"password",
|
|
1428
|
-
"protocol",
|
|
1429
|
-
"use_ssl",
|
|
1430
|
-
)
|
|
1431
|
-
},
|
|
1447
|
+
"Configuration",
|
|
1448
|
+
{"fields": ("host", "port", "protocol", "use_ssl")},
|
|
1432
1449
|
),
|
|
1433
1450
|
)
|
|
1434
1451
|
|
|
@@ -1526,17 +1543,10 @@ class AssistantProfileAdmin(
|
|
|
1526
1543
|
changelist_actions = ["my_profile"]
|
|
1527
1544
|
fieldsets = (
|
|
1528
1545
|
("Owner", {"fields": ("user", "group")}),
|
|
1546
|
+
("Credentials", {"fields": ("user_key_hash",)}),
|
|
1529
1547
|
(
|
|
1530
|
-
|
|
1531
|
-
{
|
|
1532
|
-
"fields": (
|
|
1533
|
-
"scopes",
|
|
1534
|
-
"is_active",
|
|
1535
|
-
"user_key_hash",
|
|
1536
|
-
"created_at",
|
|
1537
|
-
"last_used_at",
|
|
1538
|
-
)
|
|
1539
|
-
},
|
|
1548
|
+
"Configuration",
|
|
1549
|
+
{"fields": ("scopes", "is_active", "created_at", "last_used_at")},
|
|
1540
1550
|
),
|
|
1541
1551
|
)
|
|
1542
1552
|
|
|
@@ -1627,14 +1637,19 @@ class AssistantProfileAdmin(
|
|
|
1627
1637
|
config = dict(getattr(settings, "MCP_SIGIL_SERVER", {}))
|
|
1628
1638
|
host = config.get("host") or "127.0.0.1"
|
|
1629
1639
|
port = config.get("port", 8800)
|
|
1640
|
+
base_url, issuer_url = resolve_base_urls(config)
|
|
1630
1641
|
if isinstance(response, dict):
|
|
1631
1642
|
response.setdefault("mcp_server_host", host)
|
|
1632
1643
|
response.setdefault("mcp_server_port", port)
|
|
1644
|
+
response.setdefault("mcp_server_base_url", base_url)
|
|
1645
|
+
response.setdefault("mcp_server_issuer_url", issuer_url)
|
|
1633
1646
|
else:
|
|
1634
1647
|
context_data = getattr(response, "context_data", None)
|
|
1635
1648
|
if context_data is not None:
|
|
1636
1649
|
context_data.setdefault("mcp_server_host", host)
|
|
1637
1650
|
context_data.setdefault("mcp_server_port", port)
|
|
1651
|
+
context_data.setdefault("mcp_server_base_url", base_url)
|
|
1652
|
+
context_data.setdefault("mcp_server_issuer_url", issuer_url)
|
|
1638
1653
|
return response
|
|
1639
1654
|
|
|
1640
1655
|
def start_server(self, request):
|
|
@@ -1922,7 +1937,7 @@ class ProductFetchWizardForm(forms.Form):
|
|
|
1922
1937
|
@admin.register(Product)
|
|
1923
1938
|
class ProductAdmin(EntityModelAdmin):
|
|
1924
1939
|
form = ProductAdminForm
|
|
1925
|
-
actions = ["fetch_odoo_product"]
|
|
1940
|
+
actions = ["fetch_odoo_product", "register_from_odoo"]
|
|
1926
1941
|
|
|
1927
1942
|
def _odoo_profile_admin(self):
|
|
1928
1943
|
return self.admin_site._registry.get(OdooProfile)
|
|
@@ -1932,7 +1947,7 @@ class ProductAdmin(EntityModelAdmin):
|
|
|
1932
1947
|
return profile.execute(
|
|
1933
1948
|
"product.product",
|
|
1934
1949
|
"search_read",
|
|
1935
|
-
domain,
|
|
1950
|
+
[domain],
|
|
1936
1951
|
{
|
|
1937
1952
|
"fields": [
|
|
1938
1953
|
"name",
|
|
@@ -2072,6 +2087,169 @@ class ProductAdmin(EntityModelAdmin):
|
|
|
2072
2087
|
context["media"] = self.media + form.media
|
|
2073
2088
|
return TemplateResponse(request, "admin/core/product/fetch_odoo.html", context)
|
|
2074
2089
|
|
|
2090
|
+
def get_urls(self):
|
|
2091
|
+
urls = super().get_urls()
|
|
2092
|
+
custom = [
|
|
2093
|
+
path(
|
|
2094
|
+
"register-from-odoo/",
|
|
2095
|
+
self.admin_site.admin_view(self.register_from_odoo_view),
|
|
2096
|
+
name=f"{self.opts.app_label}_{self.opts.model_name}_register_from_odoo",
|
|
2097
|
+
)
|
|
2098
|
+
]
|
|
2099
|
+
return custom + urls
|
|
2100
|
+
|
|
2101
|
+
@admin.action(description="Register from Odoo")
|
|
2102
|
+
def register_from_odoo(self, request, queryset=None): # pragma: no cover - simple redirect
|
|
2103
|
+
return HttpResponseRedirect(
|
|
2104
|
+
reverse(
|
|
2105
|
+
f"admin:{self.opts.app_label}_{self.opts.model_name}_register_from_odoo"
|
|
2106
|
+
)
|
|
2107
|
+
)
|
|
2108
|
+
|
|
2109
|
+
def _build_register_context(self, request):
|
|
2110
|
+
opts = self.model._meta
|
|
2111
|
+
context = self.admin_site.each_context(request)
|
|
2112
|
+
context.update(
|
|
2113
|
+
{
|
|
2114
|
+
"opts": opts,
|
|
2115
|
+
"title": _("Register from Odoo"),
|
|
2116
|
+
"has_credentials": False,
|
|
2117
|
+
"profile_url": None,
|
|
2118
|
+
"products": [],
|
|
2119
|
+
"selected_product_id": request.POST.get("product_id", ""),
|
|
2120
|
+
}
|
|
2121
|
+
)
|
|
2122
|
+
|
|
2123
|
+
profile_admin = self._odoo_profile_admin()
|
|
2124
|
+
if profile_admin is not None:
|
|
2125
|
+
context["profile_url"] = profile_admin.get_my_profile_url(request)
|
|
2126
|
+
|
|
2127
|
+
profile = getattr(request.user, "odoo_profile", None)
|
|
2128
|
+
if not profile or not profile.is_verified:
|
|
2129
|
+
context["credential_error"] = _(
|
|
2130
|
+
"Configure your Odoo employee credentials before registering products."
|
|
2131
|
+
)
|
|
2132
|
+
return context, None
|
|
2133
|
+
|
|
2134
|
+
try:
|
|
2135
|
+
products = profile.execute(
|
|
2136
|
+
"product.product",
|
|
2137
|
+
"search_read",
|
|
2138
|
+
[[]],
|
|
2139
|
+
{
|
|
2140
|
+
"fields": [
|
|
2141
|
+
"name",
|
|
2142
|
+
"description_sale",
|
|
2143
|
+
"list_price",
|
|
2144
|
+
"standard_price",
|
|
2145
|
+
],
|
|
2146
|
+
"limit": 0,
|
|
2147
|
+
},
|
|
2148
|
+
)
|
|
2149
|
+
except Exception:
|
|
2150
|
+
context["error"] = _("Unable to fetch products from Odoo.")
|
|
2151
|
+
return context, []
|
|
2152
|
+
|
|
2153
|
+
context["has_credentials"] = True
|
|
2154
|
+
simplified = []
|
|
2155
|
+
for product in products:
|
|
2156
|
+
simplified.append(
|
|
2157
|
+
{
|
|
2158
|
+
"id": product.get("id"),
|
|
2159
|
+
"name": product.get("name", ""),
|
|
2160
|
+
"description_sale": product.get("description_sale", ""),
|
|
2161
|
+
"list_price": product.get("list_price"),
|
|
2162
|
+
"standard_price": product.get("standard_price"),
|
|
2163
|
+
}
|
|
2164
|
+
)
|
|
2165
|
+
context["products"] = simplified
|
|
2166
|
+
return context, simplified
|
|
2167
|
+
|
|
2168
|
+
def register_from_odoo_view(self, request):
|
|
2169
|
+
context, products = self._build_register_context(request)
|
|
2170
|
+
if products is None:
|
|
2171
|
+
return TemplateResponse(
|
|
2172
|
+
request, "admin/core/product/register_from_odoo.html", context
|
|
2173
|
+
)
|
|
2174
|
+
|
|
2175
|
+
if request.method == "POST" and context.get("has_credentials"):
|
|
2176
|
+
if not self.has_add_permission(request):
|
|
2177
|
+
context["form_error"] = _(
|
|
2178
|
+
"You do not have permission to add products."
|
|
2179
|
+
)
|
|
2180
|
+
else:
|
|
2181
|
+
product_id = request.POST.get("product_id")
|
|
2182
|
+
if not product_id:
|
|
2183
|
+
context["form_error"] = _("Select a product to register.")
|
|
2184
|
+
else:
|
|
2185
|
+
try:
|
|
2186
|
+
odoo_id = int(product_id)
|
|
2187
|
+
except (TypeError, ValueError):
|
|
2188
|
+
context["form_error"] = _("Invalid product selection.")
|
|
2189
|
+
else:
|
|
2190
|
+
match = next(
|
|
2191
|
+
(item for item in products if item.get("id") == odoo_id),
|
|
2192
|
+
None,
|
|
2193
|
+
)
|
|
2194
|
+
if not match:
|
|
2195
|
+
context["form_error"] = _(
|
|
2196
|
+
"The selected product was not found. Reload the page and try again."
|
|
2197
|
+
)
|
|
2198
|
+
else:
|
|
2199
|
+
existing = self.model.objects.filter(
|
|
2200
|
+
odoo_product__id=odoo_id
|
|
2201
|
+
).first()
|
|
2202
|
+
if existing:
|
|
2203
|
+
self.message_user(
|
|
2204
|
+
request,
|
|
2205
|
+
_(
|
|
2206
|
+
"Product %(name)s already imported; opening existing record."
|
|
2207
|
+
)
|
|
2208
|
+
% {"name": existing.name},
|
|
2209
|
+
level=messages.WARNING,
|
|
2210
|
+
)
|
|
2211
|
+
return HttpResponseRedirect(
|
|
2212
|
+
reverse(
|
|
2213
|
+
"admin:%s_%s_change"
|
|
2214
|
+
% (
|
|
2215
|
+
existing._meta.app_label,
|
|
2216
|
+
existing._meta.model_name,
|
|
2217
|
+
),
|
|
2218
|
+
args=[existing.pk],
|
|
2219
|
+
)
|
|
2220
|
+
)
|
|
2221
|
+
product = self.model.objects.create(
|
|
2222
|
+
name=match.get("name") or f"Odoo Product {odoo_id}",
|
|
2223
|
+
description=match.get("description_sale", "") or "",
|
|
2224
|
+
renewal_period=30,
|
|
2225
|
+
odoo_product={
|
|
2226
|
+
"id": odoo_id,
|
|
2227
|
+
"name": match.get("name", ""),
|
|
2228
|
+
},
|
|
2229
|
+
)
|
|
2230
|
+
self.log_addition(
|
|
2231
|
+
request, product, "Registered product from Odoo"
|
|
2232
|
+
)
|
|
2233
|
+
self.message_user(
|
|
2234
|
+
request,
|
|
2235
|
+
_("Imported %(name)s from Odoo.")
|
|
2236
|
+
% {"name": product.name},
|
|
2237
|
+
)
|
|
2238
|
+
return HttpResponseRedirect(
|
|
2239
|
+
reverse(
|
|
2240
|
+
"admin:%s_%s_change"
|
|
2241
|
+
% (
|
|
2242
|
+
product._meta.app_label,
|
|
2243
|
+
product._meta.model_name,
|
|
2244
|
+
),
|
|
2245
|
+
args=[product.pk],
|
|
2246
|
+
)
|
|
2247
|
+
)
|
|
2248
|
+
|
|
2249
|
+
return TemplateResponse(
|
|
2250
|
+
request, "admin/core/product/register_from_odoo.html", context
|
|
2251
|
+
)
|
|
2252
|
+
|
|
2075
2253
|
|
|
2076
2254
|
class RFIDResource(resources.ModelResource):
|
|
2077
2255
|
reference = fields.Field(
|
core/apps.py
CHANGED
|
@@ -21,6 +21,7 @@ class CoreConfig(AppConfig):
|
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
|
|
23
23
|
from django.conf import settings
|
|
24
|
+
from django.core.exceptions import ObjectDoesNotExist
|
|
24
25
|
from django.contrib.auth import get_user_model
|
|
25
26
|
from django.db.models.signals import post_migrate
|
|
26
27
|
from django.core.signals import got_request_exception
|
|
@@ -39,6 +40,26 @@ class CoreConfig(AppConfig):
|
|
|
39
40
|
)
|
|
40
41
|
from .admin_history import patch_admin_history
|
|
41
42
|
|
|
43
|
+
from django_otp.plugins.otp_totp.models import TOTPDevice as OTP_TOTPDevice
|
|
44
|
+
|
|
45
|
+
if not hasattr(
|
|
46
|
+
OTP_TOTPDevice._read_str_from_settings, "_core_totp_issuer_patch"
|
|
47
|
+
):
|
|
48
|
+
original_read_str = OTP_TOTPDevice._read_str_from_settings
|
|
49
|
+
|
|
50
|
+
def _core_totp_read_str(self, key):
|
|
51
|
+
if key == "OTP_TOTP_ISSUER":
|
|
52
|
+
try:
|
|
53
|
+
settings_obj = self.custom_settings
|
|
54
|
+
except ObjectDoesNotExist:
|
|
55
|
+
settings_obj = None
|
|
56
|
+
if settings_obj and settings_obj.issuer:
|
|
57
|
+
return settings_obj.issuer
|
|
58
|
+
return original_read_str(self, key)
|
|
59
|
+
|
|
60
|
+
_core_totp_read_str._core_totp_issuer_patch = True
|
|
61
|
+
OTP_TOTPDevice._read_str_from_settings = _core_totp_read_str
|
|
62
|
+
|
|
42
63
|
def create_default_arthexis(**kwargs):
|
|
43
64
|
User = get_user_model()
|
|
44
65
|
if not User.all_objects.exists():
|