modoboa 2.6.5__py3-none-any.whl → 2.7.0__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 +1 -0
- modoboa/admin/app_settings.py +12 -0
- modoboa/admin/{management/commands/subcommands/_mx.py → dns_checker.py} +41 -111
- modoboa/admin/jobs.py +86 -0
- modoboa/admin/management/commands/modo.py +0 -2
- modoboa/admin/tests/test_mailbox_operations.py +4 -4
- modoboa/admin/tests/test_mx.py +68 -56
- modoboa/amavis/jobs.py +11 -0
- modoboa/amavis/tests/test_jobs.py +18 -0
- modoboa/amavis/tests/test_viewsets.py +2 -3
- modoboa/autoconfig/templates/autoconfig/autoconfig.xml +2 -2
- modoboa/autoconfig/templates/autoconfig/autodiscover.xml +14 -0
- modoboa/autoconfig/tests.py +2 -1
- modoboa/autoconfig/views.py +11 -3
- modoboa/calendars/backends/caldav_.py +17 -13
- modoboa/calendars/jobs.py +7 -0
- modoboa/calendars/mocks.py +4 -1
- modoboa/calendars/serializers.py +14 -9
- modoboa/calendars/tests.py +10 -9
- modoboa/calendars/viewsets.py +3 -1
- modoboa/contacts/migrations/0008_addressbook_syncing.py +18 -0
- modoboa/contacts/models.py +1 -0
- modoboa/contacts/serializers.py +5 -2
- modoboa/contacts/tasks.py +9 -3
- modoboa/contacts/tests.py +32 -6
- modoboa/contacts/viewsets.py +7 -1
- modoboa/core/api/v2/serializers.py +0 -7
- modoboa/core/api/v2/tests.py +0 -10
- modoboa/core/app_settings.py +0 -22
- modoboa/core/commands/deploy.py +13 -0
- modoboa/core/commands/templates/cron_config.py.tpl +33 -0
- modoboa/core/commands/templates/settings.py.tpl +21 -0
- modoboa/core/jobs.py +34 -0
- modoboa/core/management/commands/load_initial_data.py +1 -1
- modoboa/core/password_hashers/base.py +4 -1
- modoboa/core/tests/test_core.py +0 -14
- modoboa/core/tests/test_jobs.py +40 -0
- modoboa/frontend_dist/assets/{AccountAliasForm-B-hTKSFr.js → AccountAliasForm-DO6DwfjE.js} +1 -1
- modoboa/frontend_dist/assets/{AccountEditView-LkP_qhf_.js → AccountEditView-CCuN9mGB.js} +1 -1
- modoboa/frontend_dist/assets/AccountLayout-Ge7fzuZg.js +1 -0
- modoboa/frontend_dist/assets/AccountPasswordSubForm-PkFPblkR.js +1 -0
- modoboa/frontend_dist/assets/AccountView-dgseekZ8.js +1 -0
- modoboa/frontend_dist/assets/AddressBook-DZWOHOJj.js +1 -0
- modoboa/frontend_dist/assets/AdminLayout-itB_mmH_.js +1 -0
- modoboa/frontend_dist/assets/AlarmsView-King6zb6.js +1 -0
- modoboa/frontend_dist/assets/{AliasEditView-BVjNJekt.js → AliasEditView-CTjrXYPf.js} +1 -1
- modoboa/frontend_dist/assets/AliasRecipientForm-CI7bXqVp.js +1 -0
- modoboa/frontend_dist/assets/{AliasView-DvvI6cV9.js → AliasView-BN13MNN_.js} +1 -1
- modoboa/frontend_dist/assets/AuditTrailView-C1jQwgoo.js +1 -0
- modoboa/frontend_dist/assets/CalendarView-juHVIHU5.css +1 -0
- modoboa/frontend_dist/assets/CalendarView-rRSzqxrH.js +1 -0
- modoboa/frontend_dist/assets/ChoiceField-DCr12shR.js +1 -0
- modoboa/frontend_dist/assets/ComposeEmailForm-BmtKwFb1.js +2 -0
- modoboa/frontend_dist/assets/ComposeEmailForm-CVNDl-Mq.css +1 -0
- modoboa/frontend_dist/assets/ComposeEmailView-JW22Phrb.js +1 -0
- modoboa/frontend_dist/assets/ConfirmDialog-CWpdwSQ6.js +1 -0
- modoboa/frontend_dist/assets/ConnectedLayout-BP8pO27H.js +1 -0
- modoboa/frontend_dist/assets/{ConnectedLayout-C6HNXWkp.css → ConnectedLayout-Ddpb_6yT.css} +1 -1
- modoboa/frontend_dist/assets/CreationForm-CzelJsVQ.js +1 -0
- modoboa/frontend_dist/assets/DashboardView-BBkBodVj.js +1 -0
- modoboa/frontend_dist/assets/{DomainAdminList-BO8_4Cwt.js → DomainAdminList-DD7p3i6F.js} +1 -1
- modoboa/frontend_dist/assets/{DomainEditView-BXylM4aB.js → DomainEditView-DoIoNYC4.js} +1 -1
- modoboa/frontend_dist/assets/{DomainTransportForm-Ccw5BXyM.js → DomainTransportForm-CbiJF9z5.js} +1 -1
- modoboa/frontend_dist/assets/{DomainView-Bd5XQI-1.js → DomainView-BXvznFYz.js} +3 -3
- modoboa/frontend_dist/assets/DomainsView-ffYjiffp.js +1 -0
- modoboa/frontend_dist/assets/EmailField-KhhJYA4D.js +1 -0
- modoboa/frontend_dist/assets/EmailSchedulingForm-CQL5Vfdr.js +1 -0
- modoboa/frontend_dist/assets/EmailView-Bq2bHZBO.js +1 -0
- modoboa/frontend_dist/assets/EmptyLayout-NrTftp18.js +1 -0
- modoboa/frontend_dist/assets/FiltersView-SOQy0U_3.js +1 -0
- modoboa/frontend_dist/assets/ForwardEmailView-kEPDjWUw.js +1 -0
- modoboa/frontend_dist/assets/{HtmlEditor-DNn3CY-w.js → HtmlEditor-CyBl5wj2.js} +15 -15
- modoboa/frontend_dist/assets/IdentitiesView-CZFf4oR9.js +1 -0
- modoboa/frontend_dist/assets/InformationView-CAtneAqM.js +1 -0
- modoboa/frontend_dist/assets/{LoadingData-CnQ_aVqa.js → LoadingData-C2txD49L.js} +1 -1
- modoboa/frontend_dist/assets/{LoginCallbackView-Nh_gmh8e.js → LoginCallbackView-DAvty3CI.js} +1 -1
- modoboa/frontend_dist/assets/{LoginView-BtC9BHuJ.js → LoginView-DHYITkn4.js} +1 -1
- modoboa/frontend_dist/assets/MailboxView-BNg2v7mi.css +1 -0
- modoboa/frontend_dist/assets/MailboxView-DHg5A78D.js +5 -0
- modoboa/frontend_dist/assets/MenuItems-C9p70bSC.js +1 -0
- modoboa/frontend_dist/assets/{MessageView-C1Goz-hY.js → MessageView-CrrEJpNI.js} +1 -1
- modoboa/frontend_dist/assets/MessagesView-CyecRd4I.js +1 -0
- modoboa/frontend_dist/assets/MigrationsView-DXGKKz2h.js +1 -0
- modoboa/frontend_dist/assets/{ParametersForm-BegEnkQM.js → ParametersForm-DFKYkPAs.js} +1 -1
- modoboa/frontend_dist/assets/ParametersView-B1wT5oyt.js +1 -0
- modoboa/frontend_dist/assets/ParametersView-CfvVzKea.js +1 -0
- modoboa/frontend_dist/assets/ProviderEditView-De-8ggBp.js +1 -0
- modoboa/frontend_dist/assets/{ProviderGeneralForm-BAELnXh6.js → ProviderGeneralForm-DO-IpJMQ.js} +1 -1
- modoboa/frontend_dist/assets/ProvidersView-Bhk7sCz4.js +1 -0
- modoboa/frontend_dist/assets/QuarantineLayout-C1YszNmP.js +1 -0
- modoboa/frontend_dist/assets/QuarantineView-cElD_rS-.js +1 -0
- modoboa/frontend_dist/assets/ReplyEmailView-BvuOZdLl.js +1 -0
- modoboa/frontend_dist/assets/ResourcesForm-FWxrmVwo.js +1 -0
- modoboa/frontend_dist/assets/SelfServiceLayout-JHRvfvQf.js +1 -0
- modoboa/frontend_dist/assets/SettingsView-SIsKAXtQ.css +1 -0
- modoboa/frontend_dist/assets/SettingsView-s2l2Xl1L.js +6 -0
- modoboa/frontend_dist/assets/StatisticsView-FiidWvad.js +1 -0
- modoboa/frontend_dist/assets/TimeSerieChart-CdqUHiO_.js +1 -0
- modoboa/frontend_dist/assets/TimeSerieChart-nLIFGI0y.css +1 -0
- modoboa/frontend_dist/assets/UserLayout-DyvI8duf.js +1 -0
- modoboa/frontend_dist/assets/{VAlert-Buv8Z5G4.js → VAlert-MDbeolOo.js} +1 -1
- modoboa/frontend_dist/assets/VApp-DM1KLQfQ.js +1 -0
- modoboa/frontend_dist/assets/VAutocomplete-C9NL5_uo.css +1 -0
- modoboa/frontend_dist/assets/VAutocomplete-CQsWYWNX.js +1 -0
- modoboa/frontend_dist/assets/VAvatar-CXV_FqpP.js +1 -0
- modoboa/frontend_dist/assets/VBadge-7tDx7aI3.js +1 -0
- modoboa/frontend_dist/assets/{VCard-D7AwBs4w.js → VCard-CBtX8JF-.js} +1 -1
- modoboa/frontend_dist/assets/VCheckbox-zY1MApOy.js +1 -0
- modoboa/frontend_dist/assets/{VCheckboxBtn-B5evxN9K.js → VCheckboxBtn-B0mIT3E0.js} +1 -1
- modoboa/frontend_dist/assets/VColorPicker-BbCHvk6K.js +1 -0
- modoboa/frontend_dist/assets/{VColorPicker-B_lVDaYR.css → VColorPicker-C9m8L-6U.css} +1 -1
- modoboa/frontend_dist/assets/{VContainer-DY9021j4.js → VContainer-Cn-vKB3s.js} +1 -1
- modoboa/frontend_dist/assets/VDataTable-CNT9KOSp.js +1 -0
- modoboa/frontend_dist/assets/VDataTableServer-_yn4Ry6U.js +1 -0
- modoboa/frontend_dist/assets/VDataTableVirtual-Csxh3Gp6.js +1 -0
- modoboa/frontend_dist/assets/VDatePicker-_OUDqShN.css +1 -0
- modoboa/frontend_dist/assets/VDatePicker-iFu13xIP.js +2 -0
- modoboa/frontend_dist/assets/VDialog-lKtBdqnp.js +1 -0
- modoboa/frontend_dist/assets/VExpansionPanels-CaIvk9iF.js +1 -0
- modoboa/frontend_dist/assets/VFileInput-C7J_qVmk.js +1 -0
- modoboa/frontend_dist/assets/VForm-fdQ5d-CH.js +1 -0
- modoboa/frontend_dist/assets/VInput-BwHvhzAe.js +1 -0
- modoboa/frontend_dist/assets/VMenu-BpmJf4X2.js +1 -0
- modoboa/frontend_dist/assets/VMenu-C5A_5Hs5.css +1 -0
- modoboa/frontend_dist/assets/VPicker-B7cB3kJg.css +1 -0
- modoboa/frontend_dist/assets/VPicker-BZho70wU.js +1 -0
- modoboa/frontend_dist/assets/VProgressCircular-DRw_-iNj.js +1 -0
- modoboa/frontend_dist/assets/VRadioGroup--eiP5xtJ.js +1 -0
- modoboa/frontend_dist/assets/{VRow-DyK_Mj7R.js → VRow-83Qnr5iB.js} +1 -1
- modoboa/frontend_dist/assets/VSelect-BcmGFGif.js +1 -0
- modoboa/frontend_dist/assets/{VSelectionControl-Dkms9_P5.js → VSelectionControl-Cqi1xt-q.js} +1 -1
- modoboa/frontend_dist/assets/VSheet-C3MaHhtw.js +1 -0
- modoboa/frontend_dist/assets/VSpacer-CuSkdJZL.js +1 -0
- modoboa/frontend_dist/assets/VSwitch-XumUl685.js +1 -0
- modoboa/frontend_dist/assets/{VTable-D8ZwwC_B.js → VTable-f7wcr2AZ.js} +1 -1
- modoboa/frontend_dist/assets/VTabs-CDfdejXj.css +1 -0
- modoboa/frontend_dist/assets/VTabs-y4wNP4im.js +1 -0
- modoboa/frontend_dist/assets/VTextField-BPIvtrn4.js +1 -0
- modoboa/frontend_dist/assets/VTextField-DjbYGlzs.css +1 -0
- modoboa/frontend_dist/assets/VTextarea-B6bGYcC3.js +1 -0
- modoboa/frontend_dist/assets/VTextarea-f6vTjzFy.css +1 -0
- modoboa/frontend_dist/assets/VToolbar-BYDPtwf0.css +1 -0
- modoboa/frontend_dist/assets/VToolbar-CB6wwtYc.js +1 -0
- modoboa/frontend_dist/assets/VWindowItem-CDtLLEkg.js +1 -0
- modoboa/frontend_dist/assets/WebmailLayout-CQQAolnl.css +1 -0
- modoboa/frontend_dist/assets/WebmailLayout-tosQSHLS.js +1 -0
- modoboa/frontend_dist/assets/{accounts-BZBqbAmd.js → accounts-KUsk6LHW.js} +1 -1
- modoboa/frontend_dist/assets/{admin-uCrKjpln.js → admin-BW9cZW0P.js} +1 -1
- modoboa/frontend_dist/assets/{aliases-CFwFkX45.js → aliases-Ge0hjIsH.js} +1 -1
- modoboa/frontend_dist/assets/{amavis-DCy0U0TD.js → amavis-BbFeFfsk.js} +1 -1
- modoboa/frontend_dist/assets/{amavis-B4ScfQsG.js → amavis-DtuzP_CS.js} +1 -1
- modoboa/frontend_dist/assets/{contacts-DKmh2q-4.js → contacts-DMJlQTe0.js} +1 -1
- modoboa/frontend_dist/assets/{domains-CDE4_LDF.js → domains-Du64lcXT.js} +1 -1
- modoboa/frontend_dist/assets/{domains.store-a9Y5UlDV.js → domains.store-1U61jeCV.js} +1 -1
- modoboa/frontend_dist/assets/events-BM3in65C.js +1 -0
- modoboa/frontend_dist/assets/filter-Dihm6o59.js +1 -0
- modoboa/frontend_dist/assets/importExport-HGcNGWOm.js +1 -0
- modoboa/frontend_dist/assets/{index-B9q1vO3K.css → index-B1EK3MQe.css} +1 -1
- modoboa/frontend_dist/assets/{index-Dwg6jPTX.js → index-Dv00bmw9.js} +1 -1
- modoboa/frontend_dist/assets/{index-BjibLozh.js → index-jui3edpn.js} +47 -41
- modoboa/frontend_dist/assets/{language.store-DGuI8jG0.js → language.store-OcfdXL_-.js} +1 -1
- modoboa/frontend_dist/assets/languages-CF8hxo7x.js +1 -0
- modoboa/frontend_dist/assets/{layout-5PvsmkGR.js → layout-DOO7TRTJ.js} +1 -1
- modoboa/frontend_dist/assets/{layout.store-DJVcXtxk.js → layout.store-C0g-piJn.js} +1 -1
- modoboa/frontend_dist/assets/{logos-CMmd0hOG.js → logos-Dz2Gzei-.js} +1 -1
- modoboa/frontend_dist/assets/{logs-D2GOydPZ.js → logs-CLm32Weu.js} +1 -1
- modoboa/frontend_dist/assets/{parameters-DJdlq9sh.js → parameters-DMIAQ7cd.js} +1 -1
- modoboa/frontend_dist/assets/{parameters.store-DH3pPlaT.js → parameters.store-DLnFzCwV.js} +1 -1
- modoboa/frontend_dist/assets/{permissions-CgFJFhCp.js → permissions-CrpE0b4w.js} +1 -1
- modoboa/frontend_dist/assets/{ssrBoot-ChTB-PYP.js → ssrBoot-B7cr7q9U.js} +1 -1
- modoboa/frontend_dist/assets/{tag-CvrHE14f.js → tag-WF93n81Q.js} +1 -1
- modoboa/frontend_dist/assets/{theme-DFzlhSh5.js → theme-_0oOYChG.js} +1 -1
- modoboa/frontend_dist/assets/transports-CS61syt-.js +1 -0
- modoboa/frontend_dist/assets/webmail-CYDXU0DS.js +1 -0
- modoboa/frontend_dist/assets/webmail.store-BvHCQSjM.js +1 -0
- modoboa/frontend_dist/index.html +2 -2
- modoboa/imap_migration/models.py +3 -3
- modoboa/imap_migration/templates/imap_migration/offlineimap.conf +2 -2
- modoboa/imap_migration/tests.py +21 -0
- modoboa/lib/cryptutils.py +2 -2
- modoboa/lib/sysutils.py +1 -1
- modoboa/lib/tests/__init__.py +0 -1
- modoboa/locale/br/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/br/LC_MESSAGES/django.po +301 -273
- modoboa/locale/cs/LC_MESSAGES/django.po +286 -269
- modoboa/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/cs_CZ/LC_MESSAGES/django.po +303 -275
- modoboa/locale/de/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/de/LC_MESSAGES/django.po +297 -269
- modoboa/locale/de_DE/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/de_DE/LC_MESSAGES/django.po +303 -275
- modoboa/locale/el_GR/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/el_GR/LC_MESSAGES/django.po +303 -275
- modoboa/locale/en/LC_MESSAGES/django.po +286 -269
- modoboa/locale/es/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/es/LC_MESSAGES/django.po +302 -274
- modoboa/locale/es_MX/LC_MESSAGES/django.po +286 -269
- modoboa/locale/fi/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/fi/LC_MESSAGES/django.po +299 -271
- modoboa/locale/fr/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/fr/LC_MESSAGES/django.po +288 -271
- modoboa/locale/hu/LC_MESSAGES/django.po +286 -269
- modoboa/locale/it/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/it/LC_MESSAGES/django.po +303 -275
- modoboa/locale/ja_JP/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/ja_JP/LC_MESSAGES/django.po +390 -413
- modoboa/locale/ka/LC_MESSAGES/django.po +286 -269
- modoboa/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/nl_NL/LC_MESSAGES/django.po +299 -271
- modoboa/locale/no/LC_MESSAGES/django.po +286 -269
- modoboa/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/pl_PL/LC_MESSAGES/django.po +297 -269
- modoboa/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/pt_BR/LC_MESSAGES/django.po +298 -270
- modoboa/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/pt_PT/LC_MESSAGES/django.po +303 -275
- modoboa/locale/ro_RO/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/ro_RO/LC_MESSAGES/django.po +299 -271
- modoboa/locale/ru/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/ru/LC_MESSAGES/django.po +993 -1096
- modoboa/locale/si/LC_MESSAGES/django.po +286 -269
- modoboa/locale/sk/LC_MESSAGES/django.po +286 -269
- modoboa/locale/sk_SK/LC_MESSAGES/django.po +286 -269
- modoboa/locale/sl_SI/LC_MESSAGES/django.po +288 -269
- modoboa/locale/sv/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/sv/LC_MESSAGES/django.po +297 -269
- modoboa/locale/tr/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/tr/LC_MESSAGES/django.po +301 -272
- modoboa/locale/tr_TR/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/tr_TR/LC_MESSAGES/django.po +291 -269
- modoboa/locale/uk/LC_MESSAGES/django.po +286 -269
- modoboa/locale/zh/LC_MESSAGES/django.po +286 -269
- modoboa/locale/zh_CN/LC_MESSAGES/django.po +286 -269
- modoboa/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/zh_TW/LC_MESSAGES/django.po +295 -273
- modoboa/maillog/jobs.py +11 -0
- modoboa/maillog/tests/test_views.py +5 -4
- modoboa/parameters/api/v2/tests.py +1 -1
- modoboa/policyd/management/commands/policy_daemon.py +5 -1
- modoboa/policyd/tests.py +4 -2
- modoboa/rspamd/tests.py +20 -0
- modoboa/templates/registration/twofactor_code_verify.html +1 -1
- modoboa/webmail/app_settings.py +37 -0
- modoboa/webmail/constants.py +21 -1
- modoboa/webmail/factories.py +19 -0
- modoboa/webmail/jobs.py +27 -0
- modoboa/webmail/lib/__init__.py +0 -2
- modoboa/webmail/lib/imapemail.py +9 -11
- modoboa/webmail/lib/imapheader.py +25 -13
- modoboa/webmail/lib/imaputils.py +51 -13
- modoboa/webmail/lib/sendmail.py +88 -105
- modoboa/webmail/lib/utils.py +109 -0
- modoboa/webmail/migrations/0001_initial.py +90 -0
- modoboa/webmail/migrations/__init__.py +0 -0
- modoboa/webmail/mocks.py +12 -3
- modoboa/webmail/models.py +102 -0
- modoboa/webmail/serializers.py +84 -4
- modoboa/webmail/tests/data.py +21 -0
- modoboa/webmail/tests/test_lib_imaputils.py +33 -0
- modoboa/webmail/tests/test_viewsets.py +108 -0
- modoboa/webmail/urls.py +5 -0
- modoboa/webmail/viewsets.py +39 -9
- {modoboa-2.6.5.dist-info → modoboa-2.7.0.dist-info}/METADATA +16 -13
- {modoboa-2.6.5.dist-info → modoboa-2.7.0.dist-info}/RECORD +269 -252
- {modoboa-2.6.5.dist-info → modoboa-2.7.0.dist-info}/WHEEL +1 -1
- modoboa/admin/management/commands/handle_mailbox_operations.py +0 -103
- modoboa/core/management/commands/cleanlogs.py +0 -53
- modoboa/frontend_dist/assets/AccountLayout-APeC9bsN.js +0 -1
- modoboa/frontend_dist/assets/AccountPasswordSubForm-D4vB2yQK.js +0 -1
- modoboa/frontend_dist/assets/AccountView-CloBP1BY.js +0 -1
- modoboa/frontend_dist/assets/AddressBook-C03MXg3y.js +0 -1
- modoboa/frontend_dist/assets/AdminLayout-DFmyKOo6.js +0 -1
- modoboa/frontend_dist/assets/AlarmsView-CFhH0I5d.js +0 -1
- modoboa/frontend_dist/assets/AliasRecipientForm-CZ_SvjtG.js +0 -1
- modoboa/frontend_dist/assets/AuditTrailView-Cyq25Jle.js +0 -1
- modoboa/frontend_dist/assets/CalendarView-C_dSXRp9.js +0 -1
- modoboa/frontend_dist/assets/ChoiceField-BPhUvoyr.js +0 -1
- modoboa/frontend_dist/assets/ComposeEmailForm-D3yUbBJX.css +0 -1
- modoboa/frontend_dist/assets/ComposeEmailForm-Ns5SYs4J.js +0 -1
- modoboa/frontend_dist/assets/ComposeEmailView-DgUfiWEH.js +0 -1
- modoboa/frontend_dist/assets/ConfirmDialog-BMIg94-U.js +0 -1
- modoboa/frontend_dist/assets/ConnectedLayout-CSXdVJtV.js +0 -1
- modoboa/frontend_dist/assets/CreationForm-CaAZYATm.js +0 -1
- modoboa/frontend_dist/assets/DashboardView-DErmyUoy.js +0 -1
- modoboa/frontend_dist/assets/DomainsView-CC1EwRId.js +0 -1
- modoboa/frontend_dist/assets/EmailField-BFgga4zp.js +0 -1
- modoboa/frontend_dist/assets/EmailView-DPWTch_6.js +0 -1
- modoboa/frontend_dist/assets/EmptyLayout-DAw7fAab.js +0 -1
- modoboa/frontend_dist/assets/FiltersView-Cg9lkLw0.js +0 -1
- modoboa/frontend_dist/assets/ForwardEmailView-Dd1sxgaP.js +0 -1
- modoboa/frontend_dist/assets/IdentitiesView-CiHIQ9Qt.js +0 -1
- modoboa/frontend_dist/assets/InformationView-Cc0aBA5j.js +0 -1
- modoboa/frontend_dist/assets/MailboxView-DIVABvk5.css +0 -1
- modoboa/frontend_dist/assets/MailboxView-DplC0IXR.js +0 -5
- modoboa/frontend_dist/assets/MenuItems-efx2rqwT.js +0 -1
- modoboa/frontend_dist/assets/MessagesView-CesYYZ9f.js +0 -1
- modoboa/frontend_dist/assets/MigrationsView-DSBk8T0y.js +0 -1
- modoboa/frontend_dist/assets/ParametersView-B8W_PAiz.js +0 -1
- modoboa/frontend_dist/assets/ParametersView-Dsx2M_g9.js +0 -1
- modoboa/frontend_dist/assets/ProviderEditView-PEAr7MA1.js +0 -1
- modoboa/frontend_dist/assets/ProvidersView-CHvQf3Vv.js +0 -1
- modoboa/frontend_dist/assets/QuarantineLayout-BWEaBK48.js +0 -1
- modoboa/frontend_dist/assets/QuarantineView-BzjeDhTm.js +0 -1
- modoboa/frontend_dist/assets/ReplyEmailView-CwQ9jsce.js +0 -1
- modoboa/frontend_dist/assets/ResourcesForm-noC3AXd9.js +0 -1
- modoboa/frontend_dist/assets/SelfServiceLayout-DQgRMu0L.js +0 -1
- modoboa/frontend_dist/assets/SettingsView-DJTdIeRK.css +0 -1
- modoboa/frontend_dist/assets/SettingsView-Dgwpfd8O.js +0 -6
- modoboa/frontend_dist/assets/StatisticsView-BZkVSh5o.js +0 -1
- modoboa/frontend_dist/assets/TimeSerieChart-BXJwhTMO.js +0 -1
- modoboa/frontend_dist/assets/TimeSerieChart-C3XHmlRd.css +0 -1
- modoboa/frontend_dist/assets/UserLayout-CWK9ICfE.js +0 -1
- modoboa/frontend_dist/assets/VApp-CiIBWB_Q.js +0 -1
- modoboa/frontend_dist/assets/VAutocomplete-DcNqTIie.js +0 -1
- modoboa/frontend_dist/assets/VAutocomplete-hzGuLlUI.css +0 -1
- modoboa/frontend_dist/assets/VAvatar-BsHplcRP.js +0 -1
- modoboa/frontend_dist/assets/VBadge-CavP_E2g.js +0 -1
- modoboa/frontend_dist/assets/VCheckbox-CKW9lz5i.js +0 -1
- modoboa/frontend_dist/assets/VColorPicker-Bsrz8yif.js +0 -1
- modoboa/frontend_dist/assets/VDataTable-ocZo8ju0.js +0 -1
- modoboa/frontend_dist/assets/VDataTableServer-CJfpYXQW.js +0 -1
- modoboa/frontend_dist/assets/VDataTableVirtual-Cr8yw49k.js +0 -1
- modoboa/frontend_dist/assets/VDialog-CUMh3paI.js +0 -1
- modoboa/frontend_dist/assets/VExpansionPanels-CkwHiKsA.js +0 -1
- modoboa/frontend_dist/assets/VFileInput-D5qlksIG.js +0 -1
- modoboa/frontend_dist/assets/VForm-ClJU5x_v.js +0 -1
- modoboa/frontend_dist/assets/VInput-DhzSMFXh.js +0 -1
- modoboa/frontend_dist/assets/VMenu-B53a3gm6.js +0 -1
- modoboa/frontend_dist/assets/VMenu-BEipA1lw.css +0 -1
- modoboa/frontend_dist/assets/VPicker-B928ZVJQ.js +0 -1
- modoboa/frontend_dist/assets/VPicker-ClSXs6kv.css +0 -1
- modoboa/frontend_dist/assets/VProgressCircular-viI3jDXe.js +0 -1
- modoboa/frontend_dist/assets/VRadioGroup-C0bqFrYg.js +0 -1
- modoboa/frontend_dist/assets/VSelect-y0E5_vPn.js +0 -1
- modoboa/frontend_dist/assets/VSheet-DQkh_hsX.js +0 -1
- modoboa/frontend_dist/assets/VSpacer-CBqB9uSt.js +0 -1
- modoboa/frontend_dist/assets/VSwitch-D4xdR9jz.js +0 -1
- modoboa/frontend_dist/assets/VTabs-D2c0KeF2.js +0 -1
- modoboa/frontend_dist/assets/VTabs-NzpINroH.css +0 -1
- modoboa/frontend_dist/assets/VTextField-Cow3HZvI.css +0 -1
- modoboa/frontend_dist/assets/VTextField-DvvH1ciR.js +0 -1
- modoboa/frontend_dist/assets/VTextarea-DHtXtzqP.js +0 -1
- modoboa/frontend_dist/assets/VTextarea-DyGjqrlm.css +0 -1
- modoboa/frontend_dist/assets/VToolbar-CB2GrZpA.css +0 -1
- modoboa/frontend_dist/assets/VToolbar-pvLQtmbU.js +0 -1
- modoboa/frontend_dist/assets/VWindowItem-DErNPSH8.js +0 -1
- modoboa/frontend_dist/assets/WebmailLayout-BzW0LWYp.css +0 -1
- modoboa/frontend_dist/assets/WebmailLayout-D7J-QJ-0.js +0 -1
- modoboa/frontend_dist/assets/filter-BVvMTmPG.js +0 -1
- modoboa/frontend_dist/assets/global.store-Bha4Z76j.js +0 -1
- modoboa/frontend_dist/assets/importExport-EgijWlC1.js +0 -1
- modoboa/frontend_dist/assets/languages-Cge6pECg.js +0 -1
- modoboa/frontend_dist/assets/transports-BUSYxSF4.js +0 -1
- modoboa/frontend_dist/assets/webmail-HVg3ZHC0.js +0 -1
- modoboa/frontend_dist/assets/webmail.store-D-d88s4w.js +0 -1
- {modoboa-2.6.5.data → modoboa-2.7.0.data}/scripts/modoboa-admin.py +0 -0
- {modoboa-2.6.5.dist-info → modoboa-2.7.0.dist-info}/entry_points.txt +0 -0
- {modoboa-2.6.5.dist-info → modoboa-2.7.0.dist-info}/licenses/LICENSE +0 -0
- {modoboa-2.6.5.dist-info → modoboa-2.7.0.dist-info}/top_level.txt +0 -0
modoboa/maillog/jobs.py
ADDED
|
@@ -11,6 +11,7 @@ from django.test import override_settings
|
|
|
11
11
|
|
|
12
12
|
from modoboa.admin import factories as admin_factories
|
|
13
13
|
from modoboa.lib.tests import SimpleModoTestCase
|
|
14
|
+
from modoboa.maillog import jobs
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class RunCommandsMixin:
|
|
@@ -36,14 +37,14 @@ class RunCommandsMixin:
|
|
|
36
37
|
with open(path, "w") as fp:
|
|
37
38
|
fp.write(content)
|
|
38
39
|
self.set_global_parameter("logfile", path)
|
|
39
|
-
|
|
40
|
+
jobs.logparser()
|
|
40
41
|
|
|
41
42
|
def run_update_statistics(self, rebuild=False):
|
|
42
43
|
"""Run update_statistics command."""
|
|
43
|
-
args = []
|
|
44
44
|
if rebuild:
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
call_command("update_statistics", ["--rebuild"])
|
|
46
|
+
else:
|
|
47
|
+
jobs.update_statistics()
|
|
47
48
|
|
|
48
49
|
|
|
49
50
|
@override_settings(RRDTOOL_TEST_MODE=True)
|
|
@@ -18,7 +18,7 @@ class GlobalParametersAPITestCase(ModoAPITestCase):
|
|
|
18
18
|
resp = self.client.get(url)
|
|
19
19
|
self.assertEqual(resp.status_code, 200)
|
|
20
20
|
# core, admin, limits, pdf credentials, ...
|
|
21
|
-
self.assertEqual(len(resp.json()),
|
|
21
|
+
self.assertEqual(len(resp.json()), 12)
|
|
22
22
|
|
|
23
23
|
def test_get_structure(self):
|
|
24
24
|
url = reverse("v2:parameter-global-structure")
|
|
@@ -31,7 +31,11 @@ class Command(BaseCommand):
|
|
|
31
31
|
|
|
32
32
|
def handle(self, *args, **options):
|
|
33
33
|
"""Entry point."""
|
|
34
|
-
|
|
34
|
+
try:
|
|
35
|
+
loop = asyncio.get_event_loop()
|
|
36
|
+
except RuntimeError:
|
|
37
|
+
loop = asyncio.new_event_loop()
|
|
38
|
+
asyncio.set_event_loop(loop)
|
|
35
39
|
coro = asyncio.start_server(
|
|
36
40
|
core.new_connection, options["host"], options["port"]
|
|
37
41
|
)
|
modoboa/policyd/tests.py
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
import asyncio
|
|
4
4
|
from aiosmtplib import send
|
|
5
5
|
from unittest.mock import AsyncMock
|
|
6
|
+
import multiprocessing
|
|
6
7
|
from multiprocessing import Process
|
|
7
8
|
import socket
|
|
8
9
|
import time
|
|
9
10
|
|
|
10
|
-
|
|
11
11
|
from django import db
|
|
12
12
|
from django.core.management import call_command
|
|
13
13
|
from django.test import TransactionTestCase
|
|
@@ -21,6 +21,8 @@ from modoboa.policyd import core as policyd_core
|
|
|
21
21
|
|
|
22
22
|
from . import constants
|
|
23
23
|
|
|
24
|
+
multiprocessing.set_start_method("fork")
|
|
25
|
+
|
|
24
26
|
|
|
25
27
|
def start_policy_daemon():
|
|
26
28
|
call_command("policy_daemon")
|
|
@@ -56,7 +58,7 @@ class PolicyDaemonTestCase(RedisTestCaseMixin, ParametersMixin, TransactionTestC
|
|
|
56
58
|
self.process.daemon = True
|
|
57
59
|
self.process.start()
|
|
58
60
|
# Wait a bit for the daemon to start
|
|
59
|
-
self.process.join(0
|
|
61
|
+
self.process.join(1.0)
|
|
60
62
|
|
|
61
63
|
def set_domain_limit(self, name, value):
|
|
62
64
|
"""Set daily limit for domain."""
|
modoboa/rspamd/tests.py
CHANGED
|
@@ -2,12 +2,18 @@ import os
|
|
|
2
2
|
import shutil
|
|
3
3
|
|
|
4
4
|
from django.core import management
|
|
5
|
+
from django.test import modify_settings
|
|
5
6
|
from django.urls import reverse
|
|
6
7
|
|
|
7
8
|
from modoboa.admin import factories as admin_factories, models as admin_models
|
|
8
9
|
from modoboa.lib.tests import ModoAPITestCase
|
|
9
10
|
|
|
10
11
|
|
|
12
|
+
@modify_settings(
|
|
13
|
+
INSTALLED_APPS={
|
|
14
|
+
"append": "modoboa.rspamd",
|
|
15
|
+
}
|
|
16
|
+
)
|
|
11
17
|
class ManagementCommandTestCase(ModoAPITestCase):
|
|
12
18
|
|
|
13
19
|
@classmethod
|
|
@@ -76,3 +82,17 @@ class ParametersAPITestCase(ModoAPITestCase):
|
|
|
76
82
|
}
|
|
77
83
|
response = self.client.put(url, data, format="json")
|
|
78
84
|
self.assertEqual(response.status_code, 200)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@modify_settings(
|
|
88
|
+
MODOBOA_APPS={
|
|
89
|
+
"append": "modoboa.rspamd",
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
class CapabilitiesAPITestCase(ModoAPITestCase):
|
|
93
|
+
|
|
94
|
+
def test_get(self):
|
|
95
|
+
url = reverse("v2:capabilities")
|
|
96
|
+
response = self.client.get(url)
|
|
97
|
+
self.assertEqual(response.status_code, 200)
|
|
98
|
+
self.assertIn("rspamd", response.json()["capabilities"])
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
<input type="hidden" id="id_next" name="next" value="{{ nextlocation }}">
|
|
26
26
|
<div class="input-group{% if form.tfa_code.errors %} error{% endif %}">
|
|
27
27
|
<label>{% translate "One-time password" %}</label>
|
|
28
|
-
<input id="id_tfa_code" name="tfa_code" required>
|
|
28
|
+
<input id="id_tfa_code" name="tfa_code" required autofocus>
|
|
29
29
|
<span class="error-message">
|
|
30
30
|
{% for error in form.tfa_code.errors %}
|
|
31
31
|
{{ error }}
|
modoboa/webmail/app_settings.py
CHANGED
|
@@ -107,6 +107,43 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict(
|
|
|
107
107
|
),
|
|
108
108
|
},
|
|
109
109
|
),
|
|
110
|
+
(
|
|
111
|
+
"scheduling",
|
|
112
|
+
{
|
|
113
|
+
"label": gettext_lazy("Scheduling settings"),
|
|
114
|
+
"params": collections.OrderedDict(
|
|
115
|
+
[
|
|
116
|
+
(
|
|
117
|
+
"scheduling_smtp_server",
|
|
118
|
+
{
|
|
119
|
+
"label": gettext_lazy("Server address"),
|
|
120
|
+
"help_text": gettext_lazy(
|
|
121
|
+
"Address of your SMTP server"
|
|
122
|
+
),
|
|
123
|
+
},
|
|
124
|
+
),
|
|
125
|
+
(
|
|
126
|
+
"scheduling_smtp_secured_mode",
|
|
127
|
+
{
|
|
128
|
+
"label": gettext_lazy("Secured connection mode"),
|
|
129
|
+
"help_text": gettext_lazy(
|
|
130
|
+
"Use a secured connection to access SMTP server"
|
|
131
|
+
),
|
|
132
|
+
},
|
|
133
|
+
),
|
|
134
|
+
(
|
|
135
|
+
"scheduling_smtp_port",
|
|
136
|
+
{
|
|
137
|
+
"label": gettext_lazy("Server port"),
|
|
138
|
+
"help_text": gettext_lazy(
|
|
139
|
+
"Listening port of your SMTP server"
|
|
140
|
+
),
|
|
141
|
+
},
|
|
142
|
+
),
|
|
143
|
+
]
|
|
144
|
+
),
|
|
145
|
+
},
|
|
146
|
+
),
|
|
110
147
|
]
|
|
111
148
|
)
|
|
112
149
|
|
modoboa/webmail/constants.py
CHANGED
|
@@ -5,7 +5,27 @@ from enum import Enum
|
|
|
5
5
|
from django.utils.translation import gettext_lazy as _
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
MAILBOX_TYPES = ["inbox", "draft", "sent", "junk", "trash", "normal"]
|
|
8
|
+
MAILBOX_TYPES = ["inbox", "draft", "sent", "scheduled", "junk", "trash", "normal"]
|
|
9
|
+
|
|
10
|
+
MAILBOX_NAME_SCHEDULED = "Scheduled"
|
|
11
|
+
|
|
12
|
+
CUSTOM_HEADER_SCHEDULED_ID = "X-Scheduled-ID"
|
|
13
|
+
CUSTOM_HEADER_SCHEDULED_DATETIME = "X-Scheduled-Datetime"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SchedulingState(Enum):
|
|
17
|
+
SCHEDULED = "scheduled"
|
|
18
|
+
SENDING = "sending"
|
|
19
|
+
SEND_ERROR = "send_error"
|
|
20
|
+
MOVE_ERROR = "move_error"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
EMAIL_SCHEDULING_STATES = (
|
|
24
|
+
(SchedulingState.SCHEDULED.value, "scheduled"),
|
|
25
|
+
(SchedulingState.SENDING.value, "sending"),
|
|
26
|
+
(SchedulingState.SEND_ERROR.value, "send_error"),
|
|
27
|
+
(SchedulingState.MOVE_ERROR.value, "move_error"),
|
|
28
|
+
)
|
|
9
29
|
|
|
10
30
|
|
|
11
31
|
class SmtpConnectionMode(Enum):
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Webmail factories."""
|
|
2
|
+
|
|
3
|
+
import factory
|
|
4
|
+
|
|
5
|
+
from modoboa.admin import factories as admin_factories
|
|
6
|
+
|
|
7
|
+
from modoboa.webmail import models
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ScheduledMessageFactory(factory.django.DjangoModelFactory):
|
|
11
|
+
"""Scheduled message factory."""
|
|
12
|
+
|
|
13
|
+
class Meta:
|
|
14
|
+
model = models.ScheduledMessage
|
|
15
|
+
|
|
16
|
+
account = factory.SubFactory(admin_factories.UserFactory)
|
|
17
|
+
sender = "sender@test.com"
|
|
18
|
+
subject = "Scheduled Message"
|
|
19
|
+
to = "recipient@domain.test"
|
modoboa/webmail/jobs.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Async (RQ) jobs definition."""
|
|
2
|
+
|
|
3
|
+
from django.utils import timezone
|
|
4
|
+
|
|
5
|
+
import django_rq
|
|
6
|
+
|
|
7
|
+
from modoboa.webmail import constants, models
|
|
8
|
+
from modoboa.webmail.lib import sendmail
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def send_scheduled_message(message_id: int):
|
|
12
|
+
message = models.ScheduledMessage.objects.get(id=message_id)
|
|
13
|
+
if sendmail.send_scheduled_message(message):
|
|
14
|
+
message.delete()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def send_scheduled_messages():
|
|
18
|
+
now = timezone.now().replace(second=0, microsecond=0)
|
|
19
|
+
messages = models.ScheduledMessage.objects.filter(
|
|
20
|
+
scheduled_datetime__lte=now,
|
|
21
|
+
status=constants.SchedulingState.SCHEDULED.value,
|
|
22
|
+
)
|
|
23
|
+
queue = django_rq.get_queue("modoboa")
|
|
24
|
+
for message in messages:
|
|
25
|
+
message.status = constants.SchedulingState.SENDING.value
|
|
26
|
+
message.save()
|
|
27
|
+
queue.enqueue(send_scheduled_message, message_id=message.id)
|
modoboa/webmail/lib/__init__.py
CHANGED
|
@@ -7,7 +7,6 @@ from .imapemail import ImapEmail, ReplyModifier, ForwardModifier
|
|
|
7
7
|
from .imaputils import BodyStructure, IMAPconnector, get_imapconnector, separate_mailbox
|
|
8
8
|
from .signature import EmailSignature
|
|
9
9
|
from .utils import decode_payload
|
|
10
|
-
from .sendmail import send_mail
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
__all__ = [
|
|
@@ -22,6 +21,5 @@ __all__ = [
|
|
|
22
21
|
"decode_payload",
|
|
23
22
|
"get_imapconnector",
|
|
24
23
|
"save_attachment",
|
|
25
|
-
"send_mail",
|
|
26
24
|
"separate_mailbox",
|
|
27
25
|
]
|
modoboa/webmail/lib/imapemail.py
CHANGED
|
@@ -18,6 +18,7 @@ from django.utils.translation import gettext as _
|
|
|
18
18
|
from modoboa.contacts import models as contacts_models
|
|
19
19
|
from modoboa.lib import u2u_decode
|
|
20
20
|
from modoboa.lib.email_utils import Email
|
|
21
|
+
from modoboa.webmail import constants
|
|
21
22
|
|
|
22
23
|
from . import imapheader
|
|
23
24
|
from .attachments import get_storage_path
|
|
@@ -45,19 +46,24 @@ class ImapEmail(Email):
|
|
|
45
46
|
self.imapc = self.imapc.__enter__()
|
|
46
47
|
self.mbox, self.mailid = self.mailid.split(":")
|
|
47
48
|
self.attachments: dict[str, str] = {}
|
|
49
|
+
self.To: list = []
|
|
48
50
|
|
|
49
51
|
def __del__(self):
|
|
50
52
|
self.imapc.__exit__()
|
|
51
53
|
|
|
52
54
|
def fetch_headers(self, raw_addresses: bool = False) -> None:
|
|
53
55
|
"""Fetch message headers from server."""
|
|
56
|
+
requested_headers = self.headernames
|
|
57
|
+
if self.mbox == constants.MAILBOX_NAME_SCHEDULED:
|
|
58
|
+
requested_headers += [(constants.CUSTOM_HEADER_SCHEDULED_DATETIME, True)]
|
|
59
|
+
header_names = [header[0].upper() for header in requested_headers]
|
|
54
60
|
msg = self.imapc.fetchmail(
|
|
55
|
-
self.mbox, self.mailid, readonly=False, what=" ".join(
|
|
61
|
+
self.mbox, self.mailid, readonly=False, what=" ".join(header_names)
|
|
56
62
|
)
|
|
57
|
-
headers = msg[f"BODY[HEADER.FIELDS ({
|
|
63
|
+
headers = msg[f"BODY[HEADER.FIELDS ({' '.join(header_names)})]"]
|
|
58
64
|
self.fetch_body_structure(msg)
|
|
59
65
|
msg = email.message_from_string(headers)
|
|
60
|
-
for hdr in
|
|
66
|
+
for hdr in requested_headers:
|
|
61
67
|
label = hdr[0]
|
|
62
68
|
hdrvalue = self.get_header(msg, label, raw=raw_addresses)
|
|
63
69
|
if not hdrvalue:
|
|
@@ -110,14 +116,6 @@ class ImapEmail(Email):
|
|
|
110
116
|
self.dformat if self.dformat in self.bs.contents else fallback_fmt
|
|
111
117
|
)
|
|
112
118
|
|
|
113
|
-
@property
|
|
114
|
-
def headers_as_list(self):
|
|
115
|
-
return [hdr[0].upper() for hdr in self.headernames]
|
|
116
|
-
|
|
117
|
-
@property
|
|
118
|
-
def headers_as_text(self):
|
|
119
|
-
return " ".join(self.headers_as_list)
|
|
120
|
-
|
|
121
119
|
@property
|
|
122
120
|
def subject(self):
|
|
123
121
|
"""Just a shortcut to return a subject in any case."""
|
|
@@ -27,19 +27,19 @@ __all__ = [
|
|
|
27
27
|
# according to https://en.wikipedia.org/wiki/Date_format_by_country
|
|
28
28
|
# and https://en.wikipedia.org/wiki/Date_and_time_representation_by_country
|
|
29
29
|
DATETIME_FORMATS = {
|
|
30
|
-
"cs": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
|
|
31
|
-
"de": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
|
|
32
|
-
"en": {"SHORT": "l, P", "LONG": "N j, Y P"},
|
|
33
|
-
"es": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
|
|
34
|
-
"fr": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
|
|
35
|
-
"it": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
|
|
36
|
-
"ja_JP": {"SHORT": "l, P", "LONG": "N j, Y P"},
|
|
37
|
-
"nl": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
|
|
38
|
-
"pl_PL": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
|
|
39
|
-
"pt_PT": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
|
|
40
|
-
"pt_BR": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
|
|
41
|
-
"ru": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
|
|
42
|
-
"sv": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
|
|
30
|
+
"cs": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
|
|
31
|
+
"de": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
|
|
32
|
+
"en": {"SHORT": "l, P", "LONG": "N j, Y P", "TIME": "H:i"},
|
|
33
|
+
"es": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
|
|
34
|
+
"fr": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
|
|
35
|
+
"it": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
|
|
36
|
+
"ja_JP": {"SHORT": "l, P", "LONG": "N j, Y P", "TIME": "H:i"},
|
|
37
|
+
"nl": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
|
|
38
|
+
"pl_PL": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
|
|
39
|
+
"pt_PT": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
|
|
40
|
+
"pt_BR": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
|
|
41
|
+
"ru": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
|
|
42
|
+
"sv": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
|
|
@@ -123,6 +123,18 @@ def parse_date(value, **kwargs):
|
|
|
123
123
|
)
|
|
124
124
|
|
|
125
125
|
|
|
126
|
+
def parse_scheduled_datetime(value: str, **kwargs) -> str:
|
|
127
|
+
result = datetime.datetime.fromisoformat(value)
|
|
128
|
+
current_language = get_request().user.language
|
|
129
|
+
if timezone.now().date().day != result.date().day:
|
|
130
|
+
fmt = "LONG"
|
|
131
|
+
else:
|
|
132
|
+
fmt = "TIME"
|
|
133
|
+
return date_format(
|
|
134
|
+
result, DATETIME_FORMATS.get(current_language, DATETIME_FORMATS.get("en"))[fmt]
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
126
138
|
def parse_message_id(value, **kwargs):
|
|
127
139
|
"""Parse a Message-ID: header."""
|
|
128
140
|
return value.strip("\n")
|
modoboa/webmail/lib/imaputils.py
CHANGED
|
@@ -13,6 +13,7 @@ from modoboa.lib import imap_utf7 # noqa
|
|
|
13
13
|
from modoboa.lib import oauth2
|
|
14
14
|
from modoboa.lib.exceptions import InternalError
|
|
15
15
|
from modoboa.parameters import tools as param_tools
|
|
16
|
+
from modoboa.webmail import constants
|
|
16
17
|
|
|
17
18
|
from ..exceptions import ImapError, WebmailInternalError
|
|
18
19
|
from .fetch_parser import FetchResponseParser
|
|
@@ -292,16 +293,21 @@ class IMAPconnector:
|
|
|
292
293
|
return f"OR {old} {c}"
|
|
293
294
|
|
|
294
295
|
if criterion == "both":
|
|
295
|
-
criterion = "from_addr,
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
296
|
+
criterion = "from_addr,subject"
|
|
297
|
+
|
|
298
|
+
if not pattern:
|
|
299
|
+
criterions = "ALL"
|
|
300
|
+
else:
|
|
301
|
+
criterions = ""
|
|
302
|
+
for c in criterion.split(","):
|
|
303
|
+
if c == "from_addr":
|
|
304
|
+
key = "FROM"
|
|
305
|
+
elif c == "subject":
|
|
306
|
+
key = "SUBJECT"
|
|
307
|
+
else:
|
|
308
|
+
continue
|
|
309
|
+
criterions = or_criterion(criterions, f'({key} "{pattern}")')
|
|
310
|
+
|
|
305
311
|
self.criterions = [bytearray(criterions, "utf8")]
|
|
306
312
|
|
|
307
313
|
def messages_count(self, **kwargs) -> int:
|
|
@@ -485,6 +491,16 @@ class IMAPconnector:
|
|
|
485
491
|
"type": "sent",
|
|
486
492
|
"label": _("Sent"),
|
|
487
493
|
},
|
|
494
|
+
]
|
|
495
|
+
if user.scheduledmessage_set.exists():
|
|
496
|
+
md_mailboxes += [
|
|
497
|
+
{
|
|
498
|
+
"name": constants.MAILBOX_NAME_SCHEDULED,
|
|
499
|
+
"type": "scheduled",
|
|
500
|
+
"label": _("Scheduled"),
|
|
501
|
+
},
|
|
502
|
+
]
|
|
503
|
+
md_mailboxes += [
|
|
488
504
|
{
|
|
489
505
|
"name": user.parameters.get_value("trash_folder"),
|
|
490
506
|
"type": "trash",
|
|
@@ -576,10 +592,30 @@ class IMAPconnector:
|
|
|
576
592
|
self._cmd("COPY", msgset, self._encode_mbox_name(newmailbox))
|
|
577
593
|
self._cmd("STORE", msgset, "+FLAGS", r"(\Deleted \Seen)")
|
|
578
594
|
|
|
579
|
-
def push_mail(self, mbox: str, msg):
|
|
595
|
+
def push_mail(self, mbox: str, msg) -> int:
|
|
596
|
+
"""
|
|
597
|
+
Append a new message to a mailbox.
|
|
598
|
+
|
|
599
|
+
Return the UID of the created message.
|
|
600
|
+
"""
|
|
580
601
|
now = imaplib.Time2Internaldate(time.time())
|
|
581
602
|
msg = bytes(msg)
|
|
582
|
-
|
|
603
|
+
typ, data = self.m.append(self._encode_mbox_name(mbox), r"(\Seen)", now, msg)
|
|
604
|
+
response = data[0].decode()
|
|
605
|
+
m = re.match(r"\[APPENDUID \d+ (\d+)\].+", response)
|
|
606
|
+
if m:
|
|
607
|
+
return int(m.group(1))
|
|
608
|
+
raise WebmailInternalError("Failed to retrieve message UID")
|
|
609
|
+
|
|
610
|
+
def delete_mail(self, mbox: str, uid: int) -> None:
|
|
611
|
+
"""
|
|
612
|
+
Delete given message (set \Deleted flag) and expunge mailbox.
|
|
613
|
+
|
|
614
|
+
Do not use directly.
|
|
615
|
+
"""
|
|
616
|
+
self.select_mailbox(mbox, False)
|
|
617
|
+
self._cmd("STORE", f"{uid}".encode(), "+FLAGS", r"(\Deleted)")
|
|
618
|
+
self._cmd("EXPUNGE")
|
|
583
619
|
|
|
584
620
|
def empty(self, mbox: str):
|
|
585
621
|
self.select_mailbox(mbox, False)
|
|
@@ -605,7 +641,7 @@ class IMAPconnector:
|
|
|
605
641
|
name = f"{parent}{self.hdelimiter}{name}"
|
|
606
642
|
typ, data = self.m.create(self._encode_mbox_name(name))
|
|
607
643
|
if typ == "NO":
|
|
608
|
-
raise WebmailInternalError(data[0])
|
|
644
|
+
raise WebmailInternalError(str(data[0]))
|
|
609
645
|
return True
|
|
610
646
|
|
|
611
647
|
def rename_folder(self, oldname: str, newname: str) -> bool:
|
|
@@ -694,6 +730,8 @@ class IMAPconnector:
|
|
|
694
730
|
submessages = [start]
|
|
695
731
|
mrange = str(start)
|
|
696
732
|
headers = "DATE FROM TO CC SUBJECT"
|
|
733
|
+
if mbox == constants.MAILBOX_NAME_SCHEDULED:
|
|
734
|
+
headers += " X-SCHEDULED-ID X-SCHEDULED-DATETIME"
|
|
697
735
|
query = (
|
|
698
736
|
f"(FLAGS BODYSTRUCTURE RFC822.SIZE BODY.PEEK[HEADER.FIELDS ({headers})])"
|
|
699
737
|
)
|