modoboa 2.6.4__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/autoreply/models.py +4 -4
- 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-tLHotdIR.js → AccountAliasForm-DO6DwfjE.js} +1 -1
- modoboa/frontend_dist/assets/{AccountEditView-DnQIIg47.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-n75NEN_s.js → AliasEditView-CTjrXYPf.js} +1 -1
- modoboa/frontend_dist/assets/AliasRecipientForm-CI7bXqVp.js +1 -0
- modoboa/frontend_dist/assets/{AliasView-DFIvPeIm.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-BjtfbhRj.js → DomainAdminList-DD7p3i6F.js} +1 -1
- modoboa/frontend_dist/assets/{DomainEditView-Cq5Ea4RW.js → DomainEditView-DoIoNYC4.js} +1 -1
- modoboa/frontend_dist/assets/{DomainTransportForm-DAXJGNLA.js → DomainTransportForm-CbiJF9z5.js} +1 -1
- modoboa/frontend_dist/assets/{DomainView-BfpK7U14.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-D753fIAJ.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-Tm56XNwj.js → LoadingData-C2txD49L.js} +1 -1
- modoboa/frontend_dist/assets/{LoginCallbackView-DjCHUgz5.js → LoginCallbackView-DAvty3CI.js} +1 -1
- modoboa/frontend_dist/assets/{LoginView-D0r4XBEl.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-BWh9BqzS.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-y02-GM0r.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-DO-IpJMQ.js +1 -0
- 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-CNys_LUS.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-BoH6kjPa.js → VCard-CBtX8JF-.js} +1 -1
- modoboa/frontend_dist/assets/VCheckbox-zY1MApOy.js +1 -0
- modoboa/frontend_dist/assets/{VCheckboxBtn-Ccl786Rg.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-DKMcKasC.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-CuVZGqBj.js → VRow-83Qnr5iB.js} +1 -1
- modoboa/frontend_dist/assets/VSelect-BcmGFGif.js +1 -0
- modoboa/frontend_dist/assets/{VSelectionControl-uKY9Zull.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-gDsAUSLX.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-DqbBlaLK.js → accounts-KUsk6LHW.js} +1 -1
- modoboa/frontend_dist/assets/{admin-D36m_nXq.js → admin-BW9cZW0P.js} +1 -1
- modoboa/frontend_dist/assets/{aliases-BtyF8GC8.js → aliases-Ge0hjIsH.js} +1 -1
- modoboa/frontend_dist/assets/{amavis-DySmsCJQ.js → amavis-BbFeFfsk.js} +1 -1
- modoboa/frontend_dist/assets/{amavis-B4QhM5Fa.js → amavis-DtuzP_CS.js} +1 -1
- modoboa/frontend_dist/assets/{contacts-Df8ptuMn.js → contacts-DMJlQTe0.js} +1 -1
- modoboa/frontend_dist/assets/{domains-CSxtTvUh.js → domains-Du64lcXT.js} +1 -1
- modoboa/frontend_dist/assets/domains.store-1U61jeCV.js +1 -0
- 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-Bp9Fb67E.js → index-Dv00bmw9.js} +1 -1
- modoboa/frontend_dist/assets/index-jui3edpn.js +1001 -0
- modoboa/frontend_dist/assets/{language.store-_-zrrEiS.js → language.store-OcfdXL_-.js} +1 -1
- modoboa/frontend_dist/assets/languages-CF8hxo7x.js +1 -0
- modoboa/frontend_dist/assets/{layout-CjSP_k58.js → layout-DOO7TRTJ.js} +1 -1
- modoboa/frontend_dist/assets/{layout.store-CF5WRevH.js → layout.store-C0g-piJn.js} +1 -1
- modoboa/frontend_dist/assets/{logos-95XYS6uH.js → logos-Dz2Gzei-.js} +1 -1
- modoboa/frontend_dist/assets/{logs-CyU_PIDh.js → logs-CLm32Weu.js} +1 -1
- modoboa/frontend_dist/assets/{parameters-BxAXBXU1.js → parameters-DMIAQ7cd.js} +1 -1
- modoboa/frontend_dist/assets/{parameters.store-CATOSoQZ.js → parameters.store-DLnFzCwV.js} +1 -1
- modoboa/frontend_dist/assets/{permissions-cSkGqV3M.js → permissions-CrpE0b4w.js} +1 -1
- modoboa/frontend_dist/assets/{ssrBoot-05WtbG6H.js → ssrBoot-B7cr7q9U.js} +1 -1
- modoboa/frontend_dist/assets/{tag-BmV84V2s.js → tag-WF93n81Q.js} +1 -1
- modoboa/frontend_dist/assets/{theme-myV4ekXo.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/api/v2/serializers.py +13 -0
- modoboa/imap_migration/models.py +3 -3
- modoboa/imap_migration/templates/imap_migration/offlineimap.conf +2 -2
- modoboa/imap_migration/tests.py +28 -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 +753 -317
- modoboa/locale/cs/LC_MESSAGES/django.po +671 -309
- modoboa/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/cs_CZ/LC_MESSAGES/django.po +801 -378
- modoboa/locale/de/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/de/LC_MESSAGES/django.po +1116 -455
- modoboa/locale/de_DE/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/de_DE/LC_MESSAGES/django.po +745 -318
- modoboa/locale/el_GR/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/el_GR/LC_MESSAGES/django.po +825 -393
- modoboa/locale/en/LC_MESSAGES/django.po +669 -309
- modoboa/locale/es/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/es/LC_MESSAGES/django.po +810 -390
- modoboa/locale/es_MX/LC_MESSAGES/django.po +669 -309
- modoboa/locale/fi/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/fi/LC_MESSAGES/django.po +756 -316
- modoboa/locale/fr/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/fr/LC_MESSAGES/django.po +674 -313
- modoboa/locale/hu/LC_MESSAGES/django.po +669 -309
- modoboa/locale/it/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/it/LC_MESSAGES/django.po +807 -380
- modoboa/locale/ja_JP/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/ja_JP/LC_MESSAGES/django.po +951 -419
- modoboa/locale/ka/LC_MESSAGES/django.po +669 -309
- modoboa/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/nl_NL/LC_MESSAGES/django.po +807 -387
- modoboa/locale/no/LC_MESSAGES/django.po +669 -309
- modoboa/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/pl_PL/LC_MESSAGES/django.po +826 -396
- modoboa/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/pt_BR/LC_MESSAGES/django.po +830 -392
- modoboa/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/pt_PT/LC_MESSAGES/django.po +788 -371
- modoboa/locale/ro_RO/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/ro_RO/LC_MESSAGES/django.po +763 -316
- modoboa/locale/ru/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/ru/LC_MESSAGES/django.po +1336 -1046
- modoboa/locale/si/LC_MESSAGES/django.po +669 -309
- modoboa/locale/sk/LC_MESSAGES/django.po +671 -309
- modoboa/locale/sk_SK/LC_MESSAGES/django.po +671 -309
- modoboa/locale/sl_SI/LC_MESSAGES/django.po +695 -315
- modoboa/locale/sv/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/sv/LC_MESSAGES/django.po +819 -387
- modoboa/locale/tr/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/tr/LC_MESSAGES/django.po +737 -315
- modoboa/locale/tr_TR/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/tr_TR/LC_MESSAGES/django.po +711 -310
- modoboa/locale/uk/LC_MESSAGES/django.po +671 -309
- modoboa/locale/zh/LC_MESSAGES/django.po +668 -309
- modoboa/locale/zh_CN/LC_MESSAGES/django.po +668 -309
- modoboa/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
- modoboa/locale/zh_TW/LC_MESSAGES/django.po +747 -316
- 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.4.dist-info → modoboa-2.7.0.dist-info}/METADATA +16 -13
- {modoboa-2.6.4.dist-info → modoboa-2.7.0.dist-info}/RECORD +271 -254
- {modoboa-2.6.4.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-B2HptJzY.js +0 -1
- modoboa/frontend_dist/assets/AccountPasswordSubForm-DpADntAL.js +0 -1
- modoboa/frontend_dist/assets/AccountView-CJBGh9Oe.js +0 -1
- modoboa/frontend_dist/assets/AddressBook-CrFqzlzZ.js +0 -1
- modoboa/frontend_dist/assets/AdminLayout-BbCt7YxX.js +0 -1
- modoboa/frontend_dist/assets/AlarmsView-CIlt_IKQ.js +0 -1
- modoboa/frontend_dist/assets/AliasRecipientForm-jjj5yJJw.js +0 -1
- modoboa/frontend_dist/assets/AuditTrailView-sWnED3E9.js +0 -1
- modoboa/frontend_dist/assets/CalendarView-DSCBIvef.js +0 -1
- modoboa/frontend_dist/assets/ChoiceField-RDT6XcnX.js +0 -1
- modoboa/frontend_dist/assets/ComposeEmailForm-D3yUbBJX.css +0 -1
- modoboa/frontend_dist/assets/ComposeEmailForm-G3sXQAwI.js +0 -1
- modoboa/frontend_dist/assets/ComposeEmailView-Ce1CtFjD.js +0 -1
- modoboa/frontend_dist/assets/ConfirmDialog-Du7aqKbR.js +0 -1
- modoboa/frontend_dist/assets/ConnectedLayout-BHWaBAzH.js +0 -1
- modoboa/frontend_dist/assets/CreationForm-CjtILq0_.js +0 -1
- modoboa/frontend_dist/assets/DashboardView-DFNwusDZ.js +0 -1
- modoboa/frontend_dist/assets/DomainsView-CYxLy-3Y.js +0 -1
- modoboa/frontend_dist/assets/EmailField-DmPc4pyP.js +0 -1
- modoboa/frontend_dist/assets/EmailView-BE3vYZtJ.js +0 -1
- modoboa/frontend_dist/assets/EmptyLayout-DMwKNHfI.js +0 -1
- modoboa/frontend_dist/assets/FiltersView-Dps3_fAh.js +0 -1
- modoboa/frontend_dist/assets/ForwardEmailView-C0I00eXf.js +0 -1
- modoboa/frontend_dist/assets/IdentitiesView-CoGBVhZx.js +0 -1
- modoboa/frontend_dist/assets/InformationView-eB2ayeLD.js +0 -1
- modoboa/frontend_dist/assets/MailboxView-DIVABvk5.css +0 -1
- modoboa/frontend_dist/assets/MailboxView-DNokO2WZ.js +0 -5
- modoboa/frontend_dist/assets/MenuItems-dl38msRA.js +0 -1
- modoboa/frontend_dist/assets/MessagesView-BZ7NUOwo.js +0 -1
- modoboa/frontend_dist/assets/MigrationsView-DcOVgmcH.js +0 -1
- modoboa/frontend_dist/assets/ParametersView-B6abt-LH.js +0 -1
- modoboa/frontend_dist/assets/ParametersView-v0YS6WU4.js +0 -1
- modoboa/frontend_dist/assets/ProviderEditView-BaL9mKxJ.js +0 -1
- modoboa/frontend_dist/assets/ProviderGeneralForm-eoHMDO5v.js +0 -1
- modoboa/frontend_dist/assets/ProvidersView-Zu-G35u7.js +0 -1
- modoboa/frontend_dist/assets/QuarantineLayout-BHiiksnP.js +0 -1
- modoboa/frontend_dist/assets/QuarantineView-BOXSobRz.js +0 -1
- modoboa/frontend_dist/assets/ReplyEmailView-JmSiNWPy.js +0 -1
- modoboa/frontend_dist/assets/ResourcesForm-D0YBapCG.js +0 -1
- modoboa/frontend_dist/assets/SelfServiceLayout-DUmQzUDk.js +0 -1
- modoboa/frontend_dist/assets/SettingsView-DIvKGRBY.css +0 -1
- modoboa/frontend_dist/assets/SettingsView-DMuaKagM.js +0 -6
- modoboa/frontend_dist/assets/StatisticsView-CyY_CNcI.js +0 -1
- modoboa/frontend_dist/assets/TimeSerieChart-BTDBH33K.js +0 -1
- modoboa/frontend_dist/assets/TimeSerieChart-C3XHmlRd.css +0 -1
- modoboa/frontend_dist/assets/UserLayout-DwxcQr4L.js +0 -1
- modoboa/frontend_dist/assets/VApp-B0vRc3-6.js +0 -1
- modoboa/frontend_dist/assets/VAutocomplete-Huaz_-hf.js +0 -1
- modoboa/frontend_dist/assets/VAutocomplete-hzGuLlUI.css +0 -1
- modoboa/frontend_dist/assets/VAvatar-JdJfoK0C.js +0 -1
- modoboa/frontend_dist/assets/VBadge-Cg5iu5LG.js +0 -1
- modoboa/frontend_dist/assets/VCheckbox-CCLOJFKF.js +0 -1
- modoboa/frontend_dist/assets/VColorPicker-452uc3tY.js +0 -1
- modoboa/frontend_dist/assets/VDataTable-BkLLfcQK.js +0 -1
- modoboa/frontend_dist/assets/VDataTableServer-B_6fWBA3.js +0 -1
- modoboa/frontend_dist/assets/VDataTableVirtual-Ike7oqcC.js +0 -1
- modoboa/frontend_dist/assets/VDialog-DIUeAIpw.js +0 -1
- modoboa/frontend_dist/assets/VExpansionPanels-BeMgiS7A.js +0 -1
- modoboa/frontend_dist/assets/VFileInput-Dvj95FL5.js +0 -1
- modoboa/frontend_dist/assets/VForm-DkphQD3e.js +0 -1
- modoboa/frontend_dist/assets/VInput-CbqehdOu.js +0 -1
- modoboa/frontend_dist/assets/VMenu-B6SCtOwT.js +0 -1
- modoboa/frontend_dist/assets/VMenu-BEipA1lw.css +0 -1
- modoboa/frontend_dist/assets/VPicker-BUx70Wjg.js +0 -1
- modoboa/frontend_dist/assets/VPicker-ClSXs6kv.css +0 -1
- modoboa/frontend_dist/assets/VProgressCircular-DTDGzl2O.js +0 -1
- modoboa/frontend_dist/assets/VRadioGroup-CS9ULZ1c.js +0 -1
- modoboa/frontend_dist/assets/VSelect-D5IXDPEX.js +0 -1
- modoboa/frontend_dist/assets/VSheet-HeHw6g5_.js +0 -1
- modoboa/frontend_dist/assets/VSpacer-BUxSSJbH.js +0 -1
- modoboa/frontend_dist/assets/VSwitch-Dh_dserW.js +0 -1
- modoboa/frontend_dist/assets/VTabs-5XSICLQP.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-QXQFhKxm.js +0 -1
- modoboa/frontend_dist/assets/VTextarea-Bdy4UKmV.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-CyrYSqJZ.js +0 -1
- modoboa/frontend_dist/assets/VWindowItem-kZpArwK1.js +0 -1
- modoboa/frontend_dist/assets/WebmailLayout-BzW0LWYp.css +0 -1
- modoboa/frontend_dist/assets/WebmailLayout-CNUHODnw.js +0 -1
- modoboa/frontend_dist/assets/domains.store-D4ZqCQ-O.js +0 -1
- modoboa/frontend_dist/assets/filter-DHKXX97M.js +0 -1
- modoboa/frontend_dist/assets/global.store-DYMmGKpn.js +0 -1
- modoboa/frontend_dist/assets/importExport-C-adpHJX.js +0 -1
- modoboa/frontend_dist/assets/index-B1bntsLR.js +0 -995
- modoboa/frontend_dist/assets/languages-CxjoT69j.js +0 -1
- modoboa/frontend_dist/assets/transports-ChdHV5hX.js +0 -1
- modoboa/frontend_dist/assets/webmail-B7MNMkv_.js +0 -1
- modoboa/frontend_dist/assets/webmail.store-Cm-h6hhE.js +0 -1
- {modoboa-2.6.4.data → modoboa-2.7.0.data}/scripts/modoboa-admin.py +0 -0
- {modoboa-2.6.4.dist-info → modoboa-2.7.0.dist-info}/entry_points.txt +0 -0
- {modoboa-2.6.4.dist-info → modoboa-2.7.0.dist-info}/licenses/LICENSE +0 -0
- {modoboa-2.6.4.dist-info → modoboa-2.7.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Tests cases for RQ jobs."""
|
|
2
|
+
|
|
3
|
+
from modoboa.lib.tests import ModoTestCase
|
|
4
|
+
|
|
5
|
+
from modoboa.amavis import factories, jobs, models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class JobsTestCase(ModoTestCase):
|
|
9
|
+
|
|
10
|
+
databases = "__all__"
|
|
11
|
+
|
|
12
|
+
def test_qcleanup(self):
|
|
13
|
+
factories.create_spam("user@test.com", rs="D")
|
|
14
|
+
jobs.qcleanup()
|
|
15
|
+
self.assertEqual(models.Quarantine.objects.count(), 0)
|
|
16
|
+
self.assertEqual(models.Msgs.objects.count(), 0)
|
|
17
|
+
self.assertEqual(models.Maddr.objects.count(), 0)
|
|
18
|
+
self.assertEqual(models.Msgrcpt.objects.count(), 0)
|
|
@@ -4,14 +4,13 @@ from unittest import mock
|
|
|
4
4
|
from rq import SimpleWorker
|
|
5
5
|
|
|
6
6
|
from django.core import mail
|
|
7
|
-
from django.core.management import call_command
|
|
8
7
|
from django.test import override_settings
|
|
9
8
|
from django.urls import reverse
|
|
10
9
|
|
|
11
10
|
import django_rq
|
|
12
11
|
|
|
13
12
|
from modoboa.admin import factories as admin_factories, models as admin_models
|
|
14
|
-
from modoboa.amavis import factories, models
|
|
13
|
+
from modoboa.amavis import factories, jobs, models
|
|
15
14
|
from modoboa.amavis.utils import smart_str
|
|
16
15
|
from modoboa.core import models as core_models
|
|
17
16
|
from modoboa.lib.tests import ModoAPITestCase
|
|
@@ -204,7 +203,7 @@ class QuarantineViewSetTestCase(TestDataMixin, ModoAPITestCase):
|
|
|
204
203
|
self.assertEqual(self.msgrcpt.rs, "p")
|
|
205
204
|
|
|
206
205
|
# Send notification to admins
|
|
207
|
-
|
|
206
|
+
jobs.amnotify()
|
|
208
207
|
self.assertEqual(len(mail.outbox), 1)
|
|
209
208
|
|
|
210
209
|
def _test_mark_selection(self, action, status):
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<hostname>{{ connection_settings.imap.HOSTNAME }}</hostname>
|
|
10
10
|
<port>{{ connection_settings.imap.PORT }}</port>
|
|
11
11
|
<socketType>{{ connection_settings.imap.SOCKET_TYPE }}</socketType>
|
|
12
|
-
<authentication>
|
|
12
|
+
<authentication>password-cleartext</authentication>
|
|
13
13
|
<username>{{ emailaddress }}</username>
|
|
14
14
|
</incomingServer>
|
|
15
15
|
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<hostname>{{ connection_settings.smtp.HOSTNAME }}</hostname>
|
|
18
18
|
<port>{{ connection_settings.smtp.PORT }}</port>
|
|
19
19
|
<socketType>{{ connection_settings.smtp.SOCKET_TYPE }}</socketType>
|
|
20
|
-
<authentication>
|
|
20
|
+
<authentication>password-cleartext</authentication>
|
|
21
21
|
<username>{{ emailaddress }}</username>
|
|
22
22
|
</outgoingServer>
|
|
23
23
|
</emailProvider>
|
|
@@ -9,7 +9,16 @@
|
|
|
9
9
|
<Type>IMAP</Type>
|
|
10
10
|
<Server>{{ connection_settings.imap.HOSTNAME }}</Server>
|
|
11
11
|
<Port>{{ connection_settings.imap.PORT }}</Port>
|
|
12
|
+
{% if connection_settings.imap.SOCKET_TYPE|upper == "SSL" %}
|
|
12
13
|
<SSL>true</SSL>
|
|
14
|
+
<TLS>false</TLS>
|
|
15
|
+
{% elif connection_settings.imap.SOCKET_TYPE|upper == "STARTTLS" %}
|
|
16
|
+
<SSL>false</SSL>
|
|
17
|
+
<TLS>true</TLS>
|
|
18
|
+
{% elif connection_settings.imap.SOCKET_TYPE|lower == "plain" %}
|
|
19
|
+
<SSL>false</SSL>
|
|
20
|
+
<TLS>false</TLS>
|
|
21
|
+
{% endif %}
|
|
13
22
|
<LoginName>{{ emailaddress }}</LoginName>
|
|
14
23
|
</Protocol>
|
|
15
24
|
|
|
@@ -17,8 +26,13 @@
|
|
|
17
26
|
<Type>SMTP</Type>
|
|
18
27
|
<Server>{{ connection_settings.smtp.HOSTNAME }}</Server>
|
|
19
28
|
<Port>{{ connection_settings.smtp.PORT }}</Port>
|
|
29
|
+
{% if connection_settings.smtp.SOCKET_TYPE|upper == "SSL" %}
|
|
30
|
+
<SSL>true</SSL>
|
|
31
|
+
<TLS>false</TLS>
|
|
32
|
+
{% elif connection_settings.smtp.SOCKET_TYPE|upper == "STARTTLS" %}
|
|
20
33
|
<SSL>false</SSL>
|
|
21
34
|
<TLS>true</TLS>
|
|
35
|
+
{% endif %}
|
|
22
36
|
<LoginName>{{ emailaddress }}</LoginName>
|
|
23
37
|
</Protocol>
|
|
24
38
|
|
modoboa/autoconfig/tests.py
CHANGED
|
@@ -10,7 +10,8 @@ class ViewsTestCase(TestCase):
|
|
|
10
10
|
url = reverse("autoconfig:autoconfig")
|
|
11
11
|
|
|
12
12
|
resp = self.client.get(url)
|
|
13
|
-
self.assertEqual(resp.status_code,
|
|
13
|
+
self.assertEqual(resp.status_code, 200)
|
|
14
|
+
self.assertIn(b"%EMAILLOCALPART%@%EMAILDOMAIN%", resp.content)
|
|
14
15
|
|
|
15
16
|
resp = self.client.get(f"{url}?emailaddress=test@test.com")
|
|
16
17
|
self.assertEqual(resp.status_code, 200)
|
modoboa/autoconfig/views.py
CHANGED
|
@@ -24,6 +24,10 @@ class ConfigBaseMixin:
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class AutoConfigView(ConfigBaseMixin, generic.TemplateView):
|
|
27
|
+
"""
|
|
28
|
+
Format documentation:
|
|
29
|
+
https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
|
|
30
|
+
"""
|
|
27
31
|
|
|
28
32
|
http_method_names = ["get"]
|
|
29
33
|
template_name = "autoconfig/autoconfig.xml"
|
|
@@ -31,7 +35,7 @@ class AutoConfigView(ConfigBaseMixin, generic.TemplateView):
|
|
|
31
35
|
def get_context_data(self, **kwargs):
|
|
32
36
|
emailaddress = self.request.GET.get("emailaddress")
|
|
33
37
|
if not emailaddress:
|
|
34
|
-
|
|
38
|
+
emailaddress = "%EMAILLOCALPART%@%EMAILDOMAIN%"
|
|
35
39
|
context = super().get_context_data(**kwargs)
|
|
36
40
|
context.update(self.get_common_context(emailaddress))
|
|
37
41
|
return context
|
|
@@ -88,14 +92,18 @@ class MobileConfigView(generic.View):
|
|
|
88
92
|
"EmailAccountType": "EmailTypeIMAP",
|
|
89
93
|
"EmailAddress": emailaddress,
|
|
90
94
|
# incoming
|
|
95
|
+
"IncomingMailServerAuthentication": "EmailAuthPassword",
|
|
91
96
|
"IncomingMailServerHostName": imap_settings["HOSTNAME"],
|
|
92
97
|
"IncomingMailServerPortNumber": imap_settings["PORT"],
|
|
93
|
-
"IncomingMailServerUseSSL":
|
|
98
|
+
"IncomingMailServerUseSSL": imap_settings["SOCKET_TYPE"].upper()
|
|
99
|
+
== "SSL", # `false` means StartTLS
|
|
94
100
|
"IncomingMailServerUsername": emailaddress,
|
|
95
101
|
# outgoing
|
|
102
|
+
"OutgoingMailServerAuthentication": "EmailAuthPassword",
|
|
96
103
|
"OutgoingMailServerHostName": smtp_settings["HOSTNAME"],
|
|
97
104
|
"OutgoingMailServerPortNumber": smtp_settings["PORT"],
|
|
98
|
-
"OutgoingMailServerUseSSL":
|
|
105
|
+
"OutgoingMailServerUseSSL": smtp_settings["SOCKET_TYPE"].upper()
|
|
106
|
+
== "SSL", # `false` means StartTLS,
|
|
99
107
|
"OutgoingMailServerUsername": emailaddress,
|
|
100
108
|
"OutgoingPasswordSameAsIncomingPassword": True,
|
|
101
109
|
}
|
modoboa/autoreply/models.py
CHANGED
|
@@ -38,9 +38,9 @@ class ARmessage(models.Model):
|
|
|
38
38
|
days = request.localconfig.parameters.get_value("tracking_period")
|
|
39
39
|
context["fromdate"] = localize(self.fromdate)
|
|
40
40
|
fromdate = self.fromdate.isoformat()
|
|
41
|
-
|
|
42
|
-
tz =
|
|
43
|
-
condition = [("currentdate", ":zone", tz, ":value", "ge", "iso8601",
|
|
41
|
+
datepart = fromdate[:-6]
|
|
42
|
+
tz = fromdate[-6:].replace(":", "")
|
|
43
|
+
condition = [("currentdate", ":zone", tz, ":value", "ge", "iso8601", datepart)]
|
|
44
44
|
if self.untildate:
|
|
45
45
|
context["untildate"] = localize(self.untildate)
|
|
46
46
|
condition.append(
|
|
@@ -51,7 +51,7 @@ class ARmessage(models.Model):
|
|
|
51
51
|
":value",
|
|
52
52
|
"lt",
|
|
53
53
|
"iso8601",
|
|
54
|
-
self.untildate.isoformat()
|
|
54
|
+
self.untildate.isoformat()[:-6],
|
|
55
55
|
)
|
|
56
56
|
)
|
|
57
57
|
content = self.content % context
|
|
@@ -6,6 +6,7 @@ import uuid
|
|
|
6
6
|
import caldav
|
|
7
7
|
from caldav.elements import dav, ical
|
|
8
8
|
from caldav import Calendar
|
|
9
|
+
from dateutil.relativedelta import relativedelta
|
|
9
10
|
import vobject
|
|
10
11
|
|
|
11
12
|
from django.utils import timezone
|
|
@@ -53,8 +54,10 @@ class Caldav_Backend(CalendarBackend):
|
|
|
53
54
|
start = datetime.datetime.combine(
|
|
54
55
|
vevent.dtstart.value, datetime.time.min
|
|
55
56
|
).replace(tzinfo=tz)
|
|
57
|
+
# Small back to make vuetify calendar happy. 'All day' events are generally
|
|
58
|
+
# created from one day to the day after (even for a 1 day duration...)
|
|
56
59
|
end = datetime.datetime.combine(
|
|
57
|
-
vevent.dtend.value, datetime.time.min
|
|
60
|
+
vevent.dtend.value - relativedelta(days=1), datetime.time.min
|
|
58
61
|
).replace(tzinfo=tz)
|
|
59
62
|
result.update({"allDay": all_day, "start": start, "end": end})
|
|
60
63
|
if "attendee" in vevent.contents:
|
|
@@ -80,17 +83,16 @@ class Caldav_Backend(CalendarBackend):
|
|
|
80
83
|
def create_event(self, data):
|
|
81
84
|
"""Create a new event."""
|
|
82
85
|
uid = uuid.uuid4()
|
|
83
|
-
cal = vobject.iCalendar()
|
|
84
|
-
cal.add("vevent")
|
|
85
|
-
cal.vevent.add("uid").value = str(uid)
|
|
86
|
-
cal.vevent.add("summary").value = data["title"]
|
|
87
86
|
if not data["allDay"]:
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
dtstart = data["start"]
|
|
88
|
+
dtend = data["end"]
|
|
90
89
|
else:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
dtstart = data["start_date"]
|
|
91
|
+
# Ensure consistent behavior with other calendar clients (like TB.)
|
|
92
|
+
dtend = data["end_date"] + relativedelta(days=1)
|
|
93
|
+
self.remote_cal.save_event(
|
|
94
|
+
uid=uid, dtstart=dtstart, dtend=dtend, summary=data["title"]
|
|
95
|
+
)
|
|
94
96
|
return uid
|
|
95
97
|
|
|
96
98
|
def update_event(self, uid, original_data):
|
|
@@ -102,8 +104,8 @@ class Caldav_Backend(CalendarBackend):
|
|
|
102
104
|
if "title" in data:
|
|
103
105
|
orig_evt.summary.value = data["title"]
|
|
104
106
|
if data.get("allDay"):
|
|
105
|
-
data["start"] = data["
|
|
106
|
-
data["end"] = data["
|
|
107
|
+
data["start"] = data["start_date"]
|
|
108
|
+
data["end"] = data["end_date"]
|
|
107
109
|
if "start" in data:
|
|
108
110
|
del orig_evt.contents["dtstart"]
|
|
109
111
|
orig_evt.add("dtstart").value = data["start"]
|
|
@@ -141,7 +143,9 @@ class Caldav_Backend(CalendarBackend):
|
|
|
141
143
|
|
|
142
144
|
def get_events(self, start, end):
|
|
143
145
|
"""Retrieve a list of events."""
|
|
144
|
-
orig_events = self.remote_cal.
|
|
146
|
+
orig_events = self.remote_cal.search(
|
|
147
|
+
server_expand=False, start=start, end=end, event=True
|
|
148
|
+
)
|
|
145
149
|
events = []
|
|
146
150
|
for event in orig_events:
|
|
147
151
|
events.append(self._serialize_event(event))
|
modoboa/calendars/mocks.py
CHANGED
|
@@ -49,11 +49,14 @@ class Calendar:
|
|
|
49
49
|
def add_event(self, data):
|
|
50
50
|
return True
|
|
51
51
|
|
|
52
|
+
def save_event(self, *args, **kwargs):
|
|
53
|
+
return True
|
|
54
|
+
|
|
52
55
|
def event_by_url(self, url):
|
|
53
56
|
res = objects.Event(url=url, data=EV1, parent=self)
|
|
54
57
|
return res
|
|
55
58
|
|
|
56
|
-
def
|
|
59
|
+
def search(self, **kwargs):
|
|
57
60
|
return [
|
|
58
61
|
objects.Event(data=EV1, parent=self),
|
|
59
62
|
objects.Event(data=EV2, parent=self),
|
modoboa/calendars/serializers.py
CHANGED
|
@@ -116,11 +116,11 @@ class EventSerializer(serializers.Serializer):
|
|
|
116
116
|
|
|
117
117
|
id = serializers.CharField(read_only=True)
|
|
118
118
|
title = serializers.CharField()
|
|
119
|
-
start = serializers.DateTimeField()
|
|
120
|
-
end = serializers.DateTimeField()
|
|
119
|
+
start = serializers.DateTimeField(required=False)
|
|
120
|
+
end = serializers.DateTimeField(required=False)
|
|
121
121
|
allDay = serializers.BooleanField(default=False)
|
|
122
122
|
color = serializers.CharField(read_only=True)
|
|
123
|
-
description = serializers.CharField(required=False)
|
|
123
|
+
description = serializers.CharField(required=False, allow_blank=True)
|
|
124
124
|
|
|
125
125
|
attendees = AttendeeSerializer(many=True, required=False)
|
|
126
126
|
|
|
@@ -147,10 +147,13 @@ class WritableEventSerializer(EventSerializer):
|
|
|
147
147
|
)
|
|
148
148
|
new_calendar_type = serializers.CharField(required=False)
|
|
149
149
|
|
|
150
|
+
start_date = serializers.DateField(required=False)
|
|
151
|
+
end_date = serializers.DateField(required=False)
|
|
152
|
+
|
|
150
153
|
def __init__(self, *args, **kwargs):
|
|
151
154
|
"""Set calendar list."""
|
|
152
155
|
calendar_type = kwargs.pop("calendar_type")
|
|
153
|
-
super(
|
|
156
|
+
super().__init__(*args, **kwargs)
|
|
154
157
|
self.update_calendar_field(calendar_type)
|
|
155
158
|
|
|
156
159
|
def update_calendar_field(self, calendar_type):
|
|
@@ -170,11 +173,13 @@ class WritableEventSerializer(EventSerializer):
|
|
|
170
173
|
def validate(self, data):
|
|
171
174
|
"""Make sure dates are present with allDay flag."""
|
|
172
175
|
errors = {}
|
|
173
|
-
if "allDay"
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
if data.get("allDay", False):
|
|
177
|
+
mandatory_fields = ["start_date", "end_date"]
|
|
178
|
+
else:
|
|
179
|
+
mandatory_fields = ["start", "end"]
|
|
180
|
+
for field in mandatory_fields:
|
|
181
|
+
if not data.get(field):
|
|
182
|
+
errors[field] = _("This field is required.")
|
|
178
183
|
if errors:
|
|
179
184
|
raise serializers.ValidationError(errors)
|
|
180
185
|
return data
|
modoboa/calendars/tests.py
CHANGED
|
@@ -18,6 +18,7 @@ from modoboa.lib.tests import ModoAPITestCase
|
|
|
18
18
|
from modoboa.admin.factories import populate_database
|
|
19
19
|
|
|
20
20
|
from . import factories
|
|
21
|
+
from . import jobs
|
|
21
22
|
from . import models
|
|
22
23
|
from . import mocks
|
|
23
24
|
|
|
@@ -106,7 +107,7 @@ class AccessRuleTestCase(ModoAPITestCase):
|
|
|
106
107
|
self.assertEqual(cfg.get(section, "permissions"), "Rr")
|
|
107
108
|
|
|
108
109
|
# Call a second time
|
|
109
|
-
|
|
110
|
+
jobs.generate_rights()
|
|
110
111
|
|
|
111
112
|
def test_rights_file_generation_with_admin(self):
|
|
112
113
|
self.set_global_parameter(
|
|
@@ -428,8 +429,8 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase):
|
|
|
428
429
|
url = f"/api/v2/user-calendars/{self.calendar.pk}/events/"
|
|
429
430
|
data = {
|
|
430
431
|
"title": "Test event",
|
|
431
|
-
"
|
|
432
|
-
"
|
|
432
|
+
"start_date": "2018-03-27",
|
|
433
|
+
"end_date": "2018-03-28",
|
|
433
434
|
"allDay": True,
|
|
434
435
|
"color": "#ffdddd",
|
|
435
436
|
"description": "Description",
|
|
@@ -461,8 +462,8 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase):
|
|
|
461
462
|
url = f"/api/v2/user-calendars/{self.calendar.pk}/events/1234/"
|
|
462
463
|
data = {
|
|
463
464
|
"title": "Test event",
|
|
464
|
-
"
|
|
465
|
-
"
|
|
465
|
+
"start_date": "2018-03-27",
|
|
466
|
+
"end_date": "2018-03-28",
|
|
466
467
|
"allDay": True,
|
|
467
468
|
"color": "#ffdddd",
|
|
468
469
|
"description": "Description",
|
|
@@ -483,8 +484,8 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase):
|
|
|
483
484
|
|
|
484
485
|
url = f"/api/v2/user-calendars/{self.calendar.pk}/events/1234/"
|
|
485
486
|
data = {
|
|
486
|
-
"
|
|
487
|
-
"
|
|
487
|
+
"start_date": "2018-03-27",
|
|
488
|
+
"end_date": "2018-03-28",
|
|
488
489
|
"allDay": True,
|
|
489
490
|
}
|
|
490
491
|
response = self.client.patch(url, data=data, format="json")
|
|
@@ -496,8 +497,8 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase):
|
|
|
496
497
|
self.client.post(reverse("core:login"), data)
|
|
497
498
|
data = {
|
|
498
499
|
"title": "Test event",
|
|
499
|
-
"
|
|
500
|
-
"
|
|
500
|
+
"start_date": "2018-03-27",
|
|
501
|
+
"end_date": "2018-03-28",
|
|
501
502
|
"allDay": True,
|
|
502
503
|
"color": "#ffdddd",
|
|
503
504
|
"description": "Description",
|
modoboa/calendars/viewsets.py
CHANGED
|
@@ -21,7 +21,9 @@ from . import serializers
|
|
|
21
21
|
|
|
22
22
|
def parse_date_from_iso(value):
|
|
23
23
|
"""Return a tz aware datetime parsed from an ISO date."""
|
|
24
|
-
|
|
24
|
+
result = dateutil.parser.parse(value)
|
|
25
|
+
print(result)
|
|
26
|
+
return result
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
class CheckTokenMixin:
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Generated by Django 5.2.9 on 2025-12-12 14:42
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
("contacts", "0007_alter_contact_address"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name="addressbook",
|
|
15
|
+
name="syncing",
|
|
16
|
+
field=models.BooleanField(default=False),
|
|
17
|
+
),
|
|
18
|
+
]
|
modoboa/contacts/models.py
CHANGED
|
@@ -22,6 +22,7 @@ class AddressBook(models.Model):
|
|
|
22
22
|
name = models.CharField(max_length=50)
|
|
23
23
|
sync_token = models.TextField(blank=True)
|
|
24
24
|
last_sync = models.DateTimeField(null=True)
|
|
25
|
+
syncing = models.BooleanField(default=False)
|
|
25
26
|
user = models.ForeignKey("core.User", on_delete=models.CASCADE)
|
|
26
27
|
_path = models.TextField()
|
|
27
28
|
|
modoboa/contacts/serializers.py
CHANGED
|
@@ -14,7 +14,7 @@ class AddressBookSerializer(serializers.ModelSerializer):
|
|
|
14
14
|
|
|
15
15
|
class Meta:
|
|
16
16
|
model = models.AddressBook
|
|
17
|
-
fields = ("pk", "name", "url", "last_sync")
|
|
17
|
+
fields = ("pk", "name", "url", "last_sync", "syncing")
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class EmailAddressSerializer(serializers.ModelSerializer):
|
|
@@ -235,9 +235,12 @@ class UserPreferencesSerializer(serializers.Serializer):
|
|
|
235
235
|
|
|
236
236
|
def post_save(self, request):
|
|
237
237
|
"""Create remote cal if necessary."""
|
|
238
|
+
abook = request.user.addressbook_set.first()
|
|
238
239
|
if not self.validated_data["enable_carddav_sync"]:
|
|
240
|
+
abook.last_sync = None
|
|
241
|
+
abook.sync_token = ""
|
|
242
|
+
abook.save(update_fields=["last_sync", "sync_token"])
|
|
239
243
|
return
|
|
240
|
-
abook = request.user.addressbook_set.first()
|
|
241
244
|
if abook.last_sync:
|
|
242
245
|
return
|
|
243
246
|
tasks.create_cdav_addressbook(abook, request.auth)
|
modoboa/contacts/tasks.py
CHANGED
|
@@ -49,12 +49,17 @@ def push_addressbook_to_carddav(request, addressbook):
|
|
|
49
49
|
addressbook.save(update_fields=["last_sync", "sync_token"])
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
def sync_addressbook_from_cdav(
|
|
52
|
+
def sync_addressbook_from_cdav(addressbook_id: int, access_token: str):
|
|
53
53
|
"""Fetch changes from CardDAV server."""
|
|
54
|
-
|
|
54
|
+
addressbook = models.AddressBook.objects.get(id=addressbook_id)
|
|
55
|
+
if addressbook.syncing:
|
|
56
|
+
return
|
|
57
|
+
clt = get_cdav_client(addressbook, addressbook.user.email, access_token)
|
|
55
58
|
changes = clt.sync_vcards(addressbook.sync_token)
|
|
56
59
|
if not len(changes["cards"]):
|
|
57
60
|
return
|
|
61
|
+
addressbook.syncing = True
|
|
62
|
+
addressbook.save()
|
|
58
63
|
for card in changes["cards"]:
|
|
59
64
|
# UID sometimes embded .vcf extension, sometimes not...
|
|
60
65
|
long_uid = card["href"].split("/")[-1]
|
|
@@ -73,7 +78,8 @@ def sync_addressbook_from_cdav(request, addressbook):
|
|
|
73
78
|
models.Contact.objects.filter(uid__in=[long_uid, short_uid]).delete()
|
|
74
79
|
addressbook.last_sync = timezone.now()
|
|
75
80
|
addressbook.sync_token = changes["token"]
|
|
76
|
-
addressbook.
|
|
81
|
+
addressbook.syncing = False
|
|
82
|
+
addressbook.save(update_fields=["last_sync", "sync_token", "syncing"])
|
|
77
83
|
|
|
78
84
|
|
|
79
85
|
def push_contact_to_cdav(request, contact):
|
modoboa/contacts/tests.py
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
import os
|
|
4
4
|
|
|
5
5
|
import httmock
|
|
6
|
+
from rq import SimpleWorker
|
|
6
7
|
|
|
7
8
|
from django.core import management
|
|
8
|
-
|
|
9
9
|
from django.urls import reverse
|
|
10
10
|
from django.utils import timezone
|
|
11
11
|
|
|
12
|
+
import django_rq
|
|
13
|
+
|
|
12
14
|
from modoboa.admin import factories as admin_factories
|
|
13
15
|
from modoboa.core import models as core_models
|
|
14
16
|
from modoboa.lib.tests import ModoAPITestCase
|
|
@@ -65,13 +67,25 @@ class TestDataMixin:
|
|
|
65
67
|
)
|
|
66
68
|
self.assertEqual(response.status_code, 200)
|
|
67
69
|
|
|
70
|
+
def disable_cdav_sync(self):
|
|
71
|
+
url = reverse("v2:parameter-user-detail", args=["contacts"])
|
|
72
|
+
response = self.client.put(
|
|
73
|
+
url,
|
|
74
|
+
{
|
|
75
|
+
"enable_carddav_sync": False,
|
|
76
|
+
"sync_frequency": 300,
|
|
77
|
+
},
|
|
78
|
+
format="json",
|
|
79
|
+
)
|
|
80
|
+
self.assertEqual(response.status_code, 200)
|
|
81
|
+
|
|
68
82
|
|
|
69
83
|
class ViewsTestCase(TestDataMixin, ModoAPITestCase):
|
|
70
84
|
"""Check views."""
|
|
71
85
|
|
|
72
86
|
def test_user_settings(self):
|
|
73
87
|
"""Check that remote collection creation request is sent."""
|
|
74
|
-
# 1. Addressbook with contacts must be synced
|
|
88
|
+
# 1. Addressbook with contacts must be synced manuelle
|
|
75
89
|
self.client.force_authenticate(self.user)
|
|
76
90
|
self.enable_cdav_sync()
|
|
77
91
|
self.addressbook.refresh_from_db()
|
|
@@ -87,6 +101,10 @@ class ViewsTestCase(TestDataMixin, ModoAPITestCase):
|
|
|
87
101
|
self.enable_cdav_sync()
|
|
88
102
|
abook.refresh_from_db()
|
|
89
103
|
self.assertIsNot(abook.last_sync, None)
|
|
104
|
+
# And disable sync.
|
|
105
|
+
self.disable_cdav_sync()
|
|
106
|
+
abook.refresh_from_db()
|
|
107
|
+
self.assertIs(abook.last_sync, None)
|
|
90
108
|
|
|
91
109
|
|
|
92
110
|
class AddressBookViewSetTestCase(TestDataMixin, ModoAPITestCase):
|
|
@@ -118,10 +136,18 @@ class AddressBookViewSetTestCase(TestDataMixin, ModoAPITestCase):
|
|
|
118
136
|
data = {"username": self.user.username, "password": "toto"}
|
|
119
137
|
response = self.client.post(reverse("core:login"), data)
|
|
120
138
|
self.enable_cdav_sync()
|
|
121
|
-
|
|
139
|
+
last_sync = timezone.now()
|
|
140
|
+
self.user.addressbook_set.update(last_sync=last_sync)
|
|
141
|
+
addressbook = self.user.addressbook_set.first()
|
|
142
|
+
self.assertEqual(addressbook.sync_token, "")
|
|
122
143
|
with httmock.HTTMock(mocks.options_mock, mocks.report_mock, mocks.get_mock):
|
|
123
144
|
response = self.client.get(reverse("api:addressbook-sync-from-cdav"))
|
|
145
|
+
queue = django_rq.get_queue("modoboa")
|
|
146
|
+
worker = SimpleWorker([queue], connection=queue.connection)
|
|
147
|
+
worker.work(burst=True)
|
|
124
148
|
self.assertEqual(response.status_code, 200)
|
|
149
|
+
addressbook.refresh_from_db()
|
|
150
|
+
self.assertNotEqual(addressbook.sync_token, "")
|
|
125
151
|
|
|
126
152
|
|
|
127
153
|
class CategoryViewSetTestCase(TestDataMixin, ModoAPITestCase):
|
|
@@ -167,18 +193,18 @@ class ContactViewSetTestCase(TestDataMixin, ModoAPITestCase):
|
|
|
167
193
|
url = reverse("api:contact-list")
|
|
168
194
|
response = self.client.get(url)
|
|
169
195
|
self.assertEqual(response.status_code, 200)
|
|
170
|
-
self.assertEqual(len(response.data), 3)
|
|
196
|
+
self.assertEqual(len(response.data["results"]), 3)
|
|
171
197
|
|
|
172
198
|
response = self.client.get(f"{url}?search=homer")
|
|
173
199
|
self.assertEqual(response.status_code, 200)
|
|
174
|
-
self.assertEqual(len(response.data), 1)
|
|
200
|
+
self.assertEqual(len(response.data["results"]), 1)
|
|
175
201
|
|
|
176
202
|
def test_filter_by_category(self):
|
|
177
203
|
"""Check filtering."""
|
|
178
204
|
url = reverse("api:contact-list")
|
|
179
205
|
response = self.client.get(f"{url}?category={self.category.name}")
|
|
180
206
|
self.assertEqual(response.status_code, 200)
|
|
181
|
-
self.assertEqual(len(response.data), 1)
|
|
207
|
+
self.assertEqual(len(response.data["results"]), 1)
|
|
182
208
|
|
|
183
209
|
def test_create_contact(self):
|
|
184
210
|
"""Create a new contact."""
|
modoboa/contacts/viewsets.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"""Contacts viewsets."""
|
|
2
2
|
|
|
3
3
|
import django_filters.rest_framework
|
|
4
|
+
import django_rq
|
|
4
5
|
from rest_framework.decorators import action
|
|
5
6
|
from rest_framework import filters, response, viewsets
|
|
6
7
|
from rest_framework.permissions import IsAuthenticated
|
|
7
8
|
|
|
9
|
+
from modoboa.lib import pagination
|
|
10
|
+
|
|
8
11
|
from . import models
|
|
9
12
|
from . import serializers
|
|
10
13
|
from . import tasks
|
|
@@ -40,7 +43,8 @@ class AddressBookViewSet(viewsets.GenericViewSet):
|
|
|
40
43
|
if not abook.last_sync:
|
|
41
44
|
return response.Response()
|
|
42
45
|
if request.user.parameters.get_value("enable_carddav_sync", app="contacts"):
|
|
43
|
-
|
|
46
|
+
queue = django_rq.get_queue("modoboa")
|
|
47
|
+
queue.enqueue(tasks.sync_addressbook_from_cdav, abook.pk, str(request.auth))
|
|
44
48
|
return response.Response({})
|
|
45
49
|
|
|
46
50
|
|
|
@@ -70,10 +74,12 @@ class ContactViewSet(viewsets.ModelViewSet):
|
|
|
70
74
|
"""Contact ViewSet."""
|
|
71
75
|
|
|
72
76
|
filter_backends = [
|
|
77
|
+
filters.OrderingFilter,
|
|
73
78
|
filters.SearchFilter,
|
|
74
79
|
django_filters.rest_framework.DjangoFilterBackend,
|
|
75
80
|
]
|
|
76
81
|
filterset_class = ContactFilter
|
|
82
|
+
pagination_class = pagination.CustomPageNumberPagination
|
|
77
83
|
permission_classes = [IsAuthenticated]
|
|
78
84
|
search_fields = ("^first_name", "^last_name", "^emails__address")
|
|
79
85
|
serializer_class = serializers.ContactSerializer
|
|
@@ -190,19 +190,12 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
|
|
|
190
190
|
log_maximum_age = serializers.IntegerField(default=365)
|
|
191
191
|
message_history_maximum_age = serializers.IntegerField(default=180)
|
|
192
192
|
items_per_page = serializers.IntegerField(default=30)
|
|
193
|
-
default_top_redirection = serializers.ChoiceField(
|
|
194
|
-
default="user", choices=[("user", _("User profile"))], required=False
|
|
195
|
-
)
|
|
196
193
|
|
|
197
194
|
def __init__(self, *args, **kwargs):
|
|
198
195
|
super().__init__(*args, **kwargs)
|
|
199
196
|
sms_backend_fields = sms_backends.get_all_backend_serializer_settings()
|
|
200
197
|
for field, definition in sms_backend_fields.items():
|
|
201
198
|
self.fields[field] = definition["type"](**definition["attrs"])
|
|
202
|
-
# Choices serializer for default_top_redirection field
|
|
203
|
-
self.fields["default_top_redirection"].choices = (
|
|
204
|
-
app_settings.enabled_applications()
|
|
205
|
-
)
|
|
206
199
|
# Populate choices of password_scheme
|
|
207
200
|
self.fields["password_scheme"].choices = app_settings.get_password_scheme()
|
|
208
201
|
|
modoboa/core/api/v2/tests.py
CHANGED
|
@@ -74,7 +74,6 @@ CORE_SETTINGS = {
|
|
|
74
74
|
"top_notifications_check_interval": 30,
|
|
75
75
|
"log_maximum_age": 365,
|
|
76
76
|
"items_per_page": 30,
|
|
77
|
-
"default_top_redirection": "user",
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
|
|
@@ -596,15 +595,6 @@ class FIDOViewSetTestCase(ModoAPITestCase):
|
|
|
596
595
|
self.assertIn("tokens", resp.json())
|
|
597
596
|
|
|
598
597
|
|
|
599
|
-
class CapabilitiesAPITestCase(ModoAPITestCase):
|
|
600
|
-
|
|
601
|
-
def test_get(self):
|
|
602
|
-
url = reverse("v2:capabilities")
|
|
603
|
-
response = self.client.get(url)
|
|
604
|
-
self.assertEqual(response.status_code, 200)
|
|
605
|
-
self.assertIn("rspamd", response.json()["capabilities"])
|
|
606
|
-
|
|
607
|
-
|
|
608
598
|
class ThemeAPITestCase(ModoAPITestCase):
|
|
609
599
|
|
|
610
600
|
def test_get(self):
|