arthexis 0.1.23__py3-none-any.whl → 0.1.24__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.23.dist-info → arthexis-0.1.24.dist-info}/METADATA +5 -5
- {arthexis-0.1.23.dist-info → arthexis-0.1.24.dist-info}/RECORD +17 -17
- config/settings.py +4 -0
- core/admin.py +139 -27
- core/models.py +543 -204
- core/tasks.py +25 -0
- nodes/admin.py +152 -172
- nodes/tests.py +80 -129
- nodes/urls.py +6 -0
- nodes/views.py +520 -0
- ocpp/admin.py +541 -175
- ocpp/models.py +28 -0
- ocpp/tasks.py +336 -1
- pages/views.py +60 -30
- {arthexis-0.1.23.dist-info → arthexis-0.1.24.dist-info}/WHEEL +0 -0
- {arthexis-0.1.23.dist-info → arthexis-0.1.24.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.23.dist-info → arthexis-0.1.24.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.24
|
|
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
|
|
@@ -15,7 +15,7 @@ Requires-Dist: amqp==5.3.1
|
|
|
15
15
|
Requires-Dist: annotated-types==0.7.0
|
|
16
16
|
Requires-Dist: anyio==4.9.0
|
|
17
17
|
Requires-Dist: asgiref==3.10.0
|
|
18
|
-
Requires-Dist: atproto
|
|
18
|
+
Requires-Dist: atproto<0.1.0,>=0.0.63
|
|
19
19
|
Requires-Dist: attrs==25.3.0
|
|
20
20
|
Requires-Dist: autobahn==24.4.2
|
|
21
21
|
Requires-Dist: Automat==25.4.16
|
|
@@ -70,8 +70,8 @@ Requires-Dist: psycopg-binary==3.2.12
|
|
|
70
70
|
Requires-Dist: pyasn1==0.6.1
|
|
71
71
|
Requires-Dist: pyasn1_modules==0.4.2
|
|
72
72
|
Requires-Dist: pycparser==2.22
|
|
73
|
-
Requires-Dist: pydantic==2.
|
|
74
|
-
Requires-Dist: pydantic_core==2.
|
|
73
|
+
Requires-Dist: pydantic==2.12.3
|
|
74
|
+
Requires-Dist: pydantic_core==2.41.4
|
|
75
75
|
Requires-Dist: pyOpenSSL==25.1.0
|
|
76
76
|
Requires-Dist: pyperclip==1.11.0
|
|
77
77
|
Requires-Dist: PySocks==1.7.1
|
|
@@ -106,7 +106,7 @@ Requires-Dist: vine==5.1.0
|
|
|
106
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
|
-
Requires-Dist: websockets==
|
|
109
|
+
Requires-Dist: websockets==15.0.1
|
|
110
110
|
Requires-Dist: whitenoise==6.11.0
|
|
111
111
|
Requires-Dist: plyer==2.1.0; sys_platform == "win32"
|
|
112
112
|
Requires-Dist: wsproto==1.2.0
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
arthexis-0.1.
|
|
1
|
+
arthexis-0.1.24.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=tFf0C4PO1whB4a_U7KcVlYnp2_gNC16t-cce3nNoStc,20914
|
|
14
14
|
config/settings_helpers.py,sha256=0BdBciUHIkwsWa0vV_RKAd4wDuEzgE7G-42XYiES4YQ,3127
|
|
15
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=N_x_4t7lSBIg1f13lN5qW54l29giXcGAy3_GWRVn4C0,153652
|
|
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
|
|
@@ -34,7 +34,7 @@ core/liveupdate.py,sha256=22m0ueQ10-6b-9pQJHY0_5WRYA98fysXKEXOWzIr550,691
|
|
|
34
34
|
core/log_paths.py,sha256=lxvgXPgJtVNZ-kYrqV8VFle4GFQrSxG-yRTglqvclmU,3318
|
|
35
35
|
core/mailer.py,sha256=JpW0RnD9uZ4O-wvlqeW7CMw95IFeCSkdvbankJDwHq0,2886
|
|
36
36
|
core/middleware.py,sha256=j19K9SX-Emkv7BDDtAacR9g6RWsxhKHwCc8w23JFvMM,3388
|
|
37
|
-
core/models.py,sha256=
|
|
37
|
+
core/models.py,sha256=jwr_LL0CmtLLybiFjeRFMPY1aNRLaE_IOPvQuihwnJ0,171669
|
|
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
|
|
@@ -44,7 +44,7 @@ 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
46
|
core/system.py,sha256=6ndxYDPswKkC3ySTwbgXzH0CdQYCZJytfA-99smyv_Q,42249
|
|
47
|
-
core/tasks.py,sha256=
|
|
47
|
+
core/tasks.py,sha256=ptO44VTBAoTwf7Y3pI6TnniIs4lTUgN4MKCgNAUjhm4,13135
|
|
48
48
|
core/temp_passwords.py,sha256=FieUnIUeQHmA1DoXvfJ5U6-Ayv3oDz-hSln5s_vNbA4,5271
|
|
49
49
|
core/test_system_info.py,sha256=IMPz21KEs6OC5YbL7YaIBdmJVLjRY6MgPuZpldJB5OI,6935
|
|
50
50
|
core/tests.py,sha256=PuxoarDS4reHNV4EDIyVRW7xIOFxZJYou1K_LI9ZNHY,105265
|
|
@@ -54,7 +54,7 @@ core/user_data.py,sha256=4pheHB5RqLJtmWMql30CLaCpuVqSyShXb7Sy-crRk_4,22400
|
|
|
54
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=
|
|
57
|
+
nodes/admin.py,sha256=oOPXFsqQXGUKpgH0B9SIdcPJziBqZRrgFEwo-yEH-O4,72895
|
|
58
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
|
|
@@ -65,23 +65,23 @@ 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=
|
|
69
|
-
nodes/urls.py,sha256
|
|
68
|
+
nodes/tests.py,sha256=IikaUCBOUo7r0VHJmqcnk-CsXbbBDT8687p0BmVVJOM,186523
|
|
69
|
+
nodes/urls.py,sha256=c1C-4rROmp51HbVf3KTERuFYvTRXwD5LlApoX4SIwBg,1135
|
|
70
70
|
nodes/utils.py,sha256=wt7UuSXGuq79A-g-B6EW3kK49QWJBb7zhhkw4pun4k8,4474
|
|
71
|
-
nodes/views.py,sha256=
|
|
71
|
+
nodes/views.py,sha256=b7g2TD4pHO01SK6e7uDf78zKbnQVVw3v0N7S2VBvdKQ,57780
|
|
72
72
|
ocpp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
73
|
-
ocpp/admin.py,sha256=
|
|
73
|
+
ocpp/admin.py,sha256=tRB7F9a4nQxcM-xV1AAeGRlEKQxm8i235yIq57xDU90,54379
|
|
74
74
|
ocpp/apps.py,sha256=i3NqrmIamNEQBT33CIqh7HOSOPmJXCMKrZ-DUd3whqg,842
|
|
75
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=VmkNPEACRks2kbyUa-2qt3xjrtBaipBonlgOk8xFTXg,38481
|
|
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=Hv_YUzT0dIq8OZE0yHIKzViGU7fEPKknXcXuNkJqC-g,20287
|
|
85
85
|
ocpp/test_export_import.py,sha256=ouQbTCp4mxfqoK6gondlu3PPcyrT9jSbWAX5gqqgaNk,4561
|
|
86
86
|
ocpp/test_rfid.py,sha256=IhFSlvsI8A8D3S32sRE298nYfrmqxbv7GfVErtNU3DQ,39137
|
|
87
87
|
ocpp/tests.py,sha256=0AsSR7UKZQUhWFPEwCE4FJHH1Ykl2UpooSGlBIPJOeU,207815
|
|
@@ -103,8 +103,8 @@ pages/tasks.py,sha256=ivcba_3wSQ1-cku0oDplzw6vLeQ9hBq3R4TG-LmR5gs,1913
|
|
|
103
103
|
pages/tests.py,sha256=akLS7p62PeUCaSXTAIsjAfDyv4KWOMJ2MkAGjPuX7AE,154350
|
|
104
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=7-10W-GYDzlef98Warr2JIs3oQkTwKoOMo1aFGglj14,65214
|
|
107
|
+
arthexis-0.1.24.dist-info/METADATA,sha256=9xkmegLGMFbSSMo1b3Fk8lGrksWPvJN57AeSDRWJ7so,11895
|
|
108
|
+
arthexis-0.1.24.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
109
|
+
arthexis-0.1.24.dist-info/top_level.txt,sha256=J2a2q8_BWrCZ8H2WFUNMBfO2jz8j2gax6zZh-_1QDac,29
|
|
110
|
+
arthexis-0.1.24.dist-info/RECORD,,
|
config/settings.py
CHANGED
|
@@ -666,4 +666,8 @@ CELERY_BEAT_SCHEDULE = {
|
|
|
666
666
|
"task": "ocpp.tasks.schedule_daily_charge_point_configuration_checks",
|
|
667
667
|
"schedule": crontab(minute=0, hour=0),
|
|
668
668
|
},
|
|
669
|
+
"ocpp_remote_sync": {
|
|
670
|
+
"task": "ocpp.tasks.sync_remote_chargers",
|
|
671
|
+
"schedule": crontab(minute="*"),
|
|
672
|
+
},
|
|
669
673
|
}
|
core/admin.py
CHANGED
|
@@ -50,6 +50,7 @@ import uuid
|
|
|
50
50
|
import requests
|
|
51
51
|
import datetime
|
|
52
52
|
from django.db import IntegrityError, transaction
|
|
53
|
+
from django.db.models import Q
|
|
53
54
|
import calendar
|
|
54
55
|
import re
|
|
55
56
|
from django_object_actions import DjangoObjectActions
|
|
@@ -63,7 +64,7 @@ from reportlab.graphics.barcode import qr
|
|
|
63
64
|
from reportlab.graphics.shapes import Drawing
|
|
64
65
|
from reportlab.lib.styles import getSampleStyleSheet
|
|
65
66
|
from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle
|
|
66
|
-
from ocpp.models import Transaction
|
|
67
|
+
from ocpp.models import Charger, Transaction
|
|
67
68
|
from ocpp.rfid.utils import build_mode_toggle
|
|
68
69
|
from nodes.models import EmailOutbox
|
|
69
70
|
from .github_helper import GitHubRepositoryError, create_repository_for_package
|
|
@@ -3615,13 +3616,62 @@ class RFIDAdmin(EntityModelAdmin, ImportExportModelAdmin):
|
|
|
3615
3616
|
return JsonResponse(result, status=status)
|
|
3616
3617
|
|
|
3617
3618
|
|
|
3619
|
+
class ClientReportRecurrencyFilter(admin.SimpleListFilter):
|
|
3620
|
+
title = "Recurrency"
|
|
3621
|
+
parameter_name = "recurrency"
|
|
3622
|
+
|
|
3623
|
+
def lookups(self, request, model_admin):
|
|
3624
|
+
for value, label in ClientReportSchedule.PERIODICITY_CHOICES:
|
|
3625
|
+
yield (value, label)
|
|
3626
|
+
|
|
3627
|
+
def queryset(self, request, queryset):
|
|
3628
|
+
value = self.value()
|
|
3629
|
+
if not value:
|
|
3630
|
+
return queryset
|
|
3631
|
+
if value == ClientReportSchedule.PERIODICITY_NONE:
|
|
3632
|
+
return queryset.filter(
|
|
3633
|
+
Q(schedule__isnull=True) | Q(schedule__periodicity=value)
|
|
3634
|
+
)
|
|
3635
|
+
return queryset.filter(schedule__periodicity=value)
|
|
3636
|
+
|
|
3637
|
+
|
|
3618
3638
|
@admin.register(ClientReport)
|
|
3619
3639
|
class ClientReportAdmin(EntityModelAdmin):
|
|
3620
|
-
list_display = (
|
|
3640
|
+
list_display = (
|
|
3641
|
+
"created_on",
|
|
3642
|
+
"period_range",
|
|
3643
|
+
"owner",
|
|
3644
|
+
"recurrency_display",
|
|
3645
|
+
"total_kw_period_display",
|
|
3646
|
+
"download_link",
|
|
3647
|
+
)
|
|
3648
|
+
list_select_related = ("schedule", "owner")
|
|
3649
|
+
list_filter = ("owner", ClientReportRecurrencyFilter)
|
|
3621
3650
|
readonly_fields = ("created_on", "data")
|
|
3622
3651
|
|
|
3623
3652
|
change_list_template = "admin/core/clientreport/change_list.html"
|
|
3624
3653
|
|
|
3654
|
+
def period_range(self, obj):
|
|
3655
|
+
return str(obj)
|
|
3656
|
+
|
|
3657
|
+
period_range.short_description = "Period"
|
|
3658
|
+
|
|
3659
|
+
def recurrency_display(self, obj):
|
|
3660
|
+
return obj.periodicity_label
|
|
3661
|
+
|
|
3662
|
+
recurrency_display.short_description = "Recurrency"
|
|
3663
|
+
|
|
3664
|
+
def total_kw_period_display(self, obj):
|
|
3665
|
+
return f"{obj.total_kw_period:.2f}"
|
|
3666
|
+
|
|
3667
|
+
total_kw_period_display.short_description = "Total kW (period)"
|
|
3668
|
+
|
|
3669
|
+
def download_link(self, obj):
|
|
3670
|
+
url = reverse("admin:core_clientreport_download", args=[obj.pk])
|
|
3671
|
+
return format_html('<a href="{}">Download</a>', url)
|
|
3672
|
+
|
|
3673
|
+
download_link.short_description = "Download"
|
|
3674
|
+
|
|
3625
3675
|
class ClientReportForm(forms.Form):
|
|
3626
3676
|
PERIOD_CHOICES = [
|
|
3627
3677
|
("range", "Date range"),
|
|
@@ -3657,8 +3707,28 @@ class ClientReportAdmin(EntityModelAdmin):
|
|
|
3657
3707
|
label="Month",
|
|
3658
3708
|
required=False,
|
|
3659
3709
|
widget=forms.DateInput(attrs={"type": "month"}),
|
|
3710
|
+
input_formats=["%Y-%m"],
|
|
3660
3711
|
help_text="Generates the report for the calendar month that you select.",
|
|
3661
3712
|
)
|
|
3713
|
+
language = forms.ChoiceField(
|
|
3714
|
+
label="Report language",
|
|
3715
|
+
choices=settings.LANGUAGES,
|
|
3716
|
+
help_text="Choose the language used for the generated report.",
|
|
3717
|
+
)
|
|
3718
|
+
title = forms.CharField(
|
|
3719
|
+
label="Report title",
|
|
3720
|
+
required=False,
|
|
3721
|
+
max_length=200,
|
|
3722
|
+
help_text="Optional heading that replaces the default report title.",
|
|
3723
|
+
)
|
|
3724
|
+
chargers = forms.ModelMultipleChoiceField(
|
|
3725
|
+
label="Charge points",
|
|
3726
|
+
queryset=Charger.objects.filter(connector_id__isnull=True)
|
|
3727
|
+
.order_by("display_name", "charger_id"),
|
|
3728
|
+
required=False,
|
|
3729
|
+
widget=forms.CheckboxSelectMultiple,
|
|
3730
|
+
help_text="Choose which charge points are included in the report.",
|
|
3731
|
+
)
|
|
3662
3732
|
owner = forms.ModelChoiceField(
|
|
3663
3733
|
queryset=get_user_model().objects.all(),
|
|
3664
3734
|
required=False,
|
|
@@ -3691,6 +3761,13 @@ class ClientReportAdmin(EntityModelAdmin):
|
|
|
3691
3761
|
and request.user.is_authenticated
|
|
3692
3762
|
):
|
|
3693
3763
|
self.fields["owner"].initial = request.user.pk
|
|
3764
|
+
self.fields["chargers"].widget.attrs["class"] = "charger-options"
|
|
3765
|
+
language_initial = ClientReport.default_language()
|
|
3766
|
+
if request:
|
|
3767
|
+
language_initial = ClientReport.normalize_language(
|
|
3768
|
+
getattr(request, "LANGUAGE_CODE", language_initial)
|
|
3769
|
+
)
|
|
3770
|
+
self.fields["language"].initial = language_initial
|
|
3694
3771
|
|
|
3695
3772
|
def clean(self):
|
|
3696
3773
|
cleaned = super().clean()
|
|
@@ -3735,6 +3812,10 @@ class ClientReportAdmin(EntityModelAdmin):
|
|
|
3735
3812
|
emails.append(candidate)
|
|
3736
3813
|
return emails
|
|
3737
3814
|
|
|
3815
|
+
def clean_title(self):
|
|
3816
|
+
title = self.cleaned_data.get("title")
|
|
3817
|
+
return ClientReport.normalize_title(title)
|
|
3818
|
+
|
|
3738
3819
|
def get_urls(self):
|
|
3739
3820
|
urls = super().get_urls()
|
|
3740
3821
|
custom = [
|
|
@@ -3743,6 +3824,11 @@ class ClientReportAdmin(EntityModelAdmin):
|
|
|
3743
3824
|
self.admin_site.admin_view(self.generate_view),
|
|
3744
3825
|
name="core_clientreport_generate",
|
|
3745
3826
|
),
|
|
3827
|
+
path(
|
|
3828
|
+
"generate/action/",
|
|
3829
|
+
self.admin_site.admin_view(self.generate_report),
|
|
3830
|
+
name="core_clientreport_generate_report",
|
|
3831
|
+
),
|
|
3746
3832
|
path(
|
|
3747
3833
|
"download/<int:report_id>/",
|
|
3748
3834
|
self.admin_site.admin_view(self.download_view),
|
|
@@ -3763,14 +3849,37 @@ class ClientReportAdmin(EntityModelAdmin):
|
|
|
3763
3849
|
enable_emails = form.cleaned_data.get("enable_emails", False)
|
|
3764
3850
|
disable_emails = not enable_emails
|
|
3765
3851
|
recipients = form.cleaned_data.get("destinations") if enable_emails else []
|
|
3852
|
+
chargers = list(form.cleaned_data.get("chargers") or [])
|
|
3853
|
+
language = form.cleaned_data.get("language")
|
|
3854
|
+
title = form.cleaned_data.get("title")
|
|
3766
3855
|
report = ClientReport.generate(
|
|
3767
3856
|
form.cleaned_data["start"],
|
|
3768
3857
|
form.cleaned_data["end"],
|
|
3769
3858
|
owner=owner,
|
|
3770
3859
|
recipients=recipients,
|
|
3771
3860
|
disable_emails=disable_emails,
|
|
3861
|
+
chargers=chargers,
|
|
3862
|
+
language=language,
|
|
3863
|
+
title=title,
|
|
3772
3864
|
)
|
|
3773
3865
|
report.store_local_copy()
|
|
3866
|
+
if chargers:
|
|
3867
|
+
report.chargers.set(chargers)
|
|
3868
|
+
if enable_emails and recipients:
|
|
3869
|
+
delivered = report.send_delivery(
|
|
3870
|
+
to=recipients,
|
|
3871
|
+
cc=[],
|
|
3872
|
+
outbox=ClientReport.resolve_outbox_for_owner(owner),
|
|
3873
|
+
reply_to=ClientReport.resolve_reply_to_for_owner(owner),
|
|
3874
|
+
)
|
|
3875
|
+
if delivered:
|
|
3876
|
+
report.recipients = delivered
|
|
3877
|
+
report.save(update_fields=["recipients"])
|
|
3878
|
+
self.message_user(
|
|
3879
|
+
request,
|
|
3880
|
+
"Consumer report emailed to the selected recipients.",
|
|
3881
|
+
messages.SUCCESS,
|
|
3882
|
+
)
|
|
3774
3883
|
recurrence = form.cleaned_data.get("recurrence")
|
|
3775
3884
|
if recurrence and recurrence != ClientReportSchedule.PERIODICITY_NONE:
|
|
3776
3885
|
schedule = ClientReportSchedule.objects.create(
|
|
@@ -3779,12 +3888,16 @@ class ClientReportAdmin(EntityModelAdmin):
|
|
|
3779
3888
|
periodicity=recurrence,
|
|
3780
3889
|
email_recipients=recipients,
|
|
3781
3890
|
disable_emails=disable_emails,
|
|
3891
|
+
language=language,
|
|
3892
|
+
title=title,
|
|
3782
3893
|
)
|
|
3894
|
+
if chargers:
|
|
3895
|
+
schedule.chargers.set(chargers)
|
|
3783
3896
|
report.schedule = schedule
|
|
3784
3897
|
report.save(update_fields=["schedule"])
|
|
3785
3898
|
self.message_user(
|
|
3786
3899
|
request,
|
|
3787
|
-
"
|
|
3900
|
+
"Consumer report schedule created; future reports will be generated automatically.",
|
|
3788
3901
|
messages.SUCCESS,
|
|
3789
3902
|
)
|
|
3790
3903
|
if disable_emails:
|
|
@@ -3812,45 +3925,44 @@ class ClientReportAdmin(EntityModelAdmin):
|
|
|
3812
3925
|
"report": report,
|
|
3813
3926
|
"schedule": schedule,
|
|
3814
3927
|
"download_url": download_url,
|
|
3815
|
-
"
|
|
3928
|
+
"opts": self.model._meta,
|
|
3816
3929
|
}
|
|
3817
3930
|
)
|
|
3818
3931
|
return TemplateResponse(
|
|
3819
3932
|
request, "admin/core/clientreport/generate.html", context
|
|
3820
3933
|
)
|
|
3821
3934
|
|
|
3935
|
+
def get_changelist_actions(self, request):
|
|
3936
|
+
parent = getattr(super(), "get_changelist_actions", None)
|
|
3937
|
+
actions: list[str] = []
|
|
3938
|
+
if callable(parent):
|
|
3939
|
+
parent_actions = parent(request)
|
|
3940
|
+
if parent_actions:
|
|
3941
|
+
actions.extend(parent_actions)
|
|
3942
|
+
if "generate_report" not in actions:
|
|
3943
|
+
actions.append("generate_report")
|
|
3944
|
+
return actions
|
|
3945
|
+
|
|
3946
|
+
def generate_report(self, request):
|
|
3947
|
+
return HttpResponseRedirect(reverse("admin:core_clientreport_generate"))
|
|
3948
|
+
|
|
3949
|
+
generate_report.label = _("Generate report")
|
|
3950
|
+
|
|
3822
3951
|
def download_view(self, request, report_id: int):
|
|
3823
3952
|
report = get_object_or_404(ClientReport, pk=report_id)
|
|
3824
3953
|
pdf_path = report.ensure_pdf()
|
|
3825
3954
|
if not pdf_path.exists():
|
|
3826
3955
|
raise Http404("Report file unavailable")
|
|
3827
|
-
|
|
3956
|
+
end_date = report.end_date
|
|
3957
|
+
if hasattr(end_date, "isoformat"):
|
|
3958
|
+
end_date_str = end_date.isoformat()
|
|
3959
|
+
else: # pragma: no cover - fallback for unexpected values
|
|
3960
|
+
end_date_str = str(end_date)
|
|
3961
|
+
filename = f"consumer-report-{end_date_str}.pdf"
|
|
3828
3962
|
response = FileResponse(pdf_path.open("rb"), content_type="application/pdf")
|
|
3829
3963
|
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
|
3830
3964
|
return response
|
|
3831
3965
|
|
|
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
|
-
|
|
3853
|
-
|
|
3854
3966
|
@admin.register(PackageRelease)
|
|
3855
3967
|
class PackageReleaseAdmin(SaveBeforeChangeAction, EntityModelAdmin):
|
|
3856
3968
|
change_list_template = "admin/core/packagerelease/change_list.html"
|