modoboa 2.4.8__py3-none-any.whl → 2.4.10__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.
- modoboa/admin/api/v1/serializers.py +7 -1
- modoboa/admin/api/v2/serializers.py +9 -2
- modoboa/admin/api/v2/tests.py +5 -3
- modoboa/admin/api/v2/viewsets.py +12 -10
- modoboa/core/api/v2/serializers.py +34 -15
- modoboa/core/api/v2/tests.py +58 -10
- modoboa/core/api/v2/viewsets.py +32 -23
- modoboa/core/app_settings.py +1 -1
- modoboa/core/management/commands/load_initial_data.py +25 -10
- modoboa/core/migrations/0025_rename_user_email_is_active_core_user_email_c0c03f_idx.py +5 -5
- modoboa/core/tests/test_core.py +47 -6
- modoboa/frontend_dist/assets/{AccountAliasForm-BkmO1XF4.js → AccountAliasForm-DVXatAhB.js} +1 -1
- modoboa/frontend_dist/assets/{AccountEditView-BDQ_CECV.js → AccountEditView-DmvQjxpx.js} +1 -1
- modoboa/frontend_dist/assets/AccountLayout-OGtZvlHR.js +1 -0
- modoboa/frontend_dist/assets/{AccountPasswordSubForm-bJ0vggm0.js → AccountPasswordSubForm-g3IEGrgM.js} +1 -1
- modoboa/frontend_dist/assets/{AccountView-zVZjR-hV.js → AccountView-DsxYqr3k.js} +1 -1
- modoboa/frontend_dist/assets/{AddressBook-lIXK7nkB.js → AddressBook-3RoKiKon.js} +1 -1
- modoboa/frontend_dist/assets/AdminLayout-CWfn8zaQ.js +1 -0
- modoboa/frontend_dist/assets/AlarmsView-D3Mh8ntf.js +1 -0
- modoboa/frontend_dist/assets/{AliasEditView-BmdncwjA.js → AliasEditView-C15eUZ11.js} +1 -1
- modoboa/frontend_dist/assets/{AliasRecipientForm-uNOU6Es3.js → AliasRecipientForm-DVZXWaUX.js} +1 -1
- modoboa/frontend_dist/assets/{AliasView-CMagk14H.js → AliasView-Cyvc5vMb.js} +1 -1
- modoboa/frontend_dist/assets/{AuditTrailView-BHBh-Gnb.js → AuditTrailView-LI2XuLLn.js} +1 -1
- modoboa/frontend_dist/assets/{CalendarView-Bt7WsWZj.js → CalendarView-BpOlPh3f.js} +1 -1
- modoboa/frontend_dist/assets/{ChoiceField-DT-S7jc3.js → ChoiceField-7eU7c_rI.js} +1 -1
- modoboa/frontend_dist/assets/{ComposeEmailForm-CMmmKn24.js → ComposeEmailForm-CNfI7ept.js} +1 -1
- modoboa/frontend_dist/assets/ComposeEmailView-B866Xsrc.js +1 -0
- modoboa/frontend_dist/assets/{ConfirmDialog-DpaDmFAr.js → ConfirmDialog-BvqxQsD1.js} +1 -1
- modoboa/frontend_dist/assets/ConnectedLayout-BQ3ug6Jh.js +1 -0
- modoboa/frontend_dist/assets/{ConnectedLayout-AhwwAZvE.css → ConnectedLayout-BaJZ3BeR.css} +1 -1
- modoboa/frontend_dist/assets/CreationForm-C9Kh05ax.js +1 -0
- modoboa/frontend_dist/assets/CreationForm-HaiQhqwK.css +1 -0
- modoboa/frontend_dist/assets/{DashboardView-DfQoFJFl.css → DashboardView-BLlMi6Qb.css} +1 -1
- modoboa/frontend_dist/assets/DashboardView-Cw-gIcuB.js +1 -0
- modoboa/frontend_dist/assets/{DomainAdminList-DzeKY_wp.js → DomainAdminList-CsWUNKVk.js} +1 -1
- modoboa/frontend_dist/assets/DomainEditView-DocxeOeW.js +1 -0
- modoboa/frontend_dist/assets/DomainTransportForm-DPnPGBOp.js +1 -0
- modoboa/frontend_dist/assets/{DomainView-DCfSLvj-.css → DomainView-BDKoBFYr.css} +1 -1
- modoboa/frontend_dist/assets/DomainView-Djc_0PsF.js +5 -0
- modoboa/frontend_dist/assets/DomainsView-B-Lxru7P.js +1 -0
- modoboa/frontend_dist/assets/{DomainsView-BUgJLN9o.css → DomainsView-DasJ0NdZ.css} +1 -1
- modoboa/frontend_dist/assets/{EmailField-5oAk_8mr.js → EmailField-BEKxuYni.js} +1 -1
- modoboa/frontend_dist/assets/{EmailView-CHtw29LH.js → EmailView-BsR1Wes5.js} +1 -1
- modoboa/frontend_dist/assets/EmptyLayout-BzPFOeLU.js +1 -0
- modoboa/frontend_dist/assets/{FiltersView-CLZD8Zeu.js → FiltersView-5rmpC5cC.js} +1 -1
- modoboa/frontend_dist/assets/ForwardEmailView-D7MbetcT.js +1 -0
- modoboa/frontend_dist/assets/{HtmlEditor-Bothw4PW.js → HtmlEditor-uM4AtIGi.js} +1 -1
- modoboa/frontend_dist/assets/IdentitiesView-BqjD9Lue.js +1 -0
- modoboa/frontend_dist/assets/IdentitiesView-jmuItyMZ.css +1 -0
- modoboa/frontend_dist/assets/{InformationView-DV9jmCfk.js → InformationView-CghcvPn2.js} +1 -1
- modoboa/frontend_dist/assets/{LoadingData-DHVSExRd.js → LoadingData-CVD2Aen8.js} +1 -1
- modoboa/frontend_dist/assets/{LoginCallbackView-CKVmv2U7.js → LoginCallbackView-sWzBke1g.js} +1 -1
- modoboa/frontend_dist/assets/{LoginView-BM4hh8GD.js → LoginView-wmN73W0f.js} +1 -1
- modoboa/frontend_dist/assets/{MailboxView-BFT6E7QZ.js → MailboxView-T_p-_ZtJ.js} +1 -1
- modoboa/frontend_dist/assets/{MenuItems-DfTqHL7X.js → MenuItems-kHCMzR5E.js} +1 -1
- modoboa/frontend_dist/assets/{MessagesView-DdJLcDON.js → MessagesView-Cerv3xsy.js} +1 -1
- modoboa/frontend_dist/assets/{MigrationsView-CUmfnPuD.js → MigrationsView-7kjqPyYU.js} +1 -1
- modoboa/frontend_dist/assets/{ParametersForm-DpxcA8Ib.js → ParametersForm-BCeQljir.js} +1 -1
- modoboa/frontend_dist/assets/ParametersView-DspBxVMV.js +1 -0
- modoboa/frontend_dist/assets/ParametersView-Dy0H5ep1.js +1 -0
- modoboa/frontend_dist/assets/{ProviderEditView-DX4PGAI_.js → ProviderEditView-DDLMOylC.js} +1 -1
- modoboa/frontend_dist/assets/{ProviderGeneralForm-Bn5Bb3Qn.js → ProviderGeneralForm-BwOSKNHK.js} +1 -1
- modoboa/frontend_dist/assets/{ProvidersView-BbwFDjef.js → ProvidersView-C99UD0WB.js} +1 -1
- modoboa/frontend_dist/assets/ReplyEmailView-DAPBHldd.js +1 -0
- modoboa/frontend_dist/assets/{ResourcesForm-DmXW37Xr.js → ResourcesForm-D87PHeH0.js} +1 -1
- modoboa/frontend_dist/assets/{SettingsView-CDZADq3D.js → SettingsView-OxDo9wNd.js} +2 -2
- modoboa/frontend_dist/assets/{StatisticsView-SQdOdBYh.js → StatisticsView-CM__Eqku.js} +1 -1
- modoboa/frontend_dist/assets/{TimeSerieChart-D2qkZzVF.js → TimeSerieChart-C6j0uYR_.js} +1 -1
- modoboa/frontend_dist/assets/UserLayout-CckCGnPS.js +1 -0
- modoboa/frontend_dist/assets/{VAlert-CEP3cFn3.js → VAlert-B7mzOJIO.js} +1 -1
- modoboa/frontend_dist/assets/{VApp-DPJ3HTOA.js → VApp-BKxnjOoi.js} +1 -1
- modoboa/frontend_dist/assets/{VAutocomplete-CNumFmgr.js → VAutocomplete-Cc4_tcl1.js} +1 -1
- modoboa/frontend_dist/assets/{VAvatar-BlQCGAjd.js → VAvatar-BAgTUIvX.js} +1 -1
- modoboa/frontend_dist/assets/{VCard-_Wrwy8vE.js → VCard-DFWiFORP.js} +1 -1
- modoboa/frontend_dist/assets/{VCheckbox-2el3v3GD.js → VCheckbox-CKsH_vq3.js} +1 -1
- modoboa/frontend_dist/assets/{VCheckboxBtn-hftATz3z.js → VCheckboxBtn-j7di4leN.js} +1 -1
- modoboa/frontend_dist/assets/{VChip-YJr5NpVo.js → VChip-nZ0uhY7t.js} +1 -1
- modoboa/frontend_dist/assets/{VColorPicker-BeyI--Be.js → VColorPicker-Os2aeP6J.js} +1 -1
- modoboa/frontend_dist/assets/VContainer-Btam4lk2.js +1 -0
- modoboa/frontend_dist/assets/{VDataTable-DEL5kb0r.js → VDataTable-D_0_xJTl.js} +1 -1
- modoboa/frontend_dist/assets/{VDataTableServer-BSjI3T24.js → VDataTableServer-BWTt4Mzi.js} +1 -1
- modoboa/frontend_dist/assets/{VDataTableVirtual-CAmjoZpK.js → VDataTableVirtual-BwVmkt4u.js} +1 -1
- modoboa/frontend_dist/assets/{VDialog-BNvBrVUr.js → VDialog-CZqM2Ofu.js} +1 -1
- modoboa/frontend_dist/assets/{VExpansionPanels-b3QiyPDE.js → VExpansionPanels-B5D6GOa3.js} +1 -1
- modoboa/frontend_dist/assets/{VFileInput-B_0qadyu.js → VFileInput-Cv9DIPki.js} +1 -1
- modoboa/frontend_dist/assets/{VForm-BZayccL6.js → VForm-CpoZf60D.js} +1 -1
- modoboa/frontend_dist/assets/{VMenu-CTWHnLTn.js → VMenu-B_dVqOmo.js} +1 -1
- modoboa/frontend_dist/assets/{VPicker-jsHPuz0p.js → VPicker-CXkIGEze.js} +1 -1
- modoboa/frontend_dist/assets/{VProgressCircular-Bnqidj3C.js → VProgressCircular-CrEXxs7k.js} +1 -1
- modoboa/frontend_dist/assets/{VRadioGroup-s4-ckZ2k.js → VRadioGroup-D8ypjYOO.js} +1 -1
- modoboa/frontend_dist/assets/{VRow-Feh4EzZW.js → VRow-C_Ydf6yr.js} +1 -1
- modoboa/frontend_dist/assets/{VSelect-NMZYMJds.js → VSelect-CS51PDEt.js} +1 -1
- modoboa/frontend_dist/assets/{VSelectionControl-DzdbYleR.js → VSelectionControl-DiOqtY38.js} +1 -1
- modoboa/frontend_dist/assets/VSheet-5VVWtHvs.js +1 -0
- modoboa/frontend_dist/assets/VSpacer-C6PZ3X24.js +1 -0
- modoboa/frontend_dist/assets/{VSwitch-BNTZUUIk.js → VSwitch-Chg5o-Cp.js} +1 -1
- modoboa/frontend_dist/assets/{VTable-DKtDp0hU.js → VTable-KsiZ3cBz.js} +1 -1
- modoboa/frontend_dist/assets/{VTabs-DgRFLQFX.js → VTabs-tNrJIYO0.js} +1 -1
- modoboa/frontend_dist/assets/{VTextField-B1sRHgeW.js → VTextField-CLwRV0Cb.js} +1 -1
- modoboa/frontend_dist/assets/{VTextarea-B-FbARtV.js → VTextarea-Di8jbl8m.js} +1 -1
- modoboa/frontend_dist/assets/{VToolbar-BYYuEslS.js → VToolbar-CGwhgdmI.js} +1 -1
- modoboa/frontend_dist/assets/{VWindowItem-ZP4a7tq_.js → VWindowItem-wWSFAGI-.js} +1 -1
- modoboa/frontend_dist/assets/{WebmailLayout-DXdBsd-A.js → WebmailLayout-DsYThBrz.js} +1 -1
- modoboa/frontend_dist/assets/{admin-Crbc8EzJ.js → admin-CJVLMHh9.js} +1 -1
- modoboa/frontend_dist/assets/{aliases-DLwQ9O5G.js → aliases-c3n-dCV_.js} +1 -1
- modoboa/frontend_dist/assets/{contacts-yHT-CIHj.js → contacts-Bqckz8sr.js} +1 -1
- modoboa/frontend_dist/assets/{domains-_NaRU-qe.js → domains-nBMR-fRf.js} +1 -1
- modoboa/frontend_dist/assets/{domains.store-DOrci7Lr.js → domains.store-ChZgLcqP.js} +1 -1
- modoboa/frontend_dist/assets/{filter-_-hv7dYy.js → filter-D7NrAf6a.js} +1 -1
- modoboa/frontend_dist/assets/{forwardRefs-BySF_L7N.js → forwardRefs-Cpc3YYl6.js} +1 -1
- modoboa/frontend_dist/assets/{global.store-CkyRP6fP.js → global.store-DUP26-A5.js} +1 -1
- modoboa/frontend_dist/assets/importExport-Dn9vYw7T.js +1 -0
- modoboa/frontend_dist/assets/{index-BQtPj9kp.js → index-DV9Li2cg.js} +19 -19
- modoboa/frontend_dist/assets/{index-DzLisnN3.js → index-DzL89N4E.js} +1 -1
- modoboa/frontend_dist/assets/layout-CHO37cA6.js +1 -0
- modoboa/frontend_dist/assets/{layout.store-D1RpozRo.js → layout.store-BxBoBlgf.js} +1 -1
- modoboa/frontend_dist/assets/{logs-O2XN95C5.js → logs-DrTzylW7.js} +1 -1
- modoboa/frontend_dist/assets/{parameters-BlgF3Z86.js → parameters-Lgiqp7aw.js} +1 -1
- modoboa/frontend_dist/assets/{parameters.store-BeAYoSBO.js → parameters.store-C9k9DuTj.js} +1 -1
- modoboa/frontend_dist/assets/{permissions-CZTQi1hx.js → permissions-mL5hLHcW.js} +1 -1
- modoboa/frontend_dist/assets/{ssrBoot-D7JTzupl.js → ssrBoot-BsxW6uW4.js} +1 -1
- modoboa/frontend_dist/assets/{tag-DczKoIjT.js → tag-CFK9dzMJ.js} +1 -1
- modoboa/frontend_dist/assets/transports-TO08iTXJ.js +1 -0
- modoboa/frontend_dist/assets/{webmail-Dk1MjSSb.js → webmail-KrD8ZhNM.js} +1 -1
- modoboa/frontend_dist/config.json +1 -1
- modoboa/frontend_dist/index.html +1 -1
- modoboa/maillog/management/commands/logparser.py +1 -0
- modoboa/pdfcredentials/api/v2/serializers.py +17 -5
- modoboa/pdfcredentials/api/v2/tests.py +28 -0
- modoboa/pdfcredentials/app_settings.py +1 -1
- {modoboa-2.4.8.dist-info → modoboa-2.4.10.dist-info}/METADATA +2 -2
- {modoboa-2.4.8.dist-info → modoboa-2.4.10.dist-info}/RECORD +137 -136
- modoboa/frontend_dist/assets/AccountLayout-CdO0SiyN.js +0 -1
- modoboa/frontend_dist/assets/AdminLayout-lq_An0r_.js +0 -1
- modoboa/frontend_dist/assets/AlarmsView-BAiihAQo.js +0 -1
- modoboa/frontend_dist/assets/ComposeEmailView-DCktvQ_Q.js +0 -1
- modoboa/frontend_dist/assets/ConnectedLayout-C9ym17MK.js +0 -1
- modoboa/frontend_dist/assets/CreationForm-BvFT2siw.js +0 -1
- modoboa/frontend_dist/assets/CreationForm-DLumMdUy.css +0 -1
- modoboa/frontend_dist/assets/DashboardView-B-2znOCL.js +0 -1
- modoboa/frontend_dist/assets/DomainEditView-BXkqO64o.js +0 -1
- modoboa/frontend_dist/assets/DomainTransportForm-DgCR3Vtg.js +0 -1
- modoboa/frontend_dist/assets/DomainView-CwqRXXjc.js +0 -5
- modoboa/frontend_dist/assets/DomainsView-7RpzOtVN.js +0 -1
- modoboa/frontend_dist/assets/EmptyLayout-DjwbFWme.js +0 -1
- modoboa/frontend_dist/assets/ForwardEmailView-BqD8yMYF.js +0 -1
- modoboa/frontend_dist/assets/IdentitiesView-CeqGJ0Dz.js +0 -1
- modoboa/frontend_dist/assets/IdentitiesView-cvIq8q24.css +0 -1
- modoboa/frontend_dist/assets/ParametersView-CVc4kkYy.js +0 -1
- modoboa/frontend_dist/assets/ParametersView-Dwy8L1wH.js +0 -1
- modoboa/frontend_dist/assets/ReplyEmailView-DnaqKPIc.js +0 -1
- modoboa/frontend_dist/assets/UserLayout-BfuBes96.js +0 -1
- modoboa/frontend_dist/assets/VContainer-CFkETYWT.js +0 -1
- modoboa/frontend_dist/assets/VSheet-DHWcony1.js +0 -1
- modoboa/frontend_dist/assets/VSpacer-C9mNCXnQ.js +0 -1
- modoboa/frontend_dist/assets/importExport-BdyTHtRb.js +0 -1
- modoboa/frontend_dist/assets/layout-Dlgl7jya.js +0 -1
- {modoboa-2.4.8.data → modoboa-2.4.10.data}/scripts/modoboa-admin.py +0 -0
- {modoboa-2.4.8.dist-info → modoboa-2.4.10.dist-info}/WHEEL +0 -0
- {modoboa-2.4.8.dist-info → modoboa-2.4.10.dist-info}/entry_points.txt +0 -0
- {modoboa-2.4.8.dist-info → modoboa-2.4.10.dist-info}/licenses/LICENSE +0 -0
- {modoboa-2.4.8.dist-info → modoboa-2.4.10.dist-info}/top_level.txt +0 -0
|
@@ -68,12 +68,18 @@ class DomainSerializer(serializers.ModelSerializer):
|
|
|
68
68
|
"message_limit",
|
|
69
69
|
"creation",
|
|
70
70
|
"last_modification",
|
|
71
|
+
"allocated_quota_in_percent",
|
|
72
|
+
"mailbox_count",
|
|
73
|
+
"mbalias_count",
|
|
74
|
+
"domainalias_count",
|
|
75
|
+
"enable_dns_checks",
|
|
71
76
|
)
|
|
72
77
|
read_only_fields = (
|
|
73
78
|
"pk",
|
|
74
79
|
"dkim_public_key",
|
|
75
80
|
"dns_global_status",
|
|
76
|
-
"allocated_quota_in_percent"
|
|
81
|
+
"allocated_quota_in_percent",
|
|
82
|
+
"mailbox_count",
|
|
77
83
|
"mbalias_count",
|
|
78
84
|
"domainalias_count",
|
|
79
85
|
"enable_dns_checks",
|
|
@@ -61,6 +61,8 @@ class DomainSerializer(v1_serializers.DomainSerializer):
|
|
|
61
61
|
"domain_admin",
|
|
62
62
|
"transport",
|
|
63
63
|
"opened_alarms_count",
|
|
64
|
+
"sent_messages",
|
|
65
|
+
"sent_messages_in_percent",
|
|
64
66
|
)
|
|
65
67
|
|
|
66
68
|
def __init__(self, *args, **kwargs):
|
|
@@ -221,9 +223,9 @@ class AdminGlobalParametersSerializer(serializers.Serializer):
|
|
|
221
223
|
enable_dkim_checks = serializers.BooleanField(default=True)
|
|
222
224
|
enable_dmarc_checks = serializers.BooleanField(default=True)
|
|
223
225
|
enable_autoconfig_checks = serializers.BooleanField(default=True)
|
|
224
|
-
custom_dns_server = serializers.IPAddressField(allow_blank=True)
|
|
226
|
+
custom_dns_server = serializers.IPAddressField(allow_blank=True, allow_null=True)
|
|
225
227
|
enable_dnsbl_checks = serializers.BooleanField(default=True)
|
|
226
|
-
dkim_keys_storage_dir = serializers.CharField(allow_blank=True)
|
|
228
|
+
dkim_keys_storage_dir = serializers.CharField(allow_blank=True, allow_null=True)
|
|
227
229
|
dkim_default_key_length = serializers.ChoiceField(
|
|
228
230
|
default=2048, choices=constants.DKIM_KEY_LENGTHS
|
|
229
231
|
)
|
|
@@ -741,6 +743,11 @@ class AlarmSerializer(serializers.ModelSerializer):
|
|
|
741
743
|
model = models.Alarm
|
|
742
744
|
|
|
743
745
|
|
|
746
|
+
class AlarmBulkDeleteSerializer(serializers.Serializer):
|
|
747
|
+
|
|
748
|
+
ids = serializers.ListField(child=serializers.IntegerField())
|
|
749
|
+
|
|
750
|
+
|
|
744
751
|
class AlarmSwitchStatusSerializer(serializers.Serializer):
|
|
745
752
|
"""Serializer to switch the status of an Alarm."""
|
|
746
753
|
|
modoboa/admin/api/v2/tests.py
CHANGED
|
@@ -806,9 +806,10 @@ class AlarmViewSetTestCase(ModoAPITestCase):
|
|
|
806
806
|
|
|
807
807
|
def test_bulk_delete(self):
|
|
808
808
|
url = reverse("v2:alarm-bulk-delete")
|
|
809
|
-
resp = self.client.
|
|
809
|
+
resp = self.client.post(url)
|
|
810
810
|
self.assertEqual(resp.status_code, 400)
|
|
811
|
-
|
|
811
|
+
data = {"ids": ["toto"]}
|
|
812
|
+
resp = self.client.post(url, data, format="json")
|
|
812
813
|
self.assertEqual(resp.status_code, 400)
|
|
813
814
|
alarm1 = factories.AlarmFactory(
|
|
814
815
|
domain__name="test.com", mailbox=None, title="Test alarm"
|
|
@@ -816,7 +817,8 @@ class AlarmViewSetTestCase(ModoAPITestCase):
|
|
|
816
817
|
alarm2 = factories.AlarmFactory(
|
|
817
818
|
domain__name="test.com", mailbox=None, title="Test alarm"
|
|
818
819
|
)
|
|
819
|
-
|
|
820
|
+
data = {"ids": [alarm1.pk, alarm2.pk]}
|
|
821
|
+
resp = self.client.post(url, data, format="json")
|
|
820
822
|
self.assertEqual(resp.status_code, 204)
|
|
821
823
|
with self.assertRaises(models.Alarm.DoesNotExist):
|
|
822
824
|
alarm1.refresh_from_db()
|
modoboa/admin/api/v2/viewsets.py
CHANGED
|
@@ -387,11 +387,18 @@ class AlarmViewSet(
|
|
|
387
387
|
.order_by("-created")
|
|
388
388
|
)
|
|
389
389
|
|
|
390
|
+
def get_serializer_class(self, *args, **kwargs):
|
|
391
|
+
if self.action == "bulk_delete":
|
|
392
|
+
return serializers.AlarmBulkDeleteSerializer
|
|
393
|
+
elif self.action == "switch":
|
|
394
|
+
return serializers.AlarmSwitchStatusSerializer
|
|
395
|
+
return serializers.AlarmSerializer
|
|
396
|
+
|
|
390
397
|
@action(methods=["patch"], detail=True)
|
|
391
398
|
def switch(self, request, **kwargs):
|
|
392
399
|
"""Custom update method that switch status of an alarm."""
|
|
393
400
|
alarm = self.get_object()
|
|
394
|
-
serializer =
|
|
401
|
+
serializer = self.get_serializer(data=request.data)
|
|
395
402
|
serializer.is_valid(raise_exception=True)
|
|
396
403
|
if serializer.data["status"] == constants.ALARM_CLOSED:
|
|
397
404
|
alarm.close()
|
|
@@ -399,15 +406,10 @@ class AlarmViewSet(
|
|
|
399
406
|
alarm.reopen()
|
|
400
407
|
return response.Response(status=status.HTTP_204_NO_CONTENT)
|
|
401
408
|
|
|
402
|
-
@action(methods=["
|
|
409
|
+
@action(methods=["post"], detail=False)
|
|
403
410
|
def bulk_delete(self, request, **kwargs):
|
|
404
411
|
"""Delete multiple alarms at the same time."""
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
try:
|
|
409
|
-
ids = [int(alarm_id) for alarm_id in ids]
|
|
410
|
-
except ValueError:
|
|
411
|
-
return response.Response(_("Received invalid alarm id(s)"), status=400)
|
|
412
|
-
models.Alarm.objects.filter(pk__in=ids).delete()
|
|
412
|
+
serializer = self.get_serializer(data=request.data)
|
|
413
|
+
serializer.is_valid(raise_exception=True)
|
|
414
|
+
models.Alarm.objects.filter(pk__in=serializer.validated_data["ids"]).delete()
|
|
413
415
|
return response.Response(status=204)
|
|
@@ -76,8 +76,12 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
|
|
|
76
76
|
default_password = serializers.CharField(default="ChangeMe1!")
|
|
77
77
|
random_password_length = serializers.IntegerField(min_value=8, default=8)
|
|
78
78
|
allow_special_characters = serializers.BooleanField(default=False)
|
|
79
|
-
update_password_url = serializers.URLField(
|
|
80
|
-
|
|
79
|
+
update_password_url = serializers.URLField(
|
|
80
|
+
required=False, allow_blank=True, allow_null=True
|
|
81
|
+
)
|
|
82
|
+
password_recovery_msg = serializers.CharField(
|
|
83
|
+
required=False, allow_blank=True, allow_null=True
|
|
84
|
+
)
|
|
81
85
|
sms_password_recovery = serializers.BooleanField(default=False)
|
|
82
86
|
sms_provider = serializers.ChoiceField(
|
|
83
87
|
choices=constants.SMS_BACKENDS, required=False, allow_null=True
|
|
@@ -87,7 +91,9 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
|
|
|
87
91
|
ldap_server_address = serializers.CharField(default="localhost")
|
|
88
92
|
ldap_server_port = serializers.IntegerField(default=389)
|
|
89
93
|
ldap_enable_secondary_server = serializers.BooleanField(default=False)
|
|
90
|
-
ldap_secondary_server_address = serializers.CharField(
|
|
94
|
+
ldap_secondary_server_address = serializers.CharField(
|
|
95
|
+
required=False, allow_blank=True, allow_null=True
|
|
96
|
+
)
|
|
91
97
|
ldap_secondary_server_port = serializers.IntegerField(default=389, required=False)
|
|
92
98
|
ldap_secured = serializers.ChoiceField(
|
|
93
99
|
choices=constants.LDAP_SECURE_MODES, default="none"
|
|
@@ -109,9 +115,11 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
|
|
|
109
115
|
choices=constants.LDAP_AUTH_METHODS,
|
|
110
116
|
default="searchbind",
|
|
111
117
|
)
|
|
112
|
-
ldap_bind_dn = serializers.CharField(
|
|
118
|
+
ldap_bind_dn = serializers.CharField(
|
|
119
|
+
default="", required=False, allow_blank=True, allow_null=True
|
|
120
|
+
)
|
|
113
121
|
ldap_bind_password = serializers.CharField(
|
|
114
|
-
default="", required=False, allow_blank=True
|
|
122
|
+
default="", required=False, allow_blank=True, allow_null=True
|
|
115
123
|
)
|
|
116
124
|
ldap_search_base = serializers.CharField(
|
|
117
125
|
default="", required=False, allow_blank=True
|
|
@@ -124,15 +132,21 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
|
|
|
124
132
|
)
|
|
125
133
|
|
|
126
134
|
# LDAP sync settings
|
|
127
|
-
ldap_sync_bind_dn = serializers.CharField(
|
|
128
|
-
|
|
135
|
+
ldap_sync_bind_dn = serializers.CharField(
|
|
136
|
+
required=False, allow_blank=True, allow_null=True
|
|
137
|
+
)
|
|
138
|
+
ldap_sync_bind_password = serializers.CharField(
|
|
139
|
+
required=False, allow_blank=True, allow_null=True
|
|
140
|
+
)
|
|
129
141
|
ldap_enable_sync = serializers.BooleanField(default=False)
|
|
130
142
|
ldap_sync_delete_remote_account = serializers.BooleanField(default=False)
|
|
131
143
|
ldap_sync_account_dn_template = serializers.CharField(
|
|
132
|
-
required=False, allow_blank=True
|
|
144
|
+
required=False, allow_blank=True, allow_null=True
|
|
133
145
|
)
|
|
134
146
|
ldap_enable_import = serializers.BooleanField(default=False)
|
|
135
|
-
ldap_import_search_base = serializers.CharField(
|
|
147
|
+
ldap_import_search_base = serializers.CharField(
|
|
148
|
+
required=False, allow_blank=True, allow_null=True
|
|
149
|
+
)
|
|
136
150
|
ldap_import_search_filter = serializers.CharField(default="(cn=*)", required=False)
|
|
137
151
|
ldap_import_username_attr = serializers.CharField(default="cn")
|
|
138
152
|
ldap_dovecot_sync = serializers.BooleanField(default=False)
|
|
@@ -141,7 +155,9 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
|
|
|
141
155
|
)
|
|
142
156
|
|
|
143
157
|
# Dashboard settings
|
|
144
|
-
custom_welcome_message = serializers.CharField(
|
|
158
|
+
custom_welcome_message = serializers.CharField(
|
|
159
|
+
required=False, allow_blank=True, allow_null=True
|
|
160
|
+
)
|
|
145
161
|
rss_feed_url = serializers.URLField(
|
|
146
162
|
allow_blank=True, required=False, allow_null=True
|
|
147
163
|
)
|
|
@@ -162,7 +178,9 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
|
|
|
162
178
|
enable_api_communication = serializers.BooleanField(default=True)
|
|
163
179
|
check_new_versions = serializers.BooleanField(default=True)
|
|
164
180
|
send_new_versions_email = serializers.BooleanField(default=False)
|
|
165
|
-
new_versions_email_rcpt = lib_fields.DRFEmailFieldUTF8(
|
|
181
|
+
new_versions_email_rcpt = lib_fields.DRFEmailFieldUTF8(
|
|
182
|
+
required=False, allow_null=True
|
|
183
|
+
)
|
|
166
184
|
send_statistics = serializers.BooleanField(default=True)
|
|
167
185
|
|
|
168
186
|
# Misc settings
|
|
@@ -195,10 +213,11 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
|
|
|
195
213
|
return value
|
|
196
214
|
|
|
197
215
|
def validate_ldap_sync_account_dn_template(self, value):
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
216
|
+
if value:
|
|
217
|
+
try:
|
|
218
|
+
value % {"user": "toto"}
|
|
219
|
+
except (KeyError, ValueError):
|
|
220
|
+
raise serializers.ValidationError(_("Invalid syntax")) from None
|
|
202
221
|
return value
|
|
203
222
|
|
|
204
223
|
def validate_ldap_search_filter(self, value):
|
modoboa/core/api/v2/tests.py
CHANGED
|
@@ -19,7 +19,6 @@ from modoboa.admin import (
|
|
|
19
19
|
from modoboa.core import models, constants
|
|
20
20
|
from modoboa.core.tests import utils
|
|
21
21
|
from modoboa.lib.tests import ModoAPITestCase
|
|
22
|
-
from rest_framework.authtoken.models import Token
|
|
23
22
|
|
|
24
23
|
DOVEADM_TEST_PATH = utils.get_doveadm_test_path()
|
|
25
24
|
DOVECOT_USER = getpass.getuser()
|
|
@@ -183,6 +182,13 @@ class ParametersAPITestCase(ModoAPITestCase):
|
|
|
183
182
|
|
|
184
183
|
|
|
185
184
|
class AccountViewSetTestCase(ModoAPITestCase):
|
|
185
|
+
|
|
186
|
+
@classmethod
|
|
187
|
+
def setUpTestData(cls): # NOQA:N802
|
|
188
|
+
"""Create test data."""
|
|
189
|
+
super().setUpTestData()
|
|
190
|
+
factories.populate_database()
|
|
191
|
+
|
|
186
192
|
def test_me(self):
|
|
187
193
|
url = reverse("v2:account-me")
|
|
188
194
|
resp = self.client.get(url)
|
|
@@ -261,6 +267,57 @@ class AccountViewSetTestCase(ModoAPITestCase):
|
|
|
261
267
|
user.refresh_from_db()
|
|
262
268
|
self.assertEqual(user.totp_enabled, False)
|
|
263
269
|
|
|
270
|
+
def test_available_applications(self):
|
|
271
|
+
url = reverse("v2:account-available-applications")
|
|
272
|
+
resp = self.client.get(url)
|
|
273
|
+
self.assertEqual(resp.status_code, 200)
|
|
274
|
+
# admin -> only 1 app.
|
|
275
|
+
self.assertEqual(len(resp.json()), 1)
|
|
276
|
+
|
|
277
|
+
# Domain admin with mailbox
|
|
278
|
+
dadmin = models.User.objects.get(username="admin@test.com")
|
|
279
|
+
self.authenticate_user(dadmin)
|
|
280
|
+
resp = self.client.get(url)
|
|
281
|
+
self.assertEqual(resp.status_code, 200)
|
|
282
|
+
self.assertEqual(len(resp.json()), 4)
|
|
283
|
+
|
|
284
|
+
# Simple user
|
|
285
|
+
user = models.User.objects.get(username="user@test.com")
|
|
286
|
+
self.authenticate_user(user)
|
|
287
|
+
resp = self.client.get(url)
|
|
288
|
+
self.assertEqual(resp.status_code, 200)
|
|
289
|
+
self.assertEqual(len(resp.json()), 3)
|
|
290
|
+
|
|
291
|
+
@override_settings(
|
|
292
|
+
MODOBOA_APPS=[
|
|
293
|
+
"modoboa",
|
|
294
|
+
"modoboa.core",
|
|
295
|
+
"modoboa.lib",
|
|
296
|
+
"modoboa.admin",
|
|
297
|
+
"modoboa.transport",
|
|
298
|
+
"modoboa.relaydomains",
|
|
299
|
+
"modoboa.limits",
|
|
300
|
+
"modoboa.parameters",
|
|
301
|
+
"modoboa.dnstools",
|
|
302
|
+
"modoboa.policyd",
|
|
303
|
+
"modoboa.maillog",
|
|
304
|
+
"modoboa.pdfcredentials",
|
|
305
|
+
"modoboa.dmarc",
|
|
306
|
+
"modoboa.imap_migration",
|
|
307
|
+
"modoboa.autoreply",
|
|
308
|
+
"modoboa.sievefilters",
|
|
309
|
+
"modoboa.rspamd",
|
|
310
|
+
]
|
|
311
|
+
)
|
|
312
|
+
def test_available_applications_with_disabled(self):
|
|
313
|
+
url = reverse("v2:account-available-applications")
|
|
314
|
+
# Domain admin with mailbox
|
|
315
|
+
dadmin = models.User.objects.get(username="admin@test.com")
|
|
316
|
+
self.authenticate_user(dadmin)
|
|
317
|
+
resp = self.client.get(url)
|
|
318
|
+
self.assertEqual(resp.status_code, 200)
|
|
319
|
+
self.assertEqual(len(resp.json()), 1)
|
|
320
|
+
|
|
264
321
|
|
|
265
322
|
class PasswordResetTestCase(AccountViewSetTestCase):
|
|
266
323
|
def __init__(self, *args, **kwargs):
|
|
@@ -268,15 +325,6 @@ class PasswordResetTestCase(AccountViewSetTestCase):
|
|
|
268
325
|
self.id = 0
|
|
269
326
|
self.sms_token = 0
|
|
270
327
|
|
|
271
|
-
@classmethod
|
|
272
|
-
def setUpTestData(cls): # NOQA:N802
|
|
273
|
-
"""Create test data."""
|
|
274
|
-
super().setUpTestData()
|
|
275
|
-
factories.populate_database()
|
|
276
|
-
cls.da_token = Token.objects.create(
|
|
277
|
-
user=models.User.objects.get(username="admin@test.com")
|
|
278
|
-
)
|
|
279
|
-
|
|
280
328
|
@mock.patch("ovh.Client.get")
|
|
281
329
|
@mock.patch("ovh.Client.post")
|
|
282
330
|
def test_reset_password(self, client_post, client_get):
|
modoboa/core/api/v2/viewsets.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Core API v2 viewsets."""
|
|
2
2
|
|
|
3
|
+
from django.conf import settings
|
|
3
4
|
from django.utils.translation import gettext as _
|
|
4
5
|
|
|
5
6
|
from django_otp.plugins.otp_static.models import StaticDevice, StaticToken
|
|
@@ -166,29 +167,37 @@ class AccountViewSet(core_v1_viewsets.AccountViewSet):
|
|
|
166
167
|
}
|
|
167
168
|
)
|
|
168
169
|
if hasattr(request.user, "mailbox"):
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
170
|
+
if "modoboa.contacts" in settings.MODOBOA_APPS:
|
|
171
|
+
apps += [
|
|
172
|
+
{
|
|
173
|
+
"name": "contacts",
|
|
174
|
+
"label": _("Contacts"),
|
|
175
|
+
"icon": "mdi-contacts-outline",
|
|
176
|
+
"description": _("Address book"),
|
|
177
|
+
"url": "/user/contacts",
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
if "modoboa.calendars" in settings.MODOBOA_APPS:
|
|
181
|
+
apps += [
|
|
182
|
+
{
|
|
183
|
+
"name": "calendar",
|
|
184
|
+
"label": _("Calendars"),
|
|
185
|
+
"icon": "mdi-calendar",
|
|
186
|
+
"description": _("Calendar"),
|
|
187
|
+
"url": "/user/calendars",
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
if "modoboa.webmail" in settings.MODOBOA_APPS:
|
|
191
|
+
apps += [
|
|
192
|
+
{
|
|
193
|
+
"name": "webmail",
|
|
194
|
+
"label": _("Webmail"),
|
|
195
|
+
"icon": "mdi-at",
|
|
196
|
+
"description": _("Webmail"),
|
|
197
|
+
"url": "/user/webmail",
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
|
|
192
201
|
apps += exts_pool.get_available_apps()
|
|
193
202
|
serializer = serializers.ModoboaApplicationSerializer(apps, many=True)
|
|
194
203
|
return response.Response(serializer.data)
|
modoboa/core/app_settings.py
CHANGED
|
@@ -649,7 +649,7 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict(
|
|
|
649
649
|
"new_versions_email_rcpt",
|
|
650
650
|
{
|
|
651
651
|
"label": gettext_lazy("Recipient"),
|
|
652
|
-
"display": "
|
|
652
|
+
"display": "send_new_versions_email=true",
|
|
653
653
|
"help_text": gettext_lazy(
|
|
654
654
|
"Recipient of new versions notification emails."
|
|
655
655
|
),
|
|
@@ -57,6 +57,12 @@ class Command(BaseCommand):
|
|
|
57
57
|
default=False,
|
|
58
58
|
help="Omit everything related to frontend initialisation",
|
|
59
59
|
)
|
|
60
|
+
parser.add_argument(
|
|
61
|
+
"--relative-urls-in-config",
|
|
62
|
+
action="store_true",
|
|
63
|
+
default=False,
|
|
64
|
+
help="Use relative urls in generated config.json file",
|
|
65
|
+
)
|
|
60
66
|
|
|
61
67
|
def handle(self, *args, **options):
|
|
62
68
|
"""Command entry point."""
|
|
@@ -117,7 +123,10 @@ class Command(BaseCommand):
|
|
|
117
123
|
base_uris = " ".join(base_uris_list)
|
|
118
124
|
base_uri = base_uris_list[0]
|
|
119
125
|
redirect_uris = " ".join([f"{uri}/login/logged" for uri in base_uris_list])
|
|
120
|
-
|
|
126
|
+
if not options["relative_urls_in_config"]:
|
|
127
|
+
redirect_uri = redirect_uris.split(" ")[0]
|
|
128
|
+
else:
|
|
129
|
+
redirect_uri = "/login/logged"
|
|
121
130
|
client_id = ""
|
|
122
131
|
if options["dev"]:
|
|
123
132
|
base_uri = "https://localhost:3000/"
|
|
@@ -150,10 +159,6 @@ class Command(BaseCommand):
|
|
|
150
159
|
os.path.dirname(__file__), "../../../frontend_dist/"
|
|
151
160
|
)
|
|
152
161
|
frontend_target_dir = f"{settings.BASE_DIR}/frontend"
|
|
153
|
-
if hasattr(settings, "MODOBOA_CUSTOM_LOGO"):
|
|
154
|
-
logo_path = settings.MODOBOA_CUSTOM_LOGO
|
|
155
|
-
else:
|
|
156
|
-
logo_path = f"{frontend_target_dir}/assets/Modoboa_RVB-BLANC-SANS.png"
|
|
157
162
|
if os.path.isdir(base_frontend_dir):
|
|
158
163
|
shutil.rmtree(frontend_target_dir, ignore_errors=True)
|
|
159
164
|
os.makedirs(frontend_target_dir, exist_ok=True)
|
|
@@ -164,16 +169,26 @@ class Command(BaseCommand):
|
|
|
164
169
|
f"{frontend_target_dir}/{entry.name}",
|
|
165
170
|
target_is_directory=entry.is_dir(),
|
|
166
171
|
)
|
|
172
|
+
api_base_url = "/api/v2"
|
|
173
|
+
api_doc_url = "/api/schema-v2/swagger/"
|
|
174
|
+
oauth_authority_url = "/api/o"
|
|
175
|
+
if not options["relative_urls_in_config"]:
|
|
176
|
+
api_base_url = f"{base_uri}{api_base_url}"
|
|
177
|
+
api_doc_url = f"{base_uri}{base_uri}"
|
|
178
|
+
oauth_authority_url = f"{base_uri}{oauth_authority_url}"
|
|
179
|
+
oauth_post_logout_redirect_uri = base_uri
|
|
180
|
+
else:
|
|
181
|
+
oauth_post_logout_redirect_uri = ""
|
|
182
|
+
|
|
167
183
|
with open(f"{frontend_target_dir}/config.json", "w") as fp:
|
|
168
184
|
fp.write(
|
|
169
185
|
f"""{{
|
|
170
|
-
"API_BASE_URL": "{
|
|
171
|
-
"API_DOC_URL": "{
|
|
172
|
-
"OAUTH_AUTHORITY_URL": "{
|
|
186
|
+
"API_BASE_URL": "{api_base_url}",
|
|
187
|
+
"API_DOC_URL": "{api_doc_url}",
|
|
188
|
+
"OAUTH_AUTHORITY_URL": "{oauth_authority_url}",
|
|
173
189
|
"OAUTH_CLIENT_ID": "{client_id}",
|
|
174
190
|
"OAUTH_REDIRECT_URI": "{redirect_uri}",
|
|
175
|
-
"OAUTH_POST_REDIRECT_URI": "{
|
|
176
|
-
"MENU_LOGO_PATH": "{logo_path}"
|
|
191
|
+
"OAUTH_POST_REDIRECT_URI": "{oauth_post_logout_redirect_uri}"
|
|
177
192
|
}}
|
|
178
193
|
"""
|
|
179
194
|
)
|
|
@@ -9,9 +9,9 @@ class Migration(migrations.Migration):
|
|
|
9
9
|
]
|
|
10
10
|
|
|
11
11
|
operations = [
|
|
12
|
-
migrations.RenameIndex(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
),
|
|
12
|
+
# migrations.RenameIndex(
|
|
13
|
+
# model_name="user",
|
|
14
|
+
# new_name="core_user_email_c0c03f_idx",
|
|
15
|
+
# old_fields=("email", "is_active"),
|
|
16
|
+
# ),
|
|
17
17
|
]
|
modoboa/core/tests/test_core.py
CHANGED
|
@@ -86,20 +86,61 @@ class ManagementCommandsTestCase(SimpleModoTestCase):
|
|
|
86
86
|
self.addCleanup(shutil.rmtree, frontend_src)
|
|
87
87
|
|
|
88
88
|
frontend_dir = pathlib.Path(settings.BASE_DIR) / "frontend"
|
|
89
|
-
self.assertFalse(
|
|
89
|
+
self.assertFalse(
|
|
90
|
+
frontend_dir.exists(),
|
|
91
|
+
f"frontend directory {frontend_dir} doesn’t exist before load_initial_data",
|
|
92
|
+
)
|
|
90
93
|
management.call_command("load_initial_data")
|
|
91
|
-
self.assertTrue(
|
|
94
|
+
self.assertTrue(
|
|
95
|
+
frontend_dir.is_dir(),
|
|
96
|
+
f"frontend directory {frontend_dir} is directory after load_initial_data",
|
|
97
|
+
)
|
|
92
98
|
for entry in frontend_dir.iterdir():
|
|
93
99
|
if entry.name == "config.json":
|
|
94
|
-
self.assertTrue(
|
|
100
|
+
self.assertTrue(
|
|
101
|
+
entry.is_file() and not entry.is_symlink(),
|
|
102
|
+
f"frontend directory entry {entry} is regular file",
|
|
103
|
+
)
|
|
95
104
|
try:
|
|
96
105
|
content = json.loads(entry.read_text("utf-8"))
|
|
97
106
|
except (ValueError, UnicodeDecodeError):
|
|
98
|
-
self.fail(
|
|
99
|
-
|
|
107
|
+
self.fail(
|
|
108
|
+
f"frontend directory entry {entry} is not UTF-8 encoded JSON"
|
|
109
|
+
)
|
|
110
|
+
for key_name in (
|
|
111
|
+
"API_BASE_URL",
|
|
112
|
+
"OAUTH_AUTHORITY_URL",
|
|
113
|
+
"OAUTH_CLIENT_ID",
|
|
114
|
+
"OAUTH_REDIRECT_URI",
|
|
115
|
+
"OAUTH_POST_REDIRECT_URI",
|
|
116
|
+
):
|
|
100
117
|
self.assertIn(key_name, content)
|
|
101
118
|
else:
|
|
102
|
-
self.assertTrue(
|
|
119
|
+
self.assertTrue(
|
|
120
|
+
entry.is_symlink(), f"frontend directory entry {entry} is symlink"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def test_init_data_relative_urls(self):
|
|
124
|
+
"""Test behavior of --relative-urls-in-config option."""
|
|
125
|
+
# Create our own dummy frontend directory if `vite` hasn’t been invoked yet
|
|
126
|
+
frontend_src = pathlib.Path(__file__).parent.parent.parent / "frontend_dist"
|
|
127
|
+
if not frontend_src.is_dir():
|
|
128
|
+
(frontend_src / "dummy").mkdir(parents=True)
|
|
129
|
+
self.addCleanup(shutil.rmtree, frontend_src)
|
|
130
|
+
|
|
131
|
+
management.call_command("load_initial_data", "--relative-urls-in-config")
|
|
132
|
+
frontend_dir = pathlib.Path(settings.BASE_DIR) / "frontend"
|
|
133
|
+
filepath = f"{frontend_dir}/config.json"
|
|
134
|
+
self.assertTrue(frontend_dir.exists())
|
|
135
|
+
with open(filepath) as fp:
|
|
136
|
+
config = json.loads(fp.read())
|
|
137
|
+
for param in [
|
|
138
|
+
"API_BASE_URL",
|
|
139
|
+
"API_DOC_URL",
|
|
140
|
+
"OAUTH_REDIRECT_URI",
|
|
141
|
+
"OAUTH_POST_REDIRECT_URI",
|
|
142
|
+
]:
|
|
143
|
+
self.assertFalse(config[param].startswith("http"))
|
|
103
144
|
|
|
104
145
|
def test_clean_inactive_accounts(self):
|
|
105
146
|
"""Run clean_inactive_accounts command."""
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{C as O}from"./ChoiceField-
|
|
1
|
+
import{C as O}from"./ChoiceField-7eU7c_rI.js";import{u as N,b as S,g as V,h as f,i as _,c as R,o as y,w as h,a as d,f as s,e as U,t as x,l as M,k as C,L as D,q as j,m as Q,F as G,G as T,N as H}from"./index-DV9Li2cg.js";import{V as P,a as L}from"./VRow-C_Ydf6yr.js";import{V as B}from"./VAlert-B7mzOJIO.js";import{d as z}from"./VAvatar-BAgTUIvX.js";import{V as k,r as F}from"./VForm-CpoZf60D.js";import{_ as K}from"./AccountPasswordSubForm-g3IEGrgM.js";import{E as q}from"./EmailField-BEKxuYni.js";import{V as E}from"./VTextField-CLwRV0Cb.js";import{V as $}from"./VSwitch-Chg5o-Cp.js";import{u as Y}from"./domains.store-ChZgLcqP.js";import{V as J}from"./VChip-nZ0uhY7t.js";const W={class:"headline"},X={class:"mt-4"},_e={__name:"AccountRoleForm",props:{modelValue:{type:Object,default:null}},setup(g,{expose:w}){const i=N(),{$gettext:a}=S(),v=g,m=V(),c=f(()=>v.modelValue),l=f(()=>i.authUser),p=f(()=>{const n=b.value.find(r=>r.value===c.value.role);return n!==void 0?n.label:""}),e=f(()=>{const n=b.value.find(r=>r.value===c.value.role);return n!==void 0?n.help:""}),b=f(()=>l.value.role===_.SUPER_ADMIN?[...A,...o,...u,...t]:l.value.role===_.DOMAIN_ADMIN?[...A]:l.value.role===_.RESELLER?[...o,...A]:[]),A=[{label:a("Simple user"),value:_.USER,help:a("A user with no privileges but with a mailbox. He will be allowed to use all end-user features: webmail, calendar, contacts, filters.")}],o=[{label:a("Domain administrator"),value:_.DOMAIN_ADMIN,help:a("A user with privileges on one or more domain. He will be allowed to administer mailboxes and he can also have a mailbox.")}],u=[{label:a("Reseller"),value:_.RESELLER,help:a("An intermediate user who has the same privileges than a Domain administrator, plus the possibility to create domains and to assign resources.")}],t=[{label:a("Super administrator"),value:_.SUPER_ADMIN,help:a("A user with all privileges, can do anything. By default, such a user does not have a mailbox so he can't access end-user features.")}];return w({vFormRef:m,roleLabel:p}),(n,r)=>(y(),R(k,{ref_key:"vFormRef",ref:m},{default:h(()=>[d(P,null,{default:h(()=>[d(L,{cols:"7"},{default:h(()=>[d(s(O),{modelValue:c.value.role,"onUpdate:modelValue":r[0]||(r[0]=I=>c.value.role=I),label:s(a)("Choose a role"),choices:b.value,"choices-per-line":2},null,8,["modelValue","label","choices"])]),_:1}),d(L,{cols:"5"},{default:h(()=>[d(B,{color:"primary",class:"mt-11 ml-4 rounded-lg"},{default:h(()=>[U("h3",W,x(p.value),1),U("p",X,x(e.value),1),d(z,{color:"white",class:"float-right",size:"large",icon:"mdi-help-circle-outline"})]),_:1})]),_:1})]),_:1})]),_:1},512))}},Z={class:"m-label"},ee={class:"m-label"},le={class:"m-label"},Ve={__name:"AccountGeneralForm",props:{modelValue:{type:Object,default:null},editing:{type:Boolean,default:!1}},setup(g,{expose:w}){const{$gettext:i}=S(),a=N(),v=g,m=V(),c=V({}),l=f(()=>v.modelValue),p=f(()=>l.value.role===_.USER?i("Enter an email address"):i("Enter a simple username or an email address")),e=f(()=>l.value.role===_.USER?"email":"text");function b(){l.value.username.indexOf("@")!==-1&&(l.value.mailbox.full_address=l.value.username,l.value.mailbox.message_limit=null)}return w({vFormRef:m,formErrors:c}),(A,o)=>(y(),R(k,{ref_key:"vFormRef",ref:m},{default:h(()=>[U("label",Z,x(s(i)("Username")),1),d(q,{ref:"username",modelValue:l.value.username,"onUpdate:modelValue":[o[0]||(o[0]=u=>l.value.username=u),b],placeholder:p.value,type:e.value,rules:e.value==="email"?[s(F).required,s(F).email]:[s(F).required],role:l.value.role,"error-msg":c.value.value?c.value.value.username:[]},null,8,["modelValue","placeholder","type","rules","role","error-msg"]),U("label",ee,x(s(i)("First name")),1),d(E,{modelValue:l.value.first_name,"onUpdate:modelValue":o[1]||(o[1]=u=>l.value.first_name=u),autocomplete:"new-password",variant:"outlined",density:"compact"},null,8,["modelValue"]),U("label",le,x(s(i)("Last name")),1),d(E,{modelValue:l.value.last_name,"onUpdate:modelValue":o[2]||(o[2]=u=>l.value.last_name=u),autocomplete:"new-password",variant:"outlined",density:"compact"},null,8,["modelValue"]),s(a).authUser.pk!==l.value.pk?(y(),R(K,{key:0,ref:"passwordForm",modelValue:l.value,"onUpdate:modelValue":o[3]||(o[3]=u=>l.value=u),editing:g.editing,"form-errors":c.value},null,8,["modelValue","editing","form-errors"])):(y(),R(B,{key:1,type:"info",variant:"tonal",class:"py-2"},{default:h(()=>[M(x(s(i)("You can update your password from the Account section")),1)]),_:1})),d($,{modelValue:l.value.is_active,"onUpdate:modelValue":o[4]||(o[4]=u=>l.value.is_active=u),label:s(i)("Enabled"),density:"compact",color:"primary"},null,8,["modelValue","label"])]),_:1},512))}},ae={class:"m-label"},oe={class:"m-label"},te={__name:"AccountMailboxForm",props:{modelValue:{type:Object,default:null}},emits:["update:modelValue"],setup(g,{expose:w,emit:i}){const{$gettext:a}=S(),v=i,m=g,c=Y(),l=f(()=>c.domains),p=V(),e=V({mailbox:{}});D(m.modelValue,t=>{t?(e.value={...t},e.value.role===_.USER&&(e.value.mailbox||(e.value.mailbox={}),e.value.mailbox.full_address=e.value.username),e.value.mailbox.message_limit===""&&(e.value.mailbox.message_limit=null)):e.value={mailbox:{}}},{immediate:!0}),D(e,t=>{v("update:modelValue",t)},{deep:!0});const b=f(()=>{const t=e.value.mailbox.full_address;if(t&&t.indexOf("@")!==-1){const n=l.value.find(r=>r.name===t.split("@")[1]);if(n)return parseInt(n.default_mailbox_quota)}}),A=f(()=>{let t=a("Use domain default value");return b.value!==void 0&&(b.value===0?t+=` (${a("unlimited")})`:t+=` (${b.value} MB)`),t});function o(t){t&&delete e.value.mailbox.quota}function u(t){t===""&&(e.value.mailbox.message_limit=null)}return w({vFormRef:p}),(t,n)=>(y(),R(k,{ref_key:"vFormRef",ref:p},{default:h(()=>[U("label",ae,x(s(a)("Quota")),1),d($,{modelValue:e.value.mailbox.use_domain_quota,"onUpdate:modelValue":[n[0]||(n[0]=r=>e.value.mailbox.use_domain_quota=r),o],label:A.value,color:"primary"},null,8,["modelValue","label"]),e.value.mailbox.use_domain_quota?j("",!0):(y(),R(E,{key:0,modelValue:e.value.mailbox.quota,"onUpdate:modelValue":n[1]||(n[1]=r=>e.value.mailbox.quota=r),placeholder:s(a)("Ex: 10MB. Leave empty for no limit"),hint:s(a)("Quota for this mailbox, can be expressed in KB, MB (default) or GB. Define a custom value or use domain's default one. Leave empty to define an unlimited value (not allowed for domain administrators)."),"persistent-hint":"",variant:"outlined",density:"compact"},null,8,["modelValue","placeholder","hint"])),U("label",oe,x(s(a)("Daily message sending limit")),1),d(E,{modelValue:e.value.mailbox.message_limit,"onUpdate:modelValue":[n[2]||(n[2]=r=>e.value.mailbox.message_limit=r),u],placeholder:s(a)("Leave empty for no limit"),hint:s(a)("Number of messages this mailbox can send per day. Leave empty for no limit."),"persistent-hint":"",variant:"outlined",density:"compact",rules:[s(F).numericOrNull]},null,8,["modelValue","placeholder","hint","rules"]),d($,{modelValue:e.value.mailbox.is_send_only,"onUpdate:modelValue":n[3]||(n[3]=r=>e.value.mailbox.is_send_only=r),label:s(a)("Send only account"),density:"compact",color:"primary"},null,8,["modelValue","label"])]),_:1},512))}},he=C(te,[["__scopeId","data-v-41714779"]]),xe={__name:"AccountAliasForm",props:{modelValue:{type:Object,default:null}},setup(g,{expose:w}){const{$gettext:i}=S(),a=g,v=f(()=>a.modelValue),m=V(""),c=V(),l=V(),p=V(!1),e=V([]);async function b(){if(e.value=[],p.value=!0,v.value.aliases.indexOf(m.value)!==-1){e.value=[i("Alias already added")],m.value="",p.value=!1;return}try{await H.validate({aliases:[m.value],mailbox:v.value.mailbox}),v.value.aliases.push(m.value),m.value="",l.value.resetValidation()}catch(o){o.response.data.aliases?o.response.data.aliases:o.response.data.non_field_errors&&o.response.data.non_field_errors}finally{p.value=!1}}function A(o){v.value.aliases.splice(o,1)}return w({vFormRef:l}),(o,u)=>(y(),R(k,{ref_key:"vFormRef",ref:l},{default:h(()=>[d(s(q),{ref_key:"aliasField",ref:c,modelValue:m.value,"onUpdate:modelValue":u[0]||(u[0]=t=>m.value=t),placeholder:s(i)("Start typing a name here..."),hint:s(i)("Alias(es) of this mailbox. To create a catchall alias, just enter the domain name (@domain.tld)."),"persistent-hint":"",loading:p.value,"error-msg":e.value,onDomainSelected:b},null,8,["modelValue","placeholder","hint","loading","error-msg"]),(y(!0),Q(G,null,T(v.value.aliases,(t,n)=>(y(),R(J,{key:n,class:"mr-2 mt-2",closable:"","onClick:close":r=>A(n)},{default:h(()=>[M(x(t),1)]),_:2},1032,["onClick:close"]))),128))]),_:1},512))}};export{he as A,xe as _,Ve as a,_e as b};
|