modoboa 2.4.11__py3-none-any.whl → 2.5.1__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/v2/serializers.py +14 -0
- modoboa/amavis/__init__.py +3 -0
- modoboa/amavis/app_settings.py +276 -0
- modoboa/amavis/apps.py +18 -0
- modoboa/amavis/checks/__init__.py +2 -0
- modoboa/amavis/checks/settings_checks.py +59 -0
- modoboa/amavis/dbrouter.py +35 -0
- modoboa/amavis/factories.py +164 -0
- modoboa/amavis/handlers.py +146 -0
- modoboa/amavis/lib.py +381 -0
- modoboa/amavis/management/__init__.py +0 -0
- modoboa/amavis/management/commands/__init__.py +0 -0
- modoboa/amavis/management/commands/amnotify.py +99 -0
- modoboa/amavis/management/commands/qcleanup.py +84 -0
- modoboa/amavis/migrations/0001_initial.py +340 -0
- modoboa/amavis/migrations/__init__.py +0 -0
- modoboa/amavis/models.py +226 -0
- modoboa/amavis/serializers.py +139 -0
- modoboa/amavis/sql_connector.py +240 -0
- modoboa/amavis/sql_email.py +66 -0
- modoboa/amavis/tasks.py +33 -0
- modoboa/amavis/templates/amavis/notifications/pending_requests.html +16 -0
- modoboa/amavis/tests/__init__.py +0 -0
- modoboa/amavis/tests/sa-learn +3 -0
- modoboa/amavis/tests/sample_messages/quarantined-input.txt +80 -0
- modoboa/amavis/tests/sample_messages/quarantined-output-plain_nolinks.txt +17 -0
- modoboa/amavis/tests/spamc +3 -0
- modoboa/amavis/tests/test_checks.py +25 -0
- modoboa/amavis/tests/test_handlers.py +214 -0
- modoboa/amavis/tests/test_lib.py +90 -0
- modoboa/amavis/tests/test_management_commands.py +45 -0
- modoboa/amavis/tests/test_sql_email.py +67 -0
- modoboa/amavis/tests/test_utils.py +19 -0
- modoboa/amavis/tests/test_viewsets.py +319 -0
- modoboa/amavis/urls.py +11 -0
- modoboa/amavis/utils.py +105 -0
- modoboa/amavis/viewsets.py +265 -0
- modoboa/core/api/v1/serializers.py +7 -5
- modoboa/core/api/v2/serializers.py +4 -2
- modoboa/core/api/v2/tests.py +16 -4
- modoboa/core/api/v2/urls.py +5 -5
- modoboa/core/api/v2/views.py +6 -2
- modoboa/core/api/v2/viewsets.py +24 -3
- modoboa/core/commands/deploy.py +3 -0
- modoboa/core/commands/templates/settings.py.tpl +12 -11
- modoboa/core/handlers.py +6 -2
- modoboa/core/management/commands/add_allowed_hosts.py +33 -0
- modoboa/core/management/commands/load_initial_data.py +10 -0
- modoboa/core/migrations/0025_rename_user_email_is_active_core_user_email_c0c03f_idx.py +23 -5
- modoboa/core/tests/test_core.py +24 -0
- modoboa/core/utils.py +3 -0
- modoboa/frontend_dist/assets/AccountAliasForm-C0oHHyZL.js +1 -0
- modoboa/frontend_dist/assets/AccountEditView-lgSJ2Se8.js +1 -0
- modoboa/frontend_dist/assets/AccountLayout-U386K8zy.js +1 -0
- modoboa/frontend_dist/assets/AccountPasswordSubForm-YsaE_cDx.js +1 -0
- modoboa/frontend_dist/assets/AccountView-1jfKFDwb.js +1 -0
- modoboa/frontend_dist/assets/AddressBook-CwN64Zls.js +1 -0
- modoboa/frontend_dist/assets/AdminLayout-Cxm1lggg.js +1 -0
- modoboa/frontend_dist/assets/AlarmsView-9yKGbmkC.css +1 -0
- modoboa/frontend_dist/assets/AlarmsView-Bcjsicac.js +1 -0
- modoboa/frontend_dist/assets/AliasEditView-k3rVt1tG.js +1 -0
- modoboa/frontend_dist/assets/{AliasRecipientForm-IOae6sjF.js → AliasRecipientForm-IehUzKok.js} +1 -1
- modoboa/frontend_dist/assets/AliasView-DMzA10eD.js +1 -0
- modoboa/frontend_dist/assets/AuditTrailView-5dnGX5El.js +1 -0
- modoboa/frontend_dist/assets/CalendarView-DZONeDA9.js +1 -0
- modoboa/frontend_dist/assets/{ChoiceField-DJ_c78Cm.js → ChoiceField-DnwXRkht.js} +1 -1
- modoboa/frontend_dist/assets/ComposeEmailForm-kghmfNuE.js +1 -0
- modoboa/frontend_dist/assets/ComposeEmailView-DLv3wk1k.js +1 -0
- modoboa/frontend_dist/assets/ConfirmDialog-CcPrCKuI.js +1 -0
- modoboa/frontend_dist/assets/{ConnectedLayout-Dvwmicnc.css → ConnectedLayout-Bxh21hcH.css} +1 -1
- modoboa/frontend_dist/assets/ConnectedLayout-CWlBK7Hf.js +1 -0
- modoboa/frontend_dist/assets/CreationForm-CW4lxnPg.js +1 -0
- modoboa/frontend_dist/assets/DashboardView-DXVZMbMo.js +1 -0
- modoboa/frontend_dist/assets/DomainAdminList-C3jcDDc3.js +1 -0
- modoboa/frontend_dist/assets/DomainEditView-ph8AaElX.js +1 -0
- modoboa/frontend_dist/assets/{DomainTransportForm-C2xo0Yd7.js → DomainTransportForm-NCz6Bl-h.js} +1 -1
- modoboa/frontend_dist/assets/DomainView-BgMSSuU-.js +5 -0
- modoboa/frontend_dist/assets/{DomainView-BDKoBFYr.css → DomainView-CCLYXPHx.css} +1 -1
- modoboa/frontend_dist/assets/DomainsView-CEEU9btK.js +1 -0
- modoboa/frontend_dist/assets/DomainsView-DZ-ss9bI.css +1 -0
- modoboa/frontend_dist/assets/EmailField-DeqDPm5j.js +1 -0
- modoboa/frontend_dist/assets/EmailView-DczVhVO0.js +1 -0
- modoboa/frontend_dist/assets/EmptyLayout-BXgcfMLH.js +1 -0
- modoboa/frontend_dist/assets/FiltersView-nJj_gSCx.js +1 -0
- modoboa/frontend_dist/assets/ForwardEmailView-Bgv3JQb6.js +1 -0
- modoboa/frontend_dist/assets/{HtmlEditor-CJ9umKeO.js → HtmlEditor-BWRdelVw.js} +1 -1
- modoboa/frontend_dist/assets/{IdentitiesView-0ziuQ5s-.css → IdentitiesView-DPrrRMS5.css} +1 -1
- modoboa/frontend_dist/assets/IdentitiesView-Dld9IloZ.js +1 -0
- modoboa/frontend_dist/assets/InformationView-BBWKSX8D.js +1 -0
- modoboa/frontend_dist/assets/InformationView-C9vvvQhJ.css +1 -0
- modoboa/frontend_dist/assets/{LoadingData-CYwX3Jpn.js → LoadingData-G57nJ_JV.js} +1 -1
- modoboa/frontend_dist/assets/{LoginCallbackView-E01qkKn0.js → LoginCallbackView-DjyE2SG_.js} +1 -1
- modoboa/frontend_dist/assets/{LoginView-Cy4uFV9h.js → LoginView-CqCCXYLo.js} +1 -1
- modoboa/frontend_dist/assets/{MailboxView-B-aI4XBq.css → MailboxView-CfStlWhk.css} +1 -1
- modoboa/frontend_dist/assets/MailboxView-DRrs9eLO.js +1 -0
- modoboa/frontend_dist/assets/MenuItems-BqIZW5av.js +1 -0
- modoboa/frontend_dist/assets/MessageView-D_6tx_gd.js +1 -0
- modoboa/frontend_dist/assets/MessagesView-BH7JIR03.js +1 -0
- modoboa/frontend_dist/assets/MigrationsView-Cv_So9T-.js +1 -0
- modoboa/frontend_dist/assets/{ParametersForm-BZM0QSvg.js → ParametersForm-3qXttTuQ.js} +1 -1
- modoboa/frontend_dist/assets/ParametersView-3Ns04cpQ.js +1 -0
- modoboa/frontend_dist/assets/ParametersView-B5B5Dt6K.js +1 -0
- modoboa/frontend_dist/assets/ProviderEditView-zh7CY832.js +1 -0
- modoboa/frontend_dist/assets/ProviderGeneralForm-BQU7t3ma.js +1 -0
- modoboa/frontend_dist/assets/ProvidersView-CoF_ZkZA.js +1 -0
- modoboa/frontend_dist/assets/QuarantineLayout-CYBsrbJM.js +1 -0
- modoboa/frontend_dist/assets/QuarantineView-D4gOE4EQ.css +1 -0
- modoboa/frontend_dist/assets/QuarantineView-DNvpoycb.js +1 -0
- modoboa/frontend_dist/assets/ReplyEmailView-D1XTcglu.js +1 -0
- modoboa/frontend_dist/assets/ResourcesForm-BW8rUGgZ.js +1 -0
- modoboa/frontend_dist/assets/SelfServiceLayout-DfDHiYeX.js +1 -0
- modoboa/frontend_dist/assets/{SettingsView-BxLJBFY0.js → SettingsView-gQiJ2NVb.js} +2 -2
- modoboa/frontend_dist/assets/StatisticsView-DYalet_q.js +1 -0
- modoboa/frontend_dist/assets/TimeSerieChart-BZ2htbFk.js +1 -0
- modoboa/frontend_dist/assets/UserLayout-zUtHi-z-.js +1 -0
- modoboa/frontend_dist/assets/VAlert-4r6LxKtg.js +1 -0
- modoboa/frontend_dist/assets/VApp-CX_C7AUN.js +1 -0
- modoboa/frontend_dist/assets/VAutocomplete-DNKmBvyZ.js +1 -0
- modoboa/frontend_dist/assets/VAvatar-DbuoZWmf.js +1 -0
- modoboa/frontend_dist/assets/VBadge-BQrRJ9S0.css +1 -0
- modoboa/frontend_dist/assets/VBadge-Bv2nvUmC.js +1 -0
- modoboa/frontend_dist/assets/VCard-DzjUT5OP.js +1 -0
- modoboa/frontend_dist/assets/VCheckbox-dr7UFjl4.js +1 -0
- modoboa/frontend_dist/assets/{VCheckboxBtn-Dt810gWf.js → VCheckboxBtn-CpFdBnTv.js} +1 -1
- modoboa/frontend_dist/assets/VChip-CaQvfmkw.js +1 -0
- modoboa/frontend_dist/assets/VColorPicker-ByGpCW5O.js +1 -0
- modoboa/frontend_dist/assets/{VContainer-DvTbsotR.js → VContainer-74Dnn8Ux.js} +1 -1
- modoboa/frontend_dist/assets/VDataTable-CL7yHvG7.js +1 -0
- modoboa/frontend_dist/assets/VDataTableServer-BqvNcIdw.js +1 -0
- modoboa/frontend_dist/assets/VDataTableVirtual--KsOP8i6.js +1 -0
- modoboa/frontend_dist/assets/{VDialog-Bk6EWNhz.js → VDialog-DmTGCGR0.js} +1 -1
- modoboa/frontend_dist/assets/VExpansionPanels-B7sSTCwd.js +1 -0
- modoboa/frontend_dist/assets/VFileInput-SULIc6F3.js +1 -0
- modoboa/frontend_dist/assets/VForm-DsRLc-sa.js +1 -0
- modoboa/frontend_dist/assets/VInput-CcxkaOXT.css +1 -0
- modoboa/frontend_dist/assets/VInput-DVKUObZe.js +1 -0
- modoboa/frontend_dist/assets/VMenu-nv0XOgg0.js +1 -0
- modoboa/frontend_dist/assets/VPicker-DnDSWJHJ.js +1 -0
- modoboa/frontend_dist/assets/VProgressCircular-qK6p5X_Y.js +1 -0
- modoboa/frontend_dist/assets/VRadioGroup-CbiPLy0t.js +1 -0
- modoboa/frontend_dist/assets/{VRow-BF35mT1S.js → VRow-DJ0NB63-.js} +1 -1
- modoboa/frontend_dist/assets/VSelect-CxCFMHyF.js +1 -0
- modoboa/frontend_dist/assets/VSelectionControl-C-6A4us5.js +1 -0
- modoboa/frontend_dist/assets/VSheet-DI6SxLnG.js +1 -0
- modoboa/frontend_dist/assets/VSpacer-CoJVmx8k.js +1 -0
- modoboa/frontend_dist/assets/VSwitch-DPnjPQuU.js +1 -0
- modoboa/frontend_dist/assets/VTable-ldTxgQPW.js +1 -0
- modoboa/frontend_dist/assets/VTabs-aS8WSL9I.js +1 -0
- modoboa/frontend_dist/assets/VTextField-BzBVKKob.css +1 -0
- modoboa/frontend_dist/assets/VTextField-DKbr4H5w.js +1 -0
- modoboa/frontend_dist/assets/VTextarea-BttkFsM4.js +1 -0
- modoboa/frontend_dist/assets/VToolbar-BxX3W2kR.js +1 -0
- modoboa/frontend_dist/assets/VWindowItem-Cvqvdegd.js +1 -0
- modoboa/frontend_dist/assets/WebmailLayout-BT2k6U7q.js +1 -0
- modoboa/frontend_dist/assets/accounts-CC2F0a0c.js +1 -0
- modoboa/frontend_dist/assets/{admin-o-HRGnmT.js → admin-CHCHFGI6.js} +1 -1
- modoboa/frontend_dist/assets/{aliases-DDVeehyg.js → aliases-C9bUD4Ws.js} +1 -1
- modoboa/frontend_dist/assets/amavis-BhzV4rgf.js +1 -0
- modoboa/frontend_dist/assets/amavis-DCVJxuui.js +1 -0
- modoboa/frontend_dist/assets/{contacts-C84DY-Q1.js → contacts-Dxz6eWpf.js} +1 -1
- modoboa/frontend_dist/assets/{domains-Bgn4ixHL.js → domains-C2cornvL.js} +1 -1
- modoboa/frontend_dist/assets/{domains.store-DTE-V7Y1.js → domains.store-BLKRipG8.js} +1 -1
- modoboa/frontend_dist/assets/{filter-CnffiQAW.js → filter-rmxrcjKk.js} +1 -1
- modoboa/frontend_dist/assets/forwardRefs-CpzzjgpX.js +1 -0
- modoboa/frontend_dist/assets/global.store-DndbMXYb.js +1 -0
- modoboa/frontend_dist/assets/{importExport-BlQYb0NO.js → importExport-C3uqrcok.js} +1 -1
- modoboa/frontend_dist/assets/index-LhNzkzAh.js +984 -0
- modoboa/frontend_dist/assets/layout-DbjDe3Wl.js +1 -0
- modoboa/frontend_dist/assets/{layout.store-DkjrAoXt.js → layout.store-Vq5mvIp7.js} +1 -1
- modoboa/frontend_dist/assets/{logos-q8SEyAa4.js → logos-Bvcy0usu.js} +1 -1
- modoboa/frontend_dist/assets/{logs-B7IJ7LBa.js → logs-BuItINky.js} +1 -1
- modoboa/frontend_dist/assets/{parameters-A6iBEYQq.js → parameters-C8IYEP7q.js} +1 -1
- modoboa/frontend_dist/assets/{parameters.store-BiXS4_6w.js → parameters.store-1cwSP2JP.js} +1 -1
- modoboa/frontend_dist/assets/permissions-DQjAcO9S.js +1 -0
- modoboa/frontend_dist/assets/{ssrBoot-AzTdjPjk.js → ssrBoot-BxIQ9ccA.js} +1 -1
- modoboa/frontend_dist/assets/{tag-BnSYRTcD.js → tag-3cfI1_f7.js} +1 -1
- modoboa/frontend_dist/assets/transports-D4Jk4-AP.js +1 -0
- modoboa/frontend_dist/assets/{webmail-CSH_3l6R.js → webmail-B2IUjaxM.js} +1 -1
- modoboa/frontend_dist/index.html +1 -1
- modoboa/lib/email_utils.py +2 -2
- modoboa/lib/permissions.py +7 -0
- modoboa/lib/test_runners.py +29 -0
- modoboa/lib/tests/__init__.py +5 -1
- modoboa/locale/br/LC_MESSAGES/django.po +87 -75
- modoboa/locale/cs/LC_MESSAGES/django.po +82 -74
- modoboa/locale/cs_CZ/LC_MESSAGES/django.po +145 -121
- modoboa/locale/de/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/de/LC_MESSAGES/django.po +339 -651
- modoboa/locale/de_DE/LC_MESSAGES/django.po +87 -75
- modoboa/locale/el_GR/LC_MESSAGES/django.po +160 -135
- modoboa/locale/en/LC_MESSAGES/django.po +82 -74
- modoboa/locale/es/LC_MESSAGES/django.po +158 -131
- modoboa/locale/es_MX/LC_MESSAGES/django.po +82 -74
- modoboa/locale/fi/LC_MESSAGES/django.po +87 -75
- modoboa/locale/fr/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/fr/LC_MESSAGES/django.po +469 -201
- modoboa/locale/hu/LC_MESSAGES/django.po +82 -74
- modoboa/locale/it/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/it/LC_MESSAGES/django.po +148 -122
- modoboa/locale/ja_JP/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/ja_JP/LC_MESSAGES/django.po +80 -72
- modoboa/locale/ka/LC_MESSAGES/django.po +82 -74
- modoboa/locale/nl_NL/LC_MESSAGES/django.po +160 -132
- modoboa/locale/no/LC_MESSAGES/django.po +82 -74
- modoboa/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/pl_PL/LC_MESSAGES/django.po +172 -149
- modoboa/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/pt_BR/LC_MESSAGES/django.po +172 -144
- modoboa/locale/pt_PT/LC_MESSAGES/django.po +135 -112
- modoboa/locale/ro_RO/LC_MESSAGES/django.po +87 -75
- modoboa/locale/ru/LC_MESSAGES/django.po +142 -118
- modoboa/locale/si/LC_MESSAGES/django.po +82 -74
- modoboa/locale/sk/LC_MESSAGES/django.po +82 -74
- modoboa/locale/sk_SK/LC_MESSAGES/django.po +84 -76
- modoboa/locale/sl_SI/LC_MESSAGES/django.po +90 -76
- modoboa/locale/sv/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/sv/LC_MESSAGES/django.po +172 -139
- modoboa/locale/tr/LC_MESSAGES/django.po +87 -75
- modoboa/locale/tr_TR/LC_MESSAGES/django.po +84 -74
- modoboa/locale/uk/LC_MESSAGES/django.po +82 -74
- modoboa/locale/zh/LC_MESSAGES/django.po +82 -74
- modoboa/locale/zh_CN/LC_MESSAGES/django.po +82 -74
- modoboa/locale/zh_TW/LC_MESSAGES/django.po +87 -75
- modoboa/parameters/api/v2/tests.py +2 -2
- modoboa/parameters/api/v2/viewsets.py +2 -0
- modoboa/policyd/tests.py +2 -0
- modoboa/urls_api_v2.py +6 -0
- modoboa/webmail/lib/imaputils.py +2 -2
- {modoboa-2.4.11.dist-info → modoboa-2.5.1.dist-info}/METADATA +6 -4
- {modoboa-2.4.11.dist-info → modoboa-2.5.1.dist-info}/RECORD +235 -185
- modoboa/frontend_dist/assets/AccountAliasForm-BV6KvTu6.js +0 -1
- modoboa/frontend_dist/assets/AccountEditView-DDOFyfBD.js +0 -1
- modoboa/frontend_dist/assets/AccountLayout-rX51xgxT.js +0 -1
- modoboa/frontend_dist/assets/AccountPasswordSubForm-D9S6LaeH.js +0 -1
- modoboa/frontend_dist/assets/AccountView-cmvaZNq3.js +0 -1
- modoboa/frontend_dist/assets/AddressBook-DCJxL8SU.js +0 -1
- modoboa/frontend_dist/assets/AdminLayout-r0wfG2lO.js +0 -1
- modoboa/frontend_dist/assets/AlarmsView-Bheey-gp.css +0 -1
- modoboa/frontend_dist/assets/AlarmsView-C0bqC4PA.js +0 -1
- modoboa/frontend_dist/assets/AliasEditView-DVoWoCGY.js +0 -1
- modoboa/frontend_dist/assets/AliasView-DrONZXOh.js +0 -1
- modoboa/frontend_dist/assets/AuditTrailView-OTkoZaMU.js +0 -1
- modoboa/frontend_dist/assets/CalendarView-CqF4_Ui9.js +0 -1
- modoboa/frontend_dist/assets/ComposeEmailForm-DO5_GB3e.js +0 -1
- modoboa/frontend_dist/assets/ComposeEmailView-A91HCBsN.js +0 -1
- modoboa/frontend_dist/assets/ConfirmDialog-BBcgdAnO.js +0 -1
- modoboa/frontend_dist/assets/ConnectedLayout-1oRW-Rql.js +0 -1
- modoboa/frontend_dist/assets/CreationForm-71YJbjsA.js +0 -1
- modoboa/frontend_dist/assets/DashboardView-CdLpSfUl.js +0 -1
- modoboa/frontend_dist/assets/DomainAdminList-BjC4KsqI.js +0 -1
- modoboa/frontend_dist/assets/DomainEditView-CQjKwYxl.js +0 -1
- modoboa/frontend_dist/assets/DomainView-BhhuZI_N.js +0 -5
- modoboa/frontend_dist/assets/DomainsView-Cft4BP8Z.js +0 -1
- modoboa/frontend_dist/assets/DomainsView-DasJ0NdZ.css +0 -1
- modoboa/frontend_dist/assets/EmailField-C8umy0EU.js +0 -1
- modoboa/frontend_dist/assets/EmailView-ki7uEQPD.js +0 -1
- modoboa/frontend_dist/assets/EmptyLayout-DaA1XH9n.js +0 -1
- modoboa/frontend_dist/assets/FiltersView-FYFZxG4B.js +0 -1
- modoboa/frontend_dist/assets/ForwardEmailView-cUbnSYCF.js +0 -1
- modoboa/frontend_dist/assets/IdentitiesView-njNo8N5n.js +0 -1
- modoboa/frontend_dist/assets/InformationView-D1h38POt.js +0 -1
- modoboa/frontend_dist/assets/InformationView-U5Ww-Sx1.css +0 -1
- modoboa/frontend_dist/assets/MailboxView-IlrLWm_H.js +0 -1
- modoboa/frontend_dist/assets/MenuItems-BAtHWzAE.js +0 -1
- modoboa/frontend_dist/assets/MessagesView-OSpjixFq.js +0 -1
- modoboa/frontend_dist/assets/MigrationsView-DKNOsVzF.js +0 -1
- modoboa/frontend_dist/assets/ParametersView-C4bXASiq.js +0 -1
- modoboa/frontend_dist/assets/ParametersView-CYXgNmc1.js +0 -1
- modoboa/frontend_dist/assets/ProviderEditView-CyxCWTST.js +0 -1
- modoboa/frontend_dist/assets/ProviderGeneralForm-BYPjNHqB.js +0 -1
- modoboa/frontend_dist/assets/ProvidersView-CxrMkRyk.js +0 -1
- modoboa/frontend_dist/assets/ReplyEmailView-Dkw9-N26.js +0 -1
- modoboa/frontend_dist/assets/ResourcesForm-CuUvrOdY.js +0 -1
- modoboa/frontend_dist/assets/StatisticsView-BN7QsZMT.js +0 -1
- modoboa/frontend_dist/assets/TimeSerieChart-BMN8BeFZ.js +0 -1
- modoboa/frontend_dist/assets/UserLayout-B6-JQg4F.js +0 -1
- modoboa/frontend_dist/assets/VAlert-DIQTrRif.js +0 -1
- modoboa/frontend_dist/assets/VApp-CpkYA7js.js +0 -1
- modoboa/frontend_dist/assets/VAutocomplete-C4IpXyl8.js +0 -1
- modoboa/frontend_dist/assets/VAvatar-Lpb-Dion.js +0 -1
- modoboa/frontend_dist/assets/VCard-er_isjE_.js +0 -1
- modoboa/frontend_dist/assets/VCheckbox-D-u8JXP1.js +0 -1
- modoboa/frontend_dist/assets/VChip-B4iSpj8_.js +0 -1
- modoboa/frontend_dist/assets/VColorPicker-BAjGDsXv.js +0 -1
- modoboa/frontend_dist/assets/VDataTable-4JRjbtgF.js +0 -1
- modoboa/frontend_dist/assets/VDataTableServer-tIDT1m3-.js +0 -1
- modoboa/frontend_dist/assets/VDataTableVirtual-BlnO18u_.js +0 -1
- modoboa/frontend_dist/assets/VExpansionPanels-CwGtXDhr.js +0 -1
- modoboa/frontend_dist/assets/VFileInput-D1_7ZkO_.js +0 -1
- modoboa/frontend_dist/assets/VForm-DAkW4nfy.js +0 -1
- modoboa/frontend_dist/assets/VMenu-BPFJwj2f.js +0 -1
- modoboa/frontend_dist/assets/VPicker-CfT82M8N.js +0 -1
- modoboa/frontend_dist/assets/VProgressCircular-w75-3ogi.js +0 -1
- modoboa/frontend_dist/assets/VRadioGroup-0j6DNC_k.js +0 -1
- modoboa/frontend_dist/assets/VSelect-Cs4ARbAS.js +0 -1
- modoboa/frontend_dist/assets/VSelectionControl-Dg-XyRRS.js +0 -1
- modoboa/frontend_dist/assets/VSheet-Btq_Mu4s.js +0 -1
- modoboa/frontend_dist/assets/VSpacer-C7xukQmu.js +0 -1
- modoboa/frontend_dist/assets/VSwitch-Cs1NQrmk.js +0 -1
- modoboa/frontend_dist/assets/VTable-CNz2SGk4.js +0 -1
- modoboa/frontend_dist/assets/VTabs-B1fyVn4M.js +0 -1
- modoboa/frontend_dist/assets/VTextField-BdyvgvkG.js +0 -1
- modoboa/frontend_dist/assets/VTextField-C-J20yj_.css +0 -1
- modoboa/frontend_dist/assets/VTextarea-DnOMpe0Q.js +0 -1
- modoboa/frontend_dist/assets/VToolbar-BiCiBxBJ.js +0 -1
- modoboa/frontend_dist/assets/VWindowItem-ChWm_kz3.js +0 -1
- modoboa/frontend_dist/assets/WebmailLayout-o4uEkp9e.js +0 -1
- modoboa/frontend_dist/assets/forwardRefs-Dvjn_Xq4.js +0 -1
- modoboa/frontend_dist/assets/global.store-BaiD63EN.js +0 -1
- modoboa/frontend_dist/assets/index-I1VDlN4g.js +0 -984
- modoboa/frontend_dist/assets/layout-D8ZJPiJ_.js +0 -1
- modoboa/frontend_dist/assets/permissions-CITHLHVg.js +0 -1
- modoboa/frontend_dist/assets/transports-Dz7c6kIy.js +0 -1
- {modoboa-2.4.11.data → modoboa-2.5.1.data}/scripts/modoboa-admin.py +0 -0
- {modoboa-2.4.11.dist-info → modoboa-2.5.1.dist-info}/WHEEL +0 -0
- {modoboa-2.4.11.dist-info → modoboa-2.5.1.dist-info}/entry_points.txt +0 -0
- {modoboa-2.4.11.dist-info → modoboa-2.5.1.dist-info}/licenses/LICENSE +0 -0
- {modoboa-2.4.11.dist-info → modoboa-2.5.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from unittest import mock
|
|
3
|
+
|
|
4
|
+
from rq import SimpleWorker
|
|
5
|
+
|
|
6
|
+
from django.core import mail
|
|
7
|
+
from django.core.management import call_command
|
|
8
|
+
from django.test import override_settings
|
|
9
|
+
from django.urls import reverse
|
|
10
|
+
|
|
11
|
+
import django_rq
|
|
12
|
+
|
|
13
|
+
from modoboa.admin import factories as admin_factories, models as admin_models
|
|
14
|
+
from modoboa.amavis import factories, models
|
|
15
|
+
from modoboa.amavis.utils import smart_str
|
|
16
|
+
from modoboa.core import models as core_models
|
|
17
|
+
from modoboa.lib.tests import ModoAPITestCase
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
AMAVIS_SETTINGS = {
|
|
21
|
+
"localpart_is_case_sensitive": False,
|
|
22
|
+
"recipient_delimiter": "+",
|
|
23
|
+
"max_attachment_size": 14,
|
|
24
|
+
"released_msgs_cleanup": False,
|
|
25
|
+
"am_pdp_mode": "unix",
|
|
26
|
+
"am_pdp_host": "localhost",
|
|
27
|
+
"am_pdp_port": 9998,
|
|
28
|
+
"user_can_release": False,
|
|
29
|
+
"self_service": False,
|
|
30
|
+
"manual_learning": True,
|
|
31
|
+
"sa_is_local": True,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestDataMixin:
|
|
36
|
+
"""A mixin to provide test data."""
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def setUpTestData(cls): # NOQA:N802
|
|
40
|
+
"""Create some content."""
|
|
41
|
+
super().setUpTestData()
|
|
42
|
+
cls.msgrcpt = factories.create_spam("user@test.com")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@override_settings(SA_LOOKUP_PATH=(os.path.dirname(__file__),))
|
|
46
|
+
class QuarantineViewSetTestCase(TestDataMixin, ModoAPITestCase):
|
|
47
|
+
|
|
48
|
+
databases = "__all__"
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def setUpTestData(cls): # NOQA:N802
|
|
52
|
+
"""Create test data."""
|
|
53
|
+
super().setUpTestData()
|
|
54
|
+
admin_factories.populate_database()
|
|
55
|
+
|
|
56
|
+
def tearDown(self):
|
|
57
|
+
"""Restore msgrcpt state."""
|
|
58
|
+
self.msgrcpt.rs = " "
|
|
59
|
+
self.msgrcpt.save(update_fields=["rs"])
|
|
60
|
+
self.set_global_parameter("domain_level_learning", False)
|
|
61
|
+
self.set_global_parameter("user_level_learning", False)
|
|
62
|
+
|
|
63
|
+
def test_list(self):
|
|
64
|
+
url = reverse("v2:amavis-quarantine-list")
|
|
65
|
+
resp = self.client.get(url)
|
|
66
|
+
self.assertEqual(resp.status_code, 200)
|
|
67
|
+
content = resp.json()
|
|
68
|
+
self.assertEqual(content["count"], 1)
|
|
69
|
+
|
|
70
|
+
resp = self.client.get(f"{url}?search=pouet&criteria=both")
|
|
71
|
+
content = resp.json()
|
|
72
|
+
self.assertEqual(content["count"], 0)
|
|
73
|
+
|
|
74
|
+
resp = self.client.get(f"{url}?msgtype=V")
|
|
75
|
+
content = resp.json()
|
|
76
|
+
self.assertEqual(content["count"], 0)
|
|
77
|
+
factories.create_virus("user@test.com")
|
|
78
|
+
resp = self.client.get(f"{url}?msgtype=V")
|
|
79
|
+
content = resp.json()
|
|
80
|
+
self.assertEqual(content["count"], 1)
|
|
81
|
+
|
|
82
|
+
def test_retrieve(self):
|
|
83
|
+
mail_id = smart_str(self.msgrcpt.mail.mail_id)
|
|
84
|
+
rcpt = smart_str(self.msgrcpt.rid.email)
|
|
85
|
+
url = reverse("v2:amavis-quarantine-detail", args=[mail_id])
|
|
86
|
+
resp = self.client.get(f"{url}?rcpt={rcpt}")
|
|
87
|
+
self.assertEqual(resp.status_code, 200)
|
|
88
|
+
|
|
89
|
+
user = core_models.User.objects.get(username="user@test.com")
|
|
90
|
+
self.client.force_authenticate(user)
|
|
91
|
+
resp = self.client.get(f"{url}?rcpt={rcpt}")
|
|
92
|
+
self.assertEqual(resp.status_code, 200)
|
|
93
|
+
self.msgrcpt.refresh_from_db()
|
|
94
|
+
self.assertEqual(self.msgrcpt.rs, "V")
|
|
95
|
+
|
|
96
|
+
def test_headers(self):
|
|
97
|
+
mail_id = smart_str(self.msgrcpt.mail.mail_id)
|
|
98
|
+
url = reverse("v2:amavis-quarantine-headers", args=[mail_id])
|
|
99
|
+
resp = self.client.get(url)
|
|
100
|
+
self.assertEqual(resp.status_code, 200)
|
|
101
|
+
|
|
102
|
+
def test_headers_selfservice(self):
|
|
103
|
+
self.client.logout()
|
|
104
|
+
mail_id = smart_str(self.msgrcpt.mail.mail_id)
|
|
105
|
+
url = reverse("v2:amavis-quarantine-headers", args=[mail_id])
|
|
106
|
+
url = f"{url}?secret_id={smart_str(self.msgrcpt.mail.secret_id)}"
|
|
107
|
+
response = self.client.get(url)
|
|
108
|
+
self.assertEqual(response.status_code, 403)
|
|
109
|
+
|
|
110
|
+
self.set_global_parameter("self_service", True)
|
|
111
|
+
response = self.client.get(url)
|
|
112
|
+
self.assertEqual(response.status_code, 403)
|
|
113
|
+
url = f"{url}&rcpt={smart_str(self.msgrcpt.rid.email)}"
|
|
114
|
+
response = self.client.get(url)
|
|
115
|
+
self.assertEqual(response.status_code, 200)
|
|
116
|
+
|
|
117
|
+
def test_delete(self):
|
|
118
|
+
mail_id = smart_str(self.msgrcpt.mail.mail_id)
|
|
119
|
+
url = reverse("v2:amavis-quarantine-delete", args=[mail_id])
|
|
120
|
+
data = {"mailid": mail_id, "rcpt": smart_str(self.msgrcpt.rid.email)}
|
|
121
|
+
response = self.client.post(url, data, format="json")
|
|
122
|
+
self.assertEqual(response.status_code, 204)
|
|
123
|
+
self.msgrcpt.refresh_from_db()
|
|
124
|
+
self.assertEqual(self.msgrcpt.rs, "D")
|
|
125
|
+
|
|
126
|
+
def test_delete_selfservice(self):
|
|
127
|
+
self.client.logout()
|
|
128
|
+
mail_id = smart_str(self.msgrcpt.mail.mail_id)
|
|
129
|
+
url = reverse("v2:amavis-quarantine-delete", args=[mail_id])
|
|
130
|
+
data = {"mailid": mail_id, "rcpt": smart_str(self.msgrcpt.rid.email)}
|
|
131
|
+
response = self.client.post(url, data, format="json")
|
|
132
|
+
self.assertEqual(response.status_code, 403)
|
|
133
|
+
self.set_global_parameter("self_service", True)
|
|
134
|
+
data["secret_id"] = smart_str(self.msgrcpt.mail.secret_id)
|
|
135
|
+
response = self.client.post(url, data, format="json")
|
|
136
|
+
self.assertEqual(response.status_code, 204)
|
|
137
|
+
self.msgrcpt.refresh_from_db()
|
|
138
|
+
self.assertEqual(self.msgrcpt.rs, "D")
|
|
139
|
+
|
|
140
|
+
def test_delete_selection(self):
|
|
141
|
+
mail_id = smart_str(self.msgrcpt.mail.mail_id)
|
|
142
|
+
url = reverse("v2:amavis-quarantine-delete-selection")
|
|
143
|
+
data = {
|
|
144
|
+
"selection": [
|
|
145
|
+
{"mailid": mail_id, "rcpt": smart_str(self.msgrcpt.rid.email)}
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
response = self.client.post(url, data, format="json")
|
|
149
|
+
self.assertEqual(response.status_code, 204)
|
|
150
|
+
self.msgrcpt.refresh_from_db()
|
|
151
|
+
self.assertEqual(self.msgrcpt.rs, "D")
|
|
152
|
+
|
|
153
|
+
@mock.patch("socket.socket")
|
|
154
|
+
def test_release(self, mock_socket):
|
|
155
|
+
mock_socket.return_value.recv.return_value = b"250 1234 Ok\r\n"
|
|
156
|
+
mail_id = smart_str(self.msgrcpt.mail.mail_id)
|
|
157
|
+
url = reverse("v2:amavis-quarantine-release", args=[mail_id])
|
|
158
|
+
data = {"mailid": mail_id, "rcpt": smart_str(self.msgrcpt.rid.email)}
|
|
159
|
+
response = self.client.post(url, data, format="json")
|
|
160
|
+
self.assertEqual(response.status_code, 200)
|
|
161
|
+
self.msgrcpt.refresh_from_db()
|
|
162
|
+
self.assertEqual(self.msgrcpt.rs, "R")
|
|
163
|
+
|
|
164
|
+
@mock.patch("socket.socket")
|
|
165
|
+
def test_release_selfservice(self, mock_socket):
|
|
166
|
+
"""Test release view."""
|
|
167
|
+
mock_socket.return_value.recv.return_value = b"250 1234 Ok\r\n"
|
|
168
|
+
self.client.logout()
|
|
169
|
+
mail_id = smart_str(self.msgrcpt.mail.mail_id)
|
|
170
|
+
url = reverse("v2:amavis-quarantine-release", args=[mail_id])
|
|
171
|
+
self.set_global_parameter("self_service", True)
|
|
172
|
+
# Missing rcpt -> fails
|
|
173
|
+
data = {"mailid": mail_id, "secret_id": "1234"}
|
|
174
|
+
resp = self.client.post(url, data, format="json")
|
|
175
|
+
self.assertEqual(resp.status_code, 403)
|
|
176
|
+
# Wrong secret_id -> fails
|
|
177
|
+
data["rcpt"] = smart_str(self.msgrcpt.rid.email)
|
|
178
|
+
resp = self.client.post(url, data, format="json")
|
|
179
|
+
self.assertEqual(resp.status_code, 403)
|
|
180
|
+
# Request mode
|
|
181
|
+
data["secret_id"] = smart_str(self.msgrcpt.mail.secret_id)
|
|
182
|
+
resp = self.client.post(url, data, format="json")
|
|
183
|
+
self.assertEqual(resp.status_code, 200)
|
|
184
|
+
self.msgrcpt.refresh_from_db()
|
|
185
|
+
self.assertEqual(self.msgrcpt.rs, "p")
|
|
186
|
+
# Direct release mode
|
|
187
|
+
self.set_global_parameter("user_can_release", True)
|
|
188
|
+
resp = self.client.post(url, data, format="json")
|
|
189
|
+
self.assertEqual(resp.status_code, 200)
|
|
190
|
+
self.msgrcpt.refresh_from_db()
|
|
191
|
+
self.assertEqual(self.msgrcpt.rs, "R")
|
|
192
|
+
|
|
193
|
+
def test_release_request(self):
|
|
194
|
+
"""Test release request mode."""
|
|
195
|
+
user = core_models.User.objects.get(username="user@test.com")
|
|
196
|
+
self.client.force_authenticate(user)
|
|
197
|
+
|
|
198
|
+
mail_id = smart_str(self.msgrcpt.mail.mail_id)
|
|
199
|
+
url = reverse("v2:amavis-quarantine-release", args=[mail_id])
|
|
200
|
+
data = {"mailid": mail_id, "rcpt": smart_str(self.msgrcpt.rid.email)}
|
|
201
|
+
response = self.client.post(url, data, format="json")
|
|
202
|
+
self.assertEqual(response.status_code, 200)
|
|
203
|
+
self.msgrcpt.refresh_from_db()
|
|
204
|
+
self.assertEqual(self.msgrcpt.rs, "p")
|
|
205
|
+
|
|
206
|
+
# Send notification to admins
|
|
207
|
+
call_command("amnotify")
|
|
208
|
+
self.assertEqual(len(mail.outbox), 1)
|
|
209
|
+
|
|
210
|
+
def _test_mark_selection(self, action, status):
|
|
211
|
+
"""Mark message common code."""
|
|
212
|
+
mail_id = smart_str(self.msgrcpt.mail.mail_id)
|
|
213
|
+
url = reverse("v2:amavis-quarantine-mark-selection")
|
|
214
|
+
data = {
|
|
215
|
+
"selection": [
|
|
216
|
+
{"mailid": mail_id, "rcpt": smart_str(self.msgrcpt.rid.email)}
|
|
217
|
+
],
|
|
218
|
+
"type": action,
|
|
219
|
+
}
|
|
220
|
+
response = self.client.post(url, data, format="json")
|
|
221
|
+
self.assertEqual(response.status_code, 204)
|
|
222
|
+
queue = django_rq.get_queue("modoboa")
|
|
223
|
+
worker = SimpleWorker([queue], connection=queue.connection)
|
|
224
|
+
worker.work(burst=True)
|
|
225
|
+
self.msgrcpt.refresh_from_db()
|
|
226
|
+
self.assertEqual(self.msgrcpt.rs, status)
|
|
227
|
+
|
|
228
|
+
self.msgrcpt.rs = " "
|
|
229
|
+
self.msgrcpt.save(update_fields=["rs"])
|
|
230
|
+
self.set_global_parameter("sa_is_local", False)
|
|
231
|
+
response = self.client.post(url, data, format="json")
|
|
232
|
+
self.assertEqual(response.status_code, 204)
|
|
233
|
+
worker.work(burst=True)
|
|
234
|
+
self.msgrcpt.refresh_from_db()
|
|
235
|
+
self.assertEqual(self.msgrcpt.rs, status)
|
|
236
|
+
|
|
237
|
+
def test_mark_as_ham(self):
|
|
238
|
+
"""Test mark_as_ham view."""
|
|
239
|
+
self._test_mark_selection("ham", "H")
|
|
240
|
+
|
|
241
|
+
# Check domain level learning
|
|
242
|
+
self.set_global_parameter("domain_level_learning", True)
|
|
243
|
+
data = {
|
|
244
|
+
"selection": [
|
|
245
|
+
{
|
|
246
|
+
"rcpt": smart_str(self.msgrcpt.rid.email),
|
|
247
|
+
"mailid": smart_str(self.msgrcpt.mail.mail_id),
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
"type": "ham",
|
|
251
|
+
"database": "domain",
|
|
252
|
+
}
|
|
253
|
+
url = reverse("v2:amavis-quarantine-mark-selection")
|
|
254
|
+
response = self.client.post(url, data, format="json")
|
|
255
|
+
self.assertEqual(response.status_code, 204)
|
|
256
|
+
|
|
257
|
+
# Check user level learning
|
|
258
|
+
self.set_global_parameter("user_level_learning", True)
|
|
259
|
+
data["database"] = "user"
|
|
260
|
+
response = self.client.post(url, data, format="json")
|
|
261
|
+
self.assertEqual(response.status_code, 204)
|
|
262
|
+
|
|
263
|
+
def test_mark_as_spam(self):
|
|
264
|
+
"""Test mark_as_spam view."""
|
|
265
|
+
self._test_mark_selection("spam", "S")
|
|
266
|
+
|
|
267
|
+
def test_manual_learning_as_user(self):
|
|
268
|
+
"""Test learning when connected as a simple user."""
|
|
269
|
+
user = core_models.User.objects.get(username="user@test.com")
|
|
270
|
+
self.client.force_authenticate(user)
|
|
271
|
+
mail_id = smart_str(self.msgrcpt.mail.mail_id)
|
|
272
|
+
data = {
|
|
273
|
+
"selection": [
|
|
274
|
+
{"mailid": mail_id, "rcpt": smart_str(self.msgrcpt.rid.email)}
|
|
275
|
+
],
|
|
276
|
+
"type": "ham",
|
|
277
|
+
}
|
|
278
|
+
url = reverse("v2:amavis-quarantine-mark-selection")
|
|
279
|
+
response = self.client.post(url, data, format="json")
|
|
280
|
+
self.assertEqual(response.status_code, 200)
|
|
281
|
+
self.assertEqual(response.json(), {"status": "ok"})
|
|
282
|
+
self.set_global_parameter("user_level_learning", True)
|
|
283
|
+
self._test_mark_selection("ham", "H")
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class PolicyViewSetTestCase(ModoAPITestCase):
|
|
287
|
+
|
|
288
|
+
databases = "__all__"
|
|
289
|
+
|
|
290
|
+
@classmethod
|
|
291
|
+
def setUpTestData(cls): # NOQA:N802
|
|
292
|
+
"""Create test data."""
|
|
293
|
+
super().setUpTestData()
|
|
294
|
+
admin_factories.populate_database()
|
|
295
|
+
|
|
296
|
+
def test_get_policy(self):
|
|
297
|
+
domain = admin_models.Domain.objects.get(name="test.com")
|
|
298
|
+
url = reverse("v2:amavis-policy-detail", args=[domain.pk])
|
|
299
|
+
resp = self.client.get(url)
|
|
300
|
+
self.assertEqual(resp.status_code, 200)
|
|
301
|
+
|
|
302
|
+
def test_update_policy(self):
|
|
303
|
+
domain = admin_models.Domain.objects.get(name="test.com")
|
|
304
|
+
data = {"bypass_virus_checks": "Y", "spam_tag2_level": 6}
|
|
305
|
+
url = reverse("v2:amavis-policy-detail", args=[domain.pk])
|
|
306
|
+
resp = self.client.patch(url, data, format="json")
|
|
307
|
+
self.assertEqual(resp.status_code, 200)
|
|
308
|
+
p = models.Policy.objects.get(users__email="@test.com")
|
|
309
|
+
self.assertEqual(p.bypass_virus_checks, data["bypass_virus_checks"])
|
|
310
|
+
self.assertEqual(p.spam_tag2_level, data["spam_tag2_level"])
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class ParametersAPITestCase(ModoAPITestCase):
|
|
314
|
+
|
|
315
|
+
def test_update(self):
|
|
316
|
+
settings = AMAVIS_SETTINGS.copy()
|
|
317
|
+
url = reverse("v2:parameter-global-detail", args=["amavis"])
|
|
318
|
+
resp = self.client.put(url, settings, format="json")
|
|
319
|
+
self.assertEqual(resp.status_code, 200)
|
modoboa/amavis/urls.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Amavis urls."""
|
|
2
|
+
|
|
3
|
+
from rest_framework import routers
|
|
4
|
+
|
|
5
|
+
from modoboa.amavis import viewsets
|
|
6
|
+
|
|
7
|
+
router = routers.SimpleRouter()
|
|
8
|
+
router.register(r"quarantine", viewsets.QuarantineViewSet, basename="amavis-quarantine")
|
|
9
|
+
router.register(r"policies", viewsets.PolicyViewSet, basename="amavis-policy")
|
|
10
|
+
|
|
11
|
+
urlpatterns = router.urls
|
modoboa/amavis/utils.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""A collection of utility functions for working with the Amavis database."""
|
|
2
|
+
|
|
3
|
+
import chardet
|
|
4
|
+
|
|
5
|
+
from django.conf import settings
|
|
6
|
+
from django.db.models.expressions import Func
|
|
7
|
+
from django.utils.encoding import (
|
|
8
|
+
smart_bytes as django_smart_bytes,
|
|
9
|
+
smart_str as django_smart_str,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
Byte fields for text data are EVIL.
|
|
15
|
+
|
|
16
|
+
MySQL uses `varbyte` fields which mysqlclient client maps to `str` (Py2) or
|
|
17
|
+
`bytes` (Py3), Djangos smart_* functions work as expected.
|
|
18
|
+
|
|
19
|
+
PostgreSQL uses `bytea` fields which psycopg2 maps to `memoryview`,
|
|
20
|
+
Djangos smart_* functions don't work as expected, you must call `tobytes()` on
|
|
21
|
+
the memoryview for them to work.
|
|
22
|
+
|
|
23
|
+
For convenience use smart_bytes and smart_str from this file in modoboa.amavis
|
|
24
|
+
to avoid any headaches.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def smart_bytes(value, *args, **kwargs):
|
|
29
|
+
if isinstance(value, memoryview):
|
|
30
|
+
value = value.tobytes()
|
|
31
|
+
return django_smart_bytes(value, *args, **kwargs)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def smart_str(value, *args, **kwargs):
|
|
35
|
+
if isinstance(value, memoryview):
|
|
36
|
+
value = value.tobytes()
|
|
37
|
+
return django_smart_str(value, *args, **kwargs)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def fix_utf8_encoding(value):
|
|
41
|
+
"""Fix utf-8 strings that contain utf-8 escaped characters.
|
|
42
|
+
|
|
43
|
+
msgs.from_addr and msgs.subject potentialy contain badly escaped utf-8
|
|
44
|
+
characters, this utility function fixes that and should be used anytime
|
|
45
|
+
these fields are accesses.
|
|
46
|
+
|
|
47
|
+
Didn't even know the raw_unicode_escape encoding existed :)
|
|
48
|
+
https://docs.python.org/3/library/codecs.html?highlight=raw_unicode_escape#python-specific-encodings
|
|
49
|
+
"""
|
|
50
|
+
assert isinstance(value, str), "value should be of type str"
|
|
51
|
+
|
|
52
|
+
if len(value) == 0:
|
|
53
|
+
# short circuit for empty strings
|
|
54
|
+
return ""
|
|
55
|
+
|
|
56
|
+
bytes_value = value.encode("raw_unicode_escape")
|
|
57
|
+
try:
|
|
58
|
+
value = bytes_value.decode("utf-8")
|
|
59
|
+
except UnicodeDecodeError:
|
|
60
|
+
encoding = chardet.detect(bytes_value)
|
|
61
|
+
try:
|
|
62
|
+
value = bytes_value.decode(encoding["encoding"], "replace")
|
|
63
|
+
except (TypeError, UnicodeDecodeError):
|
|
64
|
+
# ??? use the original value, we've done our best to try and
|
|
65
|
+
# convert it to a clean utf-8 string.
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
return value
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ConvertFrom(Func):
|
|
72
|
+
"""Convert a binary value to a string.
|
|
73
|
+
Calls the database specific function to convert a binary value to a string
|
|
74
|
+
using the encoding set in AMAVIS_DEFAULT_DATABASE_ENCODING.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
"""PostgreSQL implementation.
|
|
78
|
+
See https://www.postgresql.org/docs/9.3/static/functions-string.html#FUNCTIONS-STRING-OTHER""" # NOQA:E501
|
|
79
|
+
function = "convert_from"
|
|
80
|
+
arity = 1
|
|
81
|
+
template = (
|
|
82
|
+
f"%(function)s(%(expressions)s, '{settings.AMAVIS_DEFAULT_DATABASE_ENCODING}')"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def as_mysql(self, compiler, connection):
|
|
86
|
+
"""MySQL implementation.
|
|
87
|
+
See https://dev.mysql.com/doc/refman/5.5/en/cast-functions.html#function_convert
|
|
88
|
+
""" # NOQA:E501
|
|
89
|
+
return super().as_sql(
|
|
90
|
+
compiler,
|
|
91
|
+
connection,
|
|
92
|
+
function="CONVERT",
|
|
93
|
+
template=f"%(function)s(%(expressions)s USING {settings.AMAVIS_DEFAULT_DATABASE_ENCODING})",
|
|
94
|
+
arity=1,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def as_sqlite(self, compiler, connection):
|
|
98
|
+
"""SQLite implementation.
|
|
99
|
+
SQLite has no equivilant function, just return the field."""
|
|
100
|
+
return super().as_sql(
|
|
101
|
+
compiler,
|
|
102
|
+
connection,
|
|
103
|
+
template="%(expressions)s",
|
|
104
|
+
arity=1,
|
|
105
|
+
)
|