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/webmail/serializers.py
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"""Webmail serializers."""
|
|
2
2
|
|
|
3
|
+
from django.utils.translation import gettext as _
|
|
4
|
+
from django.utils import timezone
|
|
5
|
+
|
|
3
6
|
from rest_framework import serializers
|
|
4
7
|
|
|
5
8
|
from modoboa.lib import email_utils
|
|
6
|
-
from modoboa.webmail import constants
|
|
9
|
+
from modoboa.webmail import constants, models
|
|
7
10
|
from modoboa.webmail.lib import imapheader, signature
|
|
11
|
+
from modoboa.webmail.lib.imaputils import get_imapconnector
|
|
8
12
|
|
|
9
13
|
|
|
10
14
|
class GlobalParametersSerializer(serializers.Serializer):
|
|
@@ -22,6 +26,13 @@ class GlobalParametersSerializer(serializers.Serializer):
|
|
|
22
26
|
smtp_port = serializers.IntegerField(default=25)
|
|
23
27
|
smtp_authentication = serializers.BooleanField(default=False)
|
|
24
28
|
|
|
29
|
+
scheduling_smtp_server = serializers.CharField(default="127.0.0.1")
|
|
30
|
+
scheduling_smtp_secured_mode = serializers.ChoiceField(
|
|
31
|
+
default=constants.SmtpConnectionMode.NONE.value,
|
|
32
|
+
choices=constants.SMTP_CONNECTION_MODES,
|
|
33
|
+
)
|
|
34
|
+
scheduling_smtp_port = serializers.IntegerField(default=25)
|
|
35
|
+
|
|
25
36
|
|
|
26
37
|
class UserPreferencesSerializer(serializers.Serializer):
|
|
27
38
|
displaymode = serializers.ChoiceField(
|
|
@@ -116,6 +127,7 @@ class EmailHeadersSerializer(serializers.Serializer):
|
|
|
116
127
|
imapid = serializers.CharField()
|
|
117
128
|
subject = serializers.SerializerMethodField()
|
|
118
129
|
from_address = serializers.SerializerMethodField()
|
|
130
|
+
recipients = serializers.SerializerMethodField()
|
|
119
131
|
date = serializers.SerializerMethodField()
|
|
120
132
|
size = serializers.IntegerField()
|
|
121
133
|
answered = serializers.BooleanField(default=False)
|
|
@@ -124,6 +136,14 @@ class EmailHeadersSerializer(serializers.Serializer):
|
|
|
124
136
|
flagged = serializers.BooleanField(default=False)
|
|
125
137
|
style = serializers.CharField(required=False)
|
|
126
138
|
|
|
139
|
+
scheduled_id = serializers.IntegerField(
|
|
140
|
+
source=constants.CUSTOM_HEADER_SCHEDULED_ID, required=False
|
|
141
|
+
)
|
|
142
|
+
scheduled_datetime = serializers.SerializerMethodField(required=False)
|
|
143
|
+
scheduled_datetime_raw = serializers.CharField(
|
|
144
|
+
source=constants.CUSTOM_HEADER_SCHEDULED_DATETIME, required=False
|
|
145
|
+
)
|
|
146
|
+
|
|
127
147
|
def get_subject(self, obj) -> str:
|
|
128
148
|
if "Subject" in obj:
|
|
129
149
|
return imapheader.parse_subject(obj["Subject"])
|
|
@@ -134,11 +154,23 @@ class EmailHeadersSerializer(serializers.Serializer):
|
|
|
134
154
|
return imapheader.parse_address(obj["From"])
|
|
135
155
|
return ""
|
|
136
156
|
|
|
157
|
+
def get_recipients(self, obj):
|
|
158
|
+
if "To" in obj:
|
|
159
|
+
return imapheader.parse_address_list(obj["To"])
|
|
160
|
+
return ""
|
|
161
|
+
|
|
137
162
|
def get_date(self, obj) -> str:
|
|
138
163
|
if "Date" in obj:
|
|
139
164
|
return imapheader.parse_date(obj["Date"])
|
|
140
165
|
return ""
|
|
141
166
|
|
|
167
|
+
def get_scheduled_datetime(self, obj) -> str:
|
|
168
|
+
if constants.CUSTOM_HEADER_SCHEDULED_DATETIME in obj:
|
|
169
|
+
return imapheader.parse_scheduled_datetime(
|
|
170
|
+
obj[constants.CUSTOM_HEADER_SCHEDULED_DATETIME]
|
|
171
|
+
)
|
|
172
|
+
return ""
|
|
173
|
+
|
|
142
174
|
|
|
143
175
|
class PaginatedEmailListSerializer(serializers.Serializer):
|
|
144
176
|
|
|
@@ -162,6 +194,11 @@ class EmailSerializer(serializers.Serializer):
|
|
|
162
194
|
reply_to = serializers.EmailField(source="Reply_To", required=False)
|
|
163
195
|
attachments = serializers.SerializerMethodField()
|
|
164
196
|
|
|
197
|
+
scheduled_datetime = serializers.DateTimeField(
|
|
198
|
+
source=constants.CUSTOM_HEADER_SCHEDULED_DATETIME.replace("-", "_"),
|
|
199
|
+
required=False,
|
|
200
|
+
)
|
|
201
|
+
|
|
165
202
|
def get_attachments(self, email):
|
|
166
203
|
result = []
|
|
167
204
|
if email.attachments:
|
|
@@ -177,6 +214,13 @@ class MoveSelectionSerializer(serializers.Serializer):
|
|
|
177
214
|
destination = serializers.CharField(required=False)
|
|
178
215
|
selection = serializers.ListField(child=serializers.CharField())
|
|
179
216
|
|
|
217
|
+
def validate_source(self, value):
|
|
218
|
+
if value == constants.MAILBOX_NAME_SCHEDULED:
|
|
219
|
+
raise serializers.ValidationError(
|
|
220
|
+
_("Moving scheduled messages is forbidden")
|
|
221
|
+
)
|
|
222
|
+
return value
|
|
223
|
+
|
|
180
224
|
def validate_selection(self, value):
|
|
181
225
|
return [item for item in value if item.isdigit()]
|
|
182
226
|
|
|
@@ -198,7 +242,25 @@ class FlagSelectionSerializer(serializers.Serializer):
|
|
|
198
242
|
return [item for item in value if item.isdigit()]
|
|
199
243
|
|
|
200
244
|
|
|
201
|
-
class
|
|
245
|
+
class ScheduledDatetimeMixin:
|
|
246
|
+
"""Mixin to inject scheduled_datetime logic into a serializer."""
|
|
247
|
+
|
|
248
|
+
scheduled_datetime = serializers.DateTimeField(required=False)
|
|
249
|
+
|
|
250
|
+
def validate_scheduled_datetime(self, value):
|
|
251
|
+
if value <= timezone.now():
|
|
252
|
+
raise serializers.ValidationError(
|
|
253
|
+
_("Only datetime in the future is allowed")
|
|
254
|
+
)
|
|
255
|
+
delta = value - timezone.now()
|
|
256
|
+
if delta.total_seconds() < 60:
|
|
257
|
+
raise serializers.ValidationError(
|
|
258
|
+
_("Provived datetime must be one minute in the future at least")
|
|
259
|
+
)
|
|
260
|
+
return value
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class SendEmailSerializer(ScheduledDatetimeMixin, serializers.Serializer):
|
|
202
264
|
|
|
203
265
|
sender = serializers.EmailField()
|
|
204
266
|
to = serializers.ListField(child=serializers.EmailField())
|
|
@@ -209,8 +271,7 @@ class SendEmailSerializer(serializers.Serializer):
|
|
|
209
271
|
|
|
210
272
|
in_reply_to = serializers.CharField(required=False)
|
|
211
273
|
|
|
212
|
-
|
|
213
|
-
return value
|
|
274
|
+
scheduled_datetime = serializers.DateTimeField(required=False)
|
|
214
275
|
|
|
215
276
|
def validate_to(self, value):
|
|
216
277
|
return email_utils.prepare_addresses(value, "envelope")
|
|
@@ -239,3 +300,22 @@ class ComposeSessionSerializer(serializers.Serializer):
|
|
|
239
300
|
class AllowedSenderSerializer(serializers.Serializer):
|
|
240
301
|
|
|
241
302
|
address = serializers.EmailField()
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class ScheduledMessageSerializer(ScheduledDatetimeMixin, serializers.ModelSerializer):
|
|
306
|
+
|
|
307
|
+
class Meta:
|
|
308
|
+
model = models.ScheduledMessage
|
|
309
|
+
fields = ["error", "scheduled_datetime"]
|
|
310
|
+
read_only_fields = ["error"]
|
|
311
|
+
|
|
312
|
+
def update(self, instance, validated_data):
|
|
313
|
+
instance = super().update(instance, validated_data)
|
|
314
|
+
message = instance.to_email_message()
|
|
315
|
+
with get_imapconnector(self.context["request"]) as imapc:
|
|
316
|
+
imapc.delete_mail(constants.MAILBOX_NAME_SCHEDULED, instance.imap_uid)
|
|
317
|
+
instance.imap_uid = imapc.push_mail(
|
|
318
|
+
constants.MAILBOX_NAME_SCHEDULED, message.message()
|
|
319
|
+
)
|
|
320
|
+
instance.save()
|
|
321
|
+
return instance
|
modoboa/webmail/tests/data.py
CHANGED
|
@@ -110,6 +110,18 @@ BODYSTRUCTURE_SAMPLE_WITH_FLAGS = [
|
|
|
110
110
|
b")",
|
|
111
111
|
]
|
|
112
112
|
|
|
113
|
+
BODYSTRUCTURE_SAMPLE_SCHEDULED = [
|
|
114
|
+
(
|
|
115
|
+
b'19 (UID 19 FLAGS (\\Seen) RFC822.SIZE 100000 BODYSTRUCTURE (("text" "plain" ("charset" "ISO-8859-1" "format" "flowed") NIL NIL "7bit" 2 1 NIL NIL NIL NIL)("message" "rfc822" ("name*" "ISO-8859-1\'\'%5B%49%4E%53%43%52%49%50%54%49%4F%4E%5D%20%52%E9%63%E9%70%74%69%6F%6E%20%64%65%20%76%6F%74%72%65%20%64%6F%73%73%69%65%72%20%64%27%69%6E%73%63%72%69%70%74%69%6F%6E%20%46%72%65%65%20%48%61%75%74%20%44%E9%62%69%74") NIL NIL "8bit" 3632 ("Wed, 13 Dec 2006 20:30:02 +0100" {70}', # noqa
|
|
116
|
+
b"[INSCRIPTION] R\xe9c\xe9ption de votre dossier d'inscription Free Haut D\xe9bit",
|
|
117
|
+
), # noqa
|
|
118
|
+
(
|
|
119
|
+
b' (("Free Haut Debit" NIL "inscription" "freetelecom.fr")) (("Free Haut Debit" NIL "inscription" "freetelecom.fr")) ((NIL NIL "hautdebit" "freetelecom.fr")) ((NIL NIL "nguyen.antoine" "wanadoo.fr")) NIL NIL NIL "<20061213193125.9DA0919AC@dgroup2-2.proxad.net>") ("text" "plain" ("charset" "iso-8859-1") NIL NIL "8bit" 1428 38 NIL ("inline" NIL) NIL NIL) 76 NIL ("inline" ("filename*" "ISO-8859-1\'\'%5B%49%4E%53%43%52%49%50%54%49%4F%4E%5D%20%52%E9%63%E9%70%74%69%6F%6E%20%64%65%20%76%6F%74%72%65%20%64%6F%73%73%69%65%72%20%64%27%69%6E%73%63%72%69%70%74%69%6F%6E%20%46%72%65%65%20%48%61%75%74%20%44%E9%62%69%74")) NIL NIL) "mixed" ("boundary" "------------040706080908000209030901") NIL NIL NIL) BODY[HEADER.FIELDS (DATE FROM TO CC SUBJECT X-SCHEDULED-ID X-SCHEDULED-DATETIME)] {348}', # noqa
|
|
120
|
+
b"Date: Tue, 19 Dec 2006 19:50:13 +0100\r\nFrom: Antoine Nguyen <nguyen.antoine@wanadoo.fr>\r\nTo: Antoine Nguyen <tonio@koalabs.org>\r\nSubject: [Fwd: [INSCRIPTION] =?ISO-8859-1?Q?R=E9c=E9ption_de_votre_?=\r\n =?ISO-8859-1?Q?dossier_d=27inscription_Free_Haut_D=E9bit=5D?=\r\nX-Scheduled-ID: 1123\r\nX-Scheduled-Datetime: 2026-02-02T15:37:59.022298+00:00\r\n\r\n",
|
|
121
|
+
),
|
|
122
|
+
b")",
|
|
123
|
+
]
|
|
124
|
+
|
|
113
125
|
EMPTY_BODYSTRUCTURE = 'BODYSTRUCTURE (("text" "plain" ("charset" "us-ascii") NIL NIL "quoted-printable" 0 0 NIL NIL NIL NIL)("application" "zip" ("name" "hotmail.com!ngyn.org!1435428000!1435514400.zip") NIL NIL "base64" 978 NIL ("attachment" ("filename" "hotmail.com!ngyn.org!1435428000!1435514400.zip")) NIL NIL) "mixed" ("boundary" "--boundary_32281_b52cf564-2a50-4f96-aeb0-ef5f83b05463") NIL NIL NIL)'
|
|
114
126
|
|
|
115
127
|
BODYSTRUCTURE_EMPTY_MAIL = [bytes(f"33 (UID 33 {EMPTY_BODYSTRUCTURE})", "utf-8")]
|
|
@@ -123,6 +135,15 @@ BODYSTRUCTURE_EMPTY_MAIL_WITH_HEADERS = [
|
|
|
123
135
|
b")",
|
|
124
136
|
]
|
|
125
137
|
|
|
138
|
+
BODYSTRUCTURE_EMPTY_MAIL_WITH_HEADERS_SCHEDULED = [
|
|
139
|
+
(
|
|
140
|
+
bytes(f"33 (UID 33 {EMPTY_BODYSTRUCTURE}", "utf-8")
|
|
141
|
+
+ b" BODY[HEADER.FIELDS (FROM TO CC DATE SUBJECT X-SCHEDULED-DATETIME)] {293}",
|
|
142
|
+
b"From: <Service.client10@maaf.fr>\r\nTo: <TONIO@NGYN.ORG>\r\nCc: \r\nSubject: Notre contact du 28/12/2011 - 192175092\r\nDate: Wed, 28 Dec 2011 13:29:17 +0100\r\nMessage-ID: <CABY0dkJspTaFn7v-1OG1nc9M0Qxn+VUTpcXzxyGNBnSnZtqMrw@mail.gmail.com>\r\nX-Scheduled-Datetime: 2026-02-02T15:37:59.022298+00:00\r\n\r\n",
|
|
143
|
+
),
|
|
144
|
+
b")",
|
|
145
|
+
]
|
|
146
|
+
|
|
126
147
|
EMPTY_BODY = [(b"33 (UID 33 BODY[1] {0}", b""), b")"]
|
|
127
148
|
|
|
128
149
|
BODYSTRUCTURE_WITH_PDF = [
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""mailutils tests"""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
from modoboa.webmail.lib.imaputils import IMAPconnector
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ImapUtilsTestCase(unittest.TestCase):
|
|
9
|
+
"""Test mail utils library"""
|
|
10
|
+
|
|
11
|
+
def setUp(self):
|
|
12
|
+
self.imap_connector = IMAPconnector(user="test", password="test")
|
|
13
|
+
|
|
14
|
+
def test_criterions_one_criterion_with_pattern(self):
|
|
15
|
+
"""Test with Criterion and pattern"""
|
|
16
|
+
self.imap_connector.criterions = []
|
|
17
|
+
self.imap_connector.parse_search_parameters("from_addr", "bob")
|
|
18
|
+
result = [bytearray('(FROM "bob")', "utf8")]
|
|
19
|
+
self.assertEqual(self.imap_connector.criterions, result)
|
|
20
|
+
|
|
21
|
+
def test_criterions_both_criterion_with_pattern(self):
|
|
22
|
+
"""Test with both Criterion and pattern"""
|
|
23
|
+
self.imap_connector.criterions = []
|
|
24
|
+
self.imap_connector.parse_search_parameters("both", "bob")
|
|
25
|
+
result = [bytearray('OR (FROM "bob") (SUBJECT "bob")', "utf8")]
|
|
26
|
+
self.assertEqual(self.imap_connector.criterions, result)
|
|
27
|
+
|
|
28
|
+
def test_criterions_one_criterion_without_pattern(self):
|
|
29
|
+
"""Test with Criterion and empty pattern"""
|
|
30
|
+
self.imap_connector.criterions = []
|
|
31
|
+
self.imap_connector.parse_search_parameters("SEEN", "")
|
|
32
|
+
result = [bytearray("ALL", "utf8")]
|
|
33
|
+
self.assertEqual(self.imap_connector.criterions, result)
|
|
@@ -1,25 +1,37 @@
|
|
|
1
1
|
from datetime import timedelta
|
|
2
|
+
import getpass
|
|
2
3
|
from io import BytesIO
|
|
3
4
|
import os
|
|
4
5
|
import shutil
|
|
5
6
|
import tempfile
|
|
6
7
|
from unittest import mock
|
|
7
8
|
|
|
9
|
+
from dateutil.relativedelta import relativedelta
|
|
10
|
+
from freezegun import freeze_time
|
|
11
|
+
from rq import SimpleWorker
|
|
12
|
+
|
|
8
13
|
from django.core import mail
|
|
14
|
+
from django.test import override_settings
|
|
9
15
|
from django.urls import reverse
|
|
10
16
|
from django.utils import timezone
|
|
11
17
|
|
|
18
|
+
import django_rq
|
|
12
19
|
from oauth2_provider.models import get_access_token_model, get_application_model
|
|
13
20
|
|
|
14
21
|
from modoboa.core import models as core_models
|
|
22
|
+
from modoboa.core.tests import utils
|
|
15
23
|
from modoboa.admin import factories as admin_factories
|
|
16
24
|
from modoboa.lib.tests import ModoAPITestCase
|
|
25
|
+
from modoboa.webmail import factories, jobs, models
|
|
17
26
|
from modoboa.webmail.lib.attachments import ComposeSessionManager
|
|
18
27
|
from modoboa.webmail.mocks import IMAP4Mock
|
|
19
28
|
|
|
20
29
|
Application = get_application_model()
|
|
21
30
|
AccessToken = get_access_token_model()
|
|
22
31
|
|
|
32
|
+
DOVEADM_TEST_PATH = utils.get_doveadm_test_path()
|
|
33
|
+
DOVECOT_USER = getpass.getuser()
|
|
34
|
+
|
|
23
35
|
|
|
24
36
|
def get_gif():
|
|
25
37
|
"""Return gif."""
|
|
@@ -243,6 +255,11 @@ class UserEmailViewSetTestCase(WebmailTestCase):
|
|
|
243
255
|
self.assertEqual(response.status_code, 200)
|
|
244
256
|
self.assertTrue(response.json()["subject"].startswith("Fwd:"))
|
|
245
257
|
|
|
258
|
+
# Scheduled message
|
|
259
|
+
response = self.client.get(f"{url}?mailbox=Scheduled&mailid=33")
|
|
260
|
+
self.assertEqual(response.status_code, 200)
|
|
261
|
+
self.assertIn("scheduled_datetime", response.json())
|
|
262
|
+
|
|
246
263
|
def test_attachment(self):
|
|
247
264
|
self.authenticate()
|
|
248
265
|
url = reverse("v2:webmail-email-attachment")
|
|
@@ -368,3 +385,94 @@ class ComposeSessionViewSetTestCase(WebmailTestCase):
|
|
|
368
385
|
self.assertEqual(response.status_code, 204)
|
|
369
386
|
self.assertEqual(len(mail.outbox), 1)
|
|
370
387
|
self.assertEqual(mail.outbox[0].from_email, '"Antoine Nguyen" <user@test.com>')
|
|
388
|
+
|
|
389
|
+
@override_settings(
|
|
390
|
+
DOVEADM_LOOKUP_PATH=[DOVEADM_TEST_PATH], DOVECOT_USER=DOVECOT_USER
|
|
391
|
+
)
|
|
392
|
+
def test_schedule_and_send(self):
|
|
393
|
+
self.authenticate()
|
|
394
|
+
uid = self._create_compose_session()
|
|
395
|
+
# Upload an attachment
|
|
396
|
+
url = reverse("v2:webmail-compose-session-attachments", args=[uid])
|
|
397
|
+
with self.settings(MEDIA_ROOT=self.workdir):
|
|
398
|
+
self.client.post(url, {"attachment": get_gif()})
|
|
399
|
+
# Schedule the message too early
|
|
400
|
+
scheduled_datetime = timezone.now()
|
|
401
|
+
url = reverse("v2:webmail-compose-session-send", args=[uid])
|
|
402
|
+
response = self.client.post(
|
|
403
|
+
url,
|
|
404
|
+
{
|
|
405
|
+
"sender": self.user.email,
|
|
406
|
+
"to": ["test@example.test"],
|
|
407
|
+
"subject": "test",
|
|
408
|
+
"body": "Test",
|
|
409
|
+
"scheduled_datetime": scheduled_datetime.isoformat(),
|
|
410
|
+
},
|
|
411
|
+
format="json",
|
|
412
|
+
)
|
|
413
|
+
self.assertEqual(response.status_code, 400)
|
|
414
|
+
self.assertIn("scheduled_datetime", response.json())
|
|
415
|
+
|
|
416
|
+
scheduled_datetime = timezone.now() + relativedelta(hours=1)
|
|
417
|
+
response = self.client.post(
|
|
418
|
+
url,
|
|
419
|
+
{
|
|
420
|
+
"sender": self.user.email,
|
|
421
|
+
"to": ["test@example.test"],
|
|
422
|
+
"cc": ["cc@example.test"],
|
|
423
|
+
"subject": "test",
|
|
424
|
+
"body": "Test",
|
|
425
|
+
"scheduled_datetime": scheduled_datetime.isoformat(),
|
|
426
|
+
},
|
|
427
|
+
format="json",
|
|
428
|
+
)
|
|
429
|
+
self.assertEqual(response.status_code, 204)
|
|
430
|
+
self.assertEqual(len(mail.outbox), 0)
|
|
431
|
+
self.assertEqual(models.ScheduledMessage.objects.count(), 1)
|
|
432
|
+
self.assertEqual(models.MessageAttachment.objects.count(), 1)
|
|
433
|
+
|
|
434
|
+
with freeze_time(scheduled_datetime + relativedelta(minutes=1)):
|
|
435
|
+
jobs.send_scheduled_messages()
|
|
436
|
+
queue = django_rq.get_queue("modoboa")
|
|
437
|
+
worker = SimpleWorker([queue], connection=queue.connection)
|
|
438
|
+
worker.work(burst=True)
|
|
439
|
+
|
|
440
|
+
self.assertEqual(len(mail.outbox), 1)
|
|
441
|
+
self.assertEqual(models.ScheduledMessage.objects.count(), 0)
|
|
442
|
+
self.assertEqual(models.MessageAttachment.objects.count(), 0)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
@override_settings(DOVEADM_LOOKUP_PATH=[DOVEADM_TEST_PATH], DOVECOT_USER=DOVECOT_USER)
|
|
446
|
+
class ScheduledMessageViewSetTestCase(WebmailTestCase):
|
|
447
|
+
|
|
448
|
+
def setUp(self):
|
|
449
|
+
super().setUp()
|
|
450
|
+
self.message = factories.ScheduledMessageFactory(
|
|
451
|
+
account=self.user, scheduled_datetime=timezone.now()
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
def test_listmailbox(self):
|
|
455
|
+
self.authenticate()
|
|
456
|
+
url = reverse("v2:webmail-email-list")
|
|
457
|
+
response = self.client.get(f"{url}?mailbox=Scheduled")
|
|
458
|
+
self.assertEqual(response.status_code, 200)
|
|
459
|
+
content = response.json()
|
|
460
|
+
self.assertEqual(content["count"], 1)
|
|
461
|
+
self.assertEqual(content["results"][0]["scheduled_id"], 1123)
|
|
462
|
+
self.assertIn("scheduled_datetime_raw", content["results"][0])
|
|
463
|
+
|
|
464
|
+
def test_delete(self):
|
|
465
|
+
self.authenticate()
|
|
466
|
+
url = reverse("v2:webmail-scheduled-message-detail", args=[self.message.id])
|
|
467
|
+
response = self.client.delete(url)
|
|
468
|
+
self.assertEqual(response.status_code, 204)
|
|
469
|
+
self.assertEqual(models.ScheduledMessage.objects.count(), 0)
|
|
470
|
+
|
|
471
|
+
def test_update(self):
|
|
472
|
+
self.authenticate()
|
|
473
|
+
url = reverse("v2:webmail-scheduled-message-detail", args=[self.message.id])
|
|
474
|
+
data = {"scheduled_datetime": timezone.now() + relativedelta(days=1)}
|
|
475
|
+
response = self.client.patch(url, data, format="json")
|
|
476
|
+
self.assertEqual(response.status_code, 200)
|
|
477
|
+
self.message.refresh_from_db()
|
|
478
|
+
self.assertEqual(self.message.imap_uid, 11)
|
modoboa/webmail/urls.py
CHANGED
modoboa/webmail/viewsets.py
CHANGED
|
@@ -5,15 +5,16 @@ from django.core.validators import validate_email
|
|
|
5
5
|
from django.http import Http404, HttpResponse
|
|
6
6
|
from django.utils.translation import gettext as _
|
|
7
7
|
|
|
8
|
-
from rest_framework import parsers, response, viewsets
|
|
8
|
+
from rest_framework import mixins, parsers, response, viewsets
|
|
9
9
|
from rest_framework.decorators import action
|
|
10
10
|
from rest_framework.permissions import IsAuthenticated
|
|
11
11
|
|
|
12
12
|
from modoboa.lib import exceptions
|
|
13
13
|
from modoboa.lib.paginator import Paginator
|
|
14
14
|
from modoboa.lib.viewsets import HasMailbox
|
|
15
|
-
from modoboa.webmail import lib, serializers
|
|
15
|
+
from modoboa.webmail import lib, models, serializers
|
|
16
16
|
from modoboa.webmail.lib import attachments
|
|
17
|
+
from modoboa.webmail.lib.sendmail import send_mail, schedule_email
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class UserMailboxViewSet(viewsets.GenericViewSet):
|
|
@@ -156,11 +157,12 @@ class UserEmailViewSet(viewsets.GenericViewSet):
|
|
|
156
157
|
|
|
157
158
|
content = imapc.fetch(page.id_start, page.id_stop, mbox=mailbox)
|
|
158
159
|
content = [dict(msg) for msg in content]
|
|
160
|
+
first_index = (page_num - 1) * messages_per_page + 1
|
|
159
161
|
serializer = self.get_serializer(
|
|
160
162
|
{
|
|
161
163
|
"count": total,
|
|
162
|
-
"first_index":
|
|
163
|
-
"last_index":
|
|
164
|
+
"first_index": first_index,
|
|
165
|
+
"last_index": first_index + len(content) - 1,
|
|
164
166
|
"prev_page": page.previous_page_number if page.has_previous else None,
|
|
165
167
|
"next_page": page.next_page_number if page.has_next else None,
|
|
166
168
|
"results": content,
|
|
@@ -376,10 +378,38 @@ class ComposeSessionViewSet(viewsets.GenericViewSet):
|
|
|
376
378
|
serializer = self.get_serializer(data=request.data)
|
|
377
379
|
serializer.is_valid(raise_exception=True)
|
|
378
380
|
manager = attachments.ComposeSessionManager(request.user.username)
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
381
|
+
if serializer.validated_data.get("scheduled_datetime"):
|
|
382
|
+
schedule_email(
|
|
383
|
+
request,
|
|
384
|
+
serializer.validated_data,
|
|
385
|
+
manager.get_content(pk)["attachments"],
|
|
386
|
+
)
|
|
384
387
|
return response.Response(status=204)
|
|
388
|
+
else:
|
|
389
|
+
status, error = send_mail(
|
|
390
|
+
request,
|
|
391
|
+
serializer.validated_data,
|
|
392
|
+
manager.get_content(pk)["attachments"],
|
|
393
|
+
)
|
|
394
|
+
if status:
|
|
395
|
+
attachments.remove_attachments_and_session(manager, pk)
|
|
396
|
+
return response.Response(status=204)
|
|
385
397
|
return response.Response({"error": error}, status=400)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
class ScheduledMessageViewSet(
|
|
401
|
+
mixins.RetrieveModelMixin,
|
|
402
|
+
mixins.UpdateModelMixin,
|
|
403
|
+
mixins.DestroyModelMixin,
|
|
404
|
+
viewsets.GenericViewSet,
|
|
405
|
+
):
|
|
406
|
+
|
|
407
|
+
permission_classes = (IsAuthenticated, HasMailbox)
|
|
408
|
+
serializer_class = serializers.ScheduledMessageSerializer
|
|
409
|
+
|
|
410
|
+
def get_queryset(self):
|
|
411
|
+
return models.ScheduledMessage.objects.filter(account=self.request.user)
|
|
412
|
+
|
|
413
|
+
def perform_destroy(self, instance):
|
|
414
|
+
if instance.delete_imap_copy():
|
|
415
|
+
instance.delete()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modoboa
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7.0
|
|
4
4
|
Summary: Mail hosting made simple
|
|
5
5
|
Author-email: Antoine Nguyen <tonio@ngyn.org>
|
|
6
6
|
License: ISC License
|
|
@@ -40,11 +40,11 @@ Classifier: Topic :: Internet :: WWW/HTTP
|
|
|
40
40
|
Requires-Python: >=3.10
|
|
41
41
|
Description-Content-Type: text/x-rst
|
|
42
42
|
License-File: LICENSE
|
|
43
|
-
Requires-Dist: django==5.2.
|
|
43
|
+
Requires-Dist: django==5.2.11
|
|
44
44
|
Requires-Dist: django-phonenumber-field[phonenumbers]==8.4.0
|
|
45
|
-
Requires-Dist: django-reversion==6.
|
|
45
|
+
Requires-Dist: django-reversion==6.1.0
|
|
46
46
|
Requires-Dist: django-xforwardedfor-middleware==2.0
|
|
47
|
-
Requires-Dist: django-otp==1.
|
|
47
|
+
Requires-Dist: django-otp==1.7.0
|
|
48
48
|
Requires-Dist: django-filter
|
|
49
49
|
Requires-Dist: django-rename-app==0.1.7
|
|
50
50
|
Requires-Dist: django-environ
|
|
@@ -54,13 +54,12 @@ Requires-Dist: drf-spectacular
|
|
|
54
54
|
Requires-Dist: uritemplate
|
|
55
55
|
Requires-Dist: django-oauth-toolkit
|
|
56
56
|
Requires-Dist: django-cors-headers
|
|
57
|
-
Requires-Dist:
|
|
58
|
-
Requires-Dist: bcrypt
|
|
57
|
+
Requires-Dist: libpass>=1.9.3
|
|
58
|
+
Requires-Dist: bcrypt~=5.0
|
|
59
59
|
Requires-Dist: asgiref
|
|
60
60
|
Requires-Dist: dnspython==2.8.0
|
|
61
61
|
Requires-Dist: feedparser==6.0.12
|
|
62
|
-
Requires-Dist: fido2==2.
|
|
63
|
-
Requires-Dist: gevent==25.9.1
|
|
62
|
+
Requires-Dist: fido2==2.1.1
|
|
64
63
|
Requires-Dist: progressbar33==2.4
|
|
65
64
|
Requires-Dist: python-dateutil
|
|
66
65
|
Requires-Dist: cryptography
|
|
@@ -71,7 +70,7 @@ Requires-Dist: chardet
|
|
|
71
70
|
Requires-Dist: ovh
|
|
72
71
|
Requires-Dist: oath
|
|
73
72
|
Requires-Dist: packaging
|
|
74
|
-
Requires-Dist: redis>=
|
|
73
|
+
Requires-Dist: redis>=7.1.0
|
|
75
74
|
Requires-Dist: rrdtool-bindings>=0.2.0
|
|
76
75
|
Requires-Dist: qrcode
|
|
77
76
|
Requires-Dist: aiosmtplib
|
|
@@ -79,12 +78,12 @@ Requires-Dist: reportlab
|
|
|
79
78
|
Requires-Dist: tldextract>=2.0.2
|
|
80
79
|
Requires-Dist: defusedxml>=0.6.0
|
|
81
80
|
Requires-Dist: python-magic>=0.4.24
|
|
82
|
-
Requires-Dist: rq>=
|
|
81
|
+
Requires-Dist: rq>=2.4.0
|
|
83
82
|
Requires-Dist: django-rq
|
|
84
83
|
Requires-Dist: sievelib>=1.4.1
|
|
85
84
|
Requires-Dist: drf-nested-routers
|
|
86
85
|
Requires-Dist: vobject
|
|
87
|
-
Requires-Dist: caldav==2.
|
|
86
|
+
Requires-Dist: caldav==2.2.6
|
|
88
87
|
Requires-Dist: html2text
|
|
89
88
|
Requires-Dist: idna
|
|
90
89
|
Provides-Extra: dev
|
|
@@ -98,12 +97,14 @@ Requires-Dist: pyOpenSSL; extra == "dev"
|
|
|
98
97
|
Provides-Extra: mysql
|
|
99
98
|
Requires-Dist: mysqlclient<2.2.8; extra == "mysql"
|
|
100
99
|
Provides-Extra: postgresql
|
|
101
|
-
Requires-Dist: psycopg[binary]>=3.1; extra == "postgresql"
|
|
100
|
+
Requires-Dist: psycopg[binary]>=3.1; platform_system != "FreeBSD" and extra == "postgresql"
|
|
101
|
+
Requires-Dist: psycopg[c]>=3.1; platform_system == "FreeBSD" and extra == "postgresql"
|
|
102
102
|
Provides-Extra: test
|
|
103
103
|
Requires-Dist: argon2-cffi>=16.1.0; extra == "test"
|
|
104
104
|
Requires-Dist: factory-boy<3.4.0; extra == "test"
|
|
105
105
|
Requires-Dist: httmock==1.4.0; extra == "test"
|
|
106
106
|
Requires-Dist: testfixtures==8.3.0; extra == "test"
|
|
107
|
+
Requires-Dist: freezegun; extra == "test"
|
|
107
108
|
Requires-Dist: tox; extra == "test"
|
|
108
109
|
Provides-Extra: ldap
|
|
109
110
|
Requires-Dist: django-auth-ldap<6.0.0; extra == "ldap"
|
|
@@ -113,7 +114,7 @@ Dynamic: license-file
|
|
|
113
114
|
Modoboa (`website <https://modoboa.org/>`_)
|
|
114
115
|
############################################
|
|
115
116
|
|
|
116
|
-
|workflow| |codecov| |latest-version|
|
|
117
|
+
|workflow| |codecov| |latest-version| |deepwiki|
|
|
117
118
|
|
|
118
119
|
`Modoboa <https://modoboa.org>`_ is a mail hosting and management platform including a modern
|
|
119
120
|
and simplified Web User Interface. It provides useful components such
|
|
@@ -190,3 +191,5 @@ If you have any questions, you can get help through the following platforms:
|
|
|
190
191
|
.. |workflow| image:: https://github.com/modoboa/modoboa/actions/workflows/modoboa.yml/badge.svg
|
|
191
192
|
.. |codecov| image:: https://codecov.io/gh/modoboa/modoboa/graph/badge.svg?token=1E5eBxJO33
|
|
192
193
|
:target: https://codecov.io/gh/modoboa/modoboa
|
|
194
|
+
.. |deepwiki| image:: https://deepwiki.com/badge.svg
|
|
195
|
+
:target: https://deepwiki.com/modoboa/modoboa
|