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.
Files changed (364) hide show
  1. modoboa/admin/api/v2/serializers.py +1 -0
  2. modoboa/admin/app_settings.py +12 -0
  3. modoboa/admin/{management/commands/subcommands/_mx.py → dns_checker.py} +41 -111
  4. modoboa/admin/jobs.py +86 -0
  5. modoboa/admin/management/commands/modo.py +0 -2
  6. modoboa/admin/tests/test_mailbox_operations.py +4 -4
  7. modoboa/admin/tests/test_mx.py +68 -56
  8. modoboa/amavis/jobs.py +11 -0
  9. modoboa/amavis/tests/test_jobs.py +18 -0
  10. modoboa/amavis/tests/test_viewsets.py +2 -3
  11. modoboa/autoconfig/templates/autoconfig/autoconfig.xml +2 -2
  12. modoboa/autoconfig/templates/autoconfig/autodiscover.xml +14 -0
  13. modoboa/autoconfig/tests.py +2 -1
  14. modoboa/autoconfig/views.py +11 -3
  15. modoboa/autoreply/models.py +4 -4
  16. modoboa/calendars/backends/caldav_.py +17 -13
  17. modoboa/calendars/jobs.py +7 -0
  18. modoboa/calendars/mocks.py +4 -1
  19. modoboa/calendars/serializers.py +14 -9
  20. modoboa/calendars/tests.py +10 -9
  21. modoboa/calendars/viewsets.py +3 -1
  22. modoboa/contacts/migrations/0008_addressbook_syncing.py +18 -0
  23. modoboa/contacts/models.py +1 -0
  24. modoboa/contacts/serializers.py +5 -2
  25. modoboa/contacts/tasks.py +9 -3
  26. modoboa/contacts/tests.py +32 -6
  27. modoboa/contacts/viewsets.py +7 -1
  28. modoboa/core/api/v2/serializers.py +0 -7
  29. modoboa/core/api/v2/tests.py +0 -10
  30. modoboa/core/app_settings.py +0 -22
  31. modoboa/core/commands/deploy.py +13 -0
  32. modoboa/core/commands/templates/cron_config.py.tpl +33 -0
  33. modoboa/core/commands/templates/settings.py.tpl +21 -0
  34. modoboa/core/jobs.py +34 -0
  35. modoboa/core/management/commands/load_initial_data.py +1 -1
  36. modoboa/core/password_hashers/base.py +4 -1
  37. modoboa/core/tests/test_core.py +0 -14
  38. modoboa/core/tests/test_jobs.py +40 -0
  39. modoboa/frontend_dist/assets/{AccountAliasForm-tLHotdIR.js → AccountAliasForm-DO6DwfjE.js} +1 -1
  40. modoboa/frontend_dist/assets/{AccountEditView-DnQIIg47.js → AccountEditView-CCuN9mGB.js} +1 -1
  41. modoboa/frontend_dist/assets/AccountLayout-Ge7fzuZg.js +1 -0
  42. modoboa/frontend_dist/assets/AccountPasswordSubForm-PkFPblkR.js +1 -0
  43. modoboa/frontend_dist/assets/AccountView-dgseekZ8.js +1 -0
  44. modoboa/frontend_dist/assets/AddressBook-DZWOHOJj.js +1 -0
  45. modoboa/frontend_dist/assets/AdminLayout-itB_mmH_.js +1 -0
  46. modoboa/frontend_dist/assets/AlarmsView-King6zb6.js +1 -0
  47. modoboa/frontend_dist/assets/{AliasEditView-n75NEN_s.js → AliasEditView-CTjrXYPf.js} +1 -1
  48. modoboa/frontend_dist/assets/AliasRecipientForm-CI7bXqVp.js +1 -0
  49. modoboa/frontend_dist/assets/{AliasView-DFIvPeIm.js → AliasView-BN13MNN_.js} +1 -1
  50. modoboa/frontend_dist/assets/AuditTrailView-C1jQwgoo.js +1 -0
  51. modoboa/frontend_dist/assets/CalendarView-juHVIHU5.css +1 -0
  52. modoboa/frontend_dist/assets/CalendarView-rRSzqxrH.js +1 -0
  53. modoboa/frontend_dist/assets/ChoiceField-DCr12shR.js +1 -0
  54. modoboa/frontend_dist/assets/ComposeEmailForm-BmtKwFb1.js +2 -0
  55. modoboa/frontend_dist/assets/ComposeEmailForm-CVNDl-Mq.css +1 -0
  56. modoboa/frontend_dist/assets/ComposeEmailView-JW22Phrb.js +1 -0
  57. modoboa/frontend_dist/assets/ConfirmDialog-CWpdwSQ6.js +1 -0
  58. modoboa/frontend_dist/assets/ConnectedLayout-BP8pO27H.js +1 -0
  59. modoboa/frontend_dist/assets/{ConnectedLayout-C6HNXWkp.css → ConnectedLayout-Ddpb_6yT.css} +1 -1
  60. modoboa/frontend_dist/assets/CreationForm-CzelJsVQ.js +1 -0
  61. modoboa/frontend_dist/assets/DashboardView-BBkBodVj.js +1 -0
  62. modoboa/frontend_dist/assets/{DomainAdminList-BjtfbhRj.js → DomainAdminList-DD7p3i6F.js} +1 -1
  63. modoboa/frontend_dist/assets/{DomainEditView-Cq5Ea4RW.js → DomainEditView-DoIoNYC4.js} +1 -1
  64. modoboa/frontend_dist/assets/{DomainTransportForm-DAXJGNLA.js → DomainTransportForm-CbiJF9z5.js} +1 -1
  65. modoboa/frontend_dist/assets/{DomainView-BfpK7U14.js → DomainView-BXvznFYz.js} +3 -3
  66. modoboa/frontend_dist/assets/DomainsView-ffYjiffp.js +1 -0
  67. modoboa/frontend_dist/assets/EmailField-KhhJYA4D.js +1 -0
  68. modoboa/frontend_dist/assets/EmailSchedulingForm-CQL5Vfdr.js +1 -0
  69. modoboa/frontend_dist/assets/EmailView-Bq2bHZBO.js +1 -0
  70. modoboa/frontend_dist/assets/EmptyLayout-NrTftp18.js +1 -0
  71. modoboa/frontend_dist/assets/FiltersView-SOQy0U_3.js +1 -0
  72. modoboa/frontend_dist/assets/ForwardEmailView-kEPDjWUw.js +1 -0
  73. modoboa/frontend_dist/assets/{HtmlEditor-D753fIAJ.js → HtmlEditor-CyBl5wj2.js} +15 -15
  74. modoboa/frontend_dist/assets/IdentitiesView-CZFf4oR9.js +1 -0
  75. modoboa/frontend_dist/assets/InformationView-CAtneAqM.js +1 -0
  76. modoboa/frontend_dist/assets/{LoadingData-Tm56XNwj.js → LoadingData-C2txD49L.js} +1 -1
  77. modoboa/frontend_dist/assets/{LoginCallbackView-DjCHUgz5.js → LoginCallbackView-DAvty3CI.js} +1 -1
  78. modoboa/frontend_dist/assets/{LoginView-D0r4XBEl.js → LoginView-DHYITkn4.js} +1 -1
  79. modoboa/frontend_dist/assets/MailboxView-BNg2v7mi.css +1 -0
  80. modoboa/frontend_dist/assets/MailboxView-DHg5A78D.js +5 -0
  81. modoboa/frontend_dist/assets/MenuItems-C9p70bSC.js +1 -0
  82. modoboa/frontend_dist/assets/{MessageView-BWh9BqzS.js → MessageView-CrrEJpNI.js} +1 -1
  83. modoboa/frontend_dist/assets/MessagesView-CyecRd4I.js +1 -0
  84. modoboa/frontend_dist/assets/MigrationsView-DXGKKz2h.js +1 -0
  85. modoboa/frontend_dist/assets/{ParametersForm-y02-GM0r.js → ParametersForm-DFKYkPAs.js} +1 -1
  86. modoboa/frontend_dist/assets/ParametersView-B1wT5oyt.js +1 -0
  87. modoboa/frontend_dist/assets/ParametersView-CfvVzKea.js +1 -0
  88. modoboa/frontend_dist/assets/ProviderEditView-De-8ggBp.js +1 -0
  89. modoboa/frontend_dist/assets/ProviderGeneralForm-DO-IpJMQ.js +1 -0
  90. modoboa/frontend_dist/assets/ProvidersView-Bhk7sCz4.js +1 -0
  91. modoboa/frontend_dist/assets/QuarantineLayout-C1YszNmP.js +1 -0
  92. modoboa/frontend_dist/assets/QuarantineView-cElD_rS-.js +1 -0
  93. modoboa/frontend_dist/assets/ReplyEmailView-BvuOZdLl.js +1 -0
  94. modoboa/frontend_dist/assets/ResourcesForm-FWxrmVwo.js +1 -0
  95. modoboa/frontend_dist/assets/SelfServiceLayout-JHRvfvQf.js +1 -0
  96. modoboa/frontend_dist/assets/SettingsView-SIsKAXtQ.css +1 -0
  97. modoboa/frontend_dist/assets/SettingsView-s2l2Xl1L.js +6 -0
  98. modoboa/frontend_dist/assets/StatisticsView-FiidWvad.js +1 -0
  99. modoboa/frontend_dist/assets/TimeSerieChart-CdqUHiO_.js +1 -0
  100. modoboa/frontend_dist/assets/TimeSerieChart-nLIFGI0y.css +1 -0
  101. modoboa/frontend_dist/assets/UserLayout-DyvI8duf.js +1 -0
  102. modoboa/frontend_dist/assets/{VAlert-CNys_LUS.js → VAlert-MDbeolOo.js} +1 -1
  103. modoboa/frontend_dist/assets/VApp-DM1KLQfQ.js +1 -0
  104. modoboa/frontend_dist/assets/VAutocomplete-C9NL5_uo.css +1 -0
  105. modoboa/frontend_dist/assets/VAutocomplete-CQsWYWNX.js +1 -0
  106. modoboa/frontend_dist/assets/VAvatar-CXV_FqpP.js +1 -0
  107. modoboa/frontend_dist/assets/VBadge-7tDx7aI3.js +1 -0
  108. modoboa/frontend_dist/assets/{VCard-BoH6kjPa.js → VCard-CBtX8JF-.js} +1 -1
  109. modoboa/frontend_dist/assets/VCheckbox-zY1MApOy.js +1 -0
  110. modoboa/frontend_dist/assets/{VCheckboxBtn-Ccl786Rg.js → VCheckboxBtn-B0mIT3E0.js} +1 -1
  111. modoboa/frontend_dist/assets/VColorPicker-BbCHvk6K.js +1 -0
  112. modoboa/frontend_dist/assets/{VColorPicker-B_lVDaYR.css → VColorPicker-C9m8L-6U.css} +1 -1
  113. modoboa/frontend_dist/assets/{VContainer-DKMcKasC.js → VContainer-Cn-vKB3s.js} +1 -1
  114. modoboa/frontend_dist/assets/VDataTable-CNT9KOSp.js +1 -0
  115. modoboa/frontend_dist/assets/VDataTableServer-_yn4Ry6U.js +1 -0
  116. modoboa/frontend_dist/assets/VDataTableVirtual-Csxh3Gp6.js +1 -0
  117. modoboa/frontend_dist/assets/VDatePicker-_OUDqShN.css +1 -0
  118. modoboa/frontend_dist/assets/VDatePicker-iFu13xIP.js +2 -0
  119. modoboa/frontend_dist/assets/VDialog-lKtBdqnp.js +1 -0
  120. modoboa/frontend_dist/assets/VExpansionPanels-CaIvk9iF.js +1 -0
  121. modoboa/frontend_dist/assets/VFileInput-C7J_qVmk.js +1 -0
  122. modoboa/frontend_dist/assets/VForm-fdQ5d-CH.js +1 -0
  123. modoboa/frontend_dist/assets/VInput-BwHvhzAe.js +1 -0
  124. modoboa/frontend_dist/assets/VMenu-BpmJf4X2.js +1 -0
  125. modoboa/frontend_dist/assets/VMenu-C5A_5Hs5.css +1 -0
  126. modoboa/frontend_dist/assets/VPicker-B7cB3kJg.css +1 -0
  127. modoboa/frontend_dist/assets/VPicker-BZho70wU.js +1 -0
  128. modoboa/frontend_dist/assets/VProgressCircular-DRw_-iNj.js +1 -0
  129. modoboa/frontend_dist/assets/VRadioGroup--eiP5xtJ.js +1 -0
  130. modoboa/frontend_dist/assets/{VRow-CuVZGqBj.js → VRow-83Qnr5iB.js} +1 -1
  131. modoboa/frontend_dist/assets/VSelect-BcmGFGif.js +1 -0
  132. modoboa/frontend_dist/assets/{VSelectionControl-uKY9Zull.js → VSelectionControl-Cqi1xt-q.js} +1 -1
  133. modoboa/frontend_dist/assets/VSheet-C3MaHhtw.js +1 -0
  134. modoboa/frontend_dist/assets/VSpacer-CuSkdJZL.js +1 -0
  135. modoboa/frontend_dist/assets/VSwitch-XumUl685.js +1 -0
  136. modoboa/frontend_dist/assets/{VTable-gDsAUSLX.js → VTable-f7wcr2AZ.js} +1 -1
  137. modoboa/frontend_dist/assets/VTabs-CDfdejXj.css +1 -0
  138. modoboa/frontend_dist/assets/VTabs-y4wNP4im.js +1 -0
  139. modoboa/frontend_dist/assets/VTextField-BPIvtrn4.js +1 -0
  140. modoboa/frontend_dist/assets/VTextField-DjbYGlzs.css +1 -0
  141. modoboa/frontend_dist/assets/VTextarea-B6bGYcC3.js +1 -0
  142. modoboa/frontend_dist/assets/VTextarea-f6vTjzFy.css +1 -0
  143. modoboa/frontend_dist/assets/VToolbar-BYDPtwf0.css +1 -0
  144. modoboa/frontend_dist/assets/VToolbar-CB6wwtYc.js +1 -0
  145. modoboa/frontend_dist/assets/VWindowItem-CDtLLEkg.js +1 -0
  146. modoboa/frontend_dist/assets/WebmailLayout-CQQAolnl.css +1 -0
  147. modoboa/frontend_dist/assets/WebmailLayout-tosQSHLS.js +1 -0
  148. modoboa/frontend_dist/assets/{accounts-DqbBlaLK.js → accounts-KUsk6LHW.js} +1 -1
  149. modoboa/frontend_dist/assets/{admin-D36m_nXq.js → admin-BW9cZW0P.js} +1 -1
  150. modoboa/frontend_dist/assets/{aliases-BtyF8GC8.js → aliases-Ge0hjIsH.js} +1 -1
  151. modoboa/frontend_dist/assets/{amavis-DySmsCJQ.js → amavis-BbFeFfsk.js} +1 -1
  152. modoboa/frontend_dist/assets/{amavis-B4QhM5Fa.js → amavis-DtuzP_CS.js} +1 -1
  153. modoboa/frontend_dist/assets/{contacts-Df8ptuMn.js → contacts-DMJlQTe0.js} +1 -1
  154. modoboa/frontend_dist/assets/{domains-CSxtTvUh.js → domains-Du64lcXT.js} +1 -1
  155. modoboa/frontend_dist/assets/domains.store-1U61jeCV.js +1 -0
  156. modoboa/frontend_dist/assets/events-BM3in65C.js +1 -0
  157. modoboa/frontend_dist/assets/filter-Dihm6o59.js +1 -0
  158. modoboa/frontend_dist/assets/importExport-HGcNGWOm.js +1 -0
  159. modoboa/frontend_dist/assets/{index-B9q1vO3K.css → index-B1EK3MQe.css} +1 -1
  160. modoboa/frontend_dist/assets/{index-Bp9Fb67E.js → index-Dv00bmw9.js} +1 -1
  161. modoboa/frontend_dist/assets/index-jui3edpn.js +1001 -0
  162. modoboa/frontend_dist/assets/{language.store-_-zrrEiS.js → language.store-OcfdXL_-.js} +1 -1
  163. modoboa/frontend_dist/assets/languages-CF8hxo7x.js +1 -0
  164. modoboa/frontend_dist/assets/{layout-CjSP_k58.js → layout-DOO7TRTJ.js} +1 -1
  165. modoboa/frontend_dist/assets/{layout.store-CF5WRevH.js → layout.store-C0g-piJn.js} +1 -1
  166. modoboa/frontend_dist/assets/{logos-95XYS6uH.js → logos-Dz2Gzei-.js} +1 -1
  167. modoboa/frontend_dist/assets/{logs-CyU_PIDh.js → logs-CLm32Weu.js} +1 -1
  168. modoboa/frontend_dist/assets/{parameters-BxAXBXU1.js → parameters-DMIAQ7cd.js} +1 -1
  169. modoboa/frontend_dist/assets/{parameters.store-CATOSoQZ.js → parameters.store-DLnFzCwV.js} +1 -1
  170. modoboa/frontend_dist/assets/{permissions-cSkGqV3M.js → permissions-CrpE0b4w.js} +1 -1
  171. modoboa/frontend_dist/assets/{ssrBoot-05WtbG6H.js → ssrBoot-B7cr7q9U.js} +1 -1
  172. modoboa/frontend_dist/assets/{tag-BmV84V2s.js → tag-WF93n81Q.js} +1 -1
  173. modoboa/frontend_dist/assets/{theme-myV4ekXo.js → theme-_0oOYChG.js} +1 -1
  174. modoboa/frontend_dist/assets/transports-CS61syt-.js +1 -0
  175. modoboa/frontend_dist/assets/webmail-CYDXU0DS.js +1 -0
  176. modoboa/frontend_dist/assets/webmail.store-BvHCQSjM.js +1 -0
  177. modoboa/frontend_dist/index.html +2 -2
  178. modoboa/imap_migration/api/v2/serializers.py +13 -0
  179. modoboa/imap_migration/models.py +3 -3
  180. modoboa/imap_migration/templates/imap_migration/offlineimap.conf +2 -2
  181. modoboa/imap_migration/tests.py +28 -0
  182. modoboa/lib/cryptutils.py +2 -2
  183. modoboa/lib/sysutils.py +1 -1
  184. modoboa/lib/tests/__init__.py +0 -1
  185. modoboa/locale/br/LC_MESSAGES/django.mo +0 -0
  186. modoboa/locale/br/LC_MESSAGES/django.po +753 -317
  187. modoboa/locale/cs/LC_MESSAGES/django.po +671 -309
  188. modoboa/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  189. modoboa/locale/cs_CZ/LC_MESSAGES/django.po +801 -378
  190. modoboa/locale/de/LC_MESSAGES/django.mo +0 -0
  191. modoboa/locale/de/LC_MESSAGES/django.po +1116 -455
  192. modoboa/locale/de_DE/LC_MESSAGES/django.mo +0 -0
  193. modoboa/locale/de_DE/LC_MESSAGES/django.po +745 -318
  194. modoboa/locale/el_GR/LC_MESSAGES/django.mo +0 -0
  195. modoboa/locale/el_GR/LC_MESSAGES/django.po +825 -393
  196. modoboa/locale/en/LC_MESSAGES/django.po +669 -309
  197. modoboa/locale/es/LC_MESSAGES/django.mo +0 -0
  198. modoboa/locale/es/LC_MESSAGES/django.po +810 -390
  199. modoboa/locale/es_MX/LC_MESSAGES/django.po +669 -309
  200. modoboa/locale/fi/LC_MESSAGES/django.mo +0 -0
  201. modoboa/locale/fi/LC_MESSAGES/django.po +756 -316
  202. modoboa/locale/fr/LC_MESSAGES/django.mo +0 -0
  203. modoboa/locale/fr/LC_MESSAGES/django.po +674 -313
  204. modoboa/locale/hu/LC_MESSAGES/django.po +669 -309
  205. modoboa/locale/it/LC_MESSAGES/django.mo +0 -0
  206. modoboa/locale/it/LC_MESSAGES/django.po +807 -380
  207. modoboa/locale/ja_JP/LC_MESSAGES/django.mo +0 -0
  208. modoboa/locale/ja_JP/LC_MESSAGES/django.po +951 -419
  209. modoboa/locale/ka/LC_MESSAGES/django.po +669 -309
  210. modoboa/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
  211. modoboa/locale/nl_NL/LC_MESSAGES/django.po +807 -387
  212. modoboa/locale/no/LC_MESSAGES/django.po +669 -309
  213. modoboa/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
  214. modoboa/locale/pl_PL/LC_MESSAGES/django.po +826 -396
  215. modoboa/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
  216. modoboa/locale/pt_BR/LC_MESSAGES/django.po +830 -392
  217. modoboa/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
  218. modoboa/locale/pt_PT/LC_MESSAGES/django.po +788 -371
  219. modoboa/locale/ro_RO/LC_MESSAGES/django.mo +0 -0
  220. modoboa/locale/ro_RO/LC_MESSAGES/django.po +763 -316
  221. modoboa/locale/ru/LC_MESSAGES/django.mo +0 -0
  222. modoboa/locale/ru/LC_MESSAGES/django.po +1336 -1046
  223. modoboa/locale/si/LC_MESSAGES/django.po +669 -309
  224. modoboa/locale/sk/LC_MESSAGES/django.po +671 -309
  225. modoboa/locale/sk_SK/LC_MESSAGES/django.po +671 -309
  226. modoboa/locale/sl_SI/LC_MESSAGES/django.po +695 -315
  227. modoboa/locale/sv/LC_MESSAGES/django.mo +0 -0
  228. modoboa/locale/sv/LC_MESSAGES/django.po +819 -387
  229. modoboa/locale/tr/LC_MESSAGES/django.mo +0 -0
  230. modoboa/locale/tr/LC_MESSAGES/django.po +737 -315
  231. modoboa/locale/tr_TR/LC_MESSAGES/django.mo +0 -0
  232. modoboa/locale/tr_TR/LC_MESSAGES/django.po +711 -310
  233. modoboa/locale/uk/LC_MESSAGES/django.po +671 -309
  234. modoboa/locale/zh/LC_MESSAGES/django.po +668 -309
  235. modoboa/locale/zh_CN/LC_MESSAGES/django.po +668 -309
  236. modoboa/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  237. modoboa/locale/zh_TW/LC_MESSAGES/django.po +747 -316
  238. modoboa/maillog/jobs.py +11 -0
  239. modoboa/maillog/tests/test_views.py +5 -4
  240. modoboa/parameters/api/v2/tests.py +1 -1
  241. modoboa/policyd/management/commands/policy_daemon.py +5 -1
  242. modoboa/policyd/tests.py +4 -2
  243. modoboa/rspamd/tests.py +20 -0
  244. modoboa/templates/registration/twofactor_code_verify.html +1 -1
  245. modoboa/webmail/app_settings.py +37 -0
  246. modoboa/webmail/constants.py +21 -1
  247. modoboa/webmail/factories.py +19 -0
  248. modoboa/webmail/jobs.py +27 -0
  249. modoboa/webmail/lib/__init__.py +0 -2
  250. modoboa/webmail/lib/imapemail.py +9 -11
  251. modoboa/webmail/lib/imapheader.py +25 -13
  252. modoboa/webmail/lib/imaputils.py +51 -13
  253. modoboa/webmail/lib/sendmail.py +88 -105
  254. modoboa/webmail/lib/utils.py +109 -0
  255. modoboa/webmail/migrations/0001_initial.py +90 -0
  256. modoboa/webmail/migrations/__init__.py +0 -0
  257. modoboa/webmail/mocks.py +12 -3
  258. modoboa/webmail/models.py +102 -0
  259. modoboa/webmail/serializers.py +84 -4
  260. modoboa/webmail/tests/data.py +21 -0
  261. modoboa/webmail/tests/test_lib_imaputils.py +33 -0
  262. modoboa/webmail/tests/test_viewsets.py +108 -0
  263. modoboa/webmail/urls.py +5 -0
  264. modoboa/webmail/viewsets.py +39 -9
  265. {modoboa-2.6.4.dist-info → modoboa-2.7.0.dist-info}/METADATA +16 -13
  266. {modoboa-2.6.4.dist-info → modoboa-2.7.0.dist-info}/RECORD +271 -254
  267. {modoboa-2.6.4.dist-info → modoboa-2.7.0.dist-info}/WHEEL +1 -1
  268. modoboa/admin/management/commands/handle_mailbox_operations.py +0 -103
  269. modoboa/core/management/commands/cleanlogs.py +0 -53
  270. modoboa/frontend_dist/assets/AccountLayout-B2HptJzY.js +0 -1
  271. modoboa/frontend_dist/assets/AccountPasswordSubForm-DpADntAL.js +0 -1
  272. modoboa/frontend_dist/assets/AccountView-CJBGh9Oe.js +0 -1
  273. modoboa/frontend_dist/assets/AddressBook-CrFqzlzZ.js +0 -1
  274. modoboa/frontend_dist/assets/AdminLayout-BbCt7YxX.js +0 -1
  275. modoboa/frontend_dist/assets/AlarmsView-CIlt_IKQ.js +0 -1
  276. modoboa/frontend_dist/assets/AliasRecipientForm-jjj5yJJw.js +0 -1
  277. modoboa/frontend_dist/assets/AuditTrailView-sWnED3E9.js +0 -1
  278. modoboa/frontend_dist/assets/CalendarView-DSCBIvef.js +0 -1
  279. modoboa/frontend_dist/assets/ChoiceField-RDT6XcnX.js +0 -1
  280. modoboa/frontend_dist/assets/ComposeEmailForm-D3yUbBJX.css +0 -1
  281. modoboa/frontend_dist/assets/ComposeEmailForm-G3sXQAwI.js +0 -1
  282. modoboa/frontend_dist/assets/ComposeEmailView-Ce1CtFjD.js +0 -1
  283. modoboa/frontend_dist/assets/ConfirmDialog-Du7aqKbR.js +0 -1
  284. modoboa/frontend_dist/assets/ConnectedLayout-BHWaBAzH.js +0 -1
  285. modoboa/frontend_dist/assets/CreationForm-CjtILq0_.js +0 -1
  286. modoboa/frontend_dist/assets/DashboardView-DFNwusDZ.js +0 -1
  287. modoboa/frontend_dist/assets/DomainsView-CYxLy-3Y.js +0 -1
  288. modoboa/frontend_dist/assets/EmailField-DmPc4pyP.js +0 -1
  289. modoboa/frontend_dist/assets/EmailView-BE3vYZtJ.js +0 -1
  290. modoboa/frontend_dist/assets/EmptyLayout-DMwKNHfI.js +0 -1
  291. modoboa/frontend_dist/assets/FiltersView-Dps3_fAh.js +0 -1
  292. modoboa/frontend_dist/assets/ForwardEmailView-C0I00eXf.js +0 -1
  293. modoboa/frontend_dist/assets/IdentitiesView-CoGBVhZx.js +0 -1
  294. modoboa/frontend_dist/assets/InformationView-eB2ayeLD.js +0 -1
  295. modoboa/frontend_dist/assets/MailboxView-DIVABvk5.css +0 -1
  296. modoboa/frontend_dist/assets/MailboxView-DNokO2WZ.js +0 -5
  297. modoboa/frontend_dist/assets/MenuItems-dl38msRA.js +0 -1
  298. modoboa/frontend_dist/assets/MessagesView-BZ7NUOwo.js +0 -1
  299. modoboa/frontend_dist/assets/MigrationsView-DcOVgmcH.js +0 -1
  300. modoboa/frontend_dist/assets/ParametersView-B6abt-LH.js +0 -1
  301. modoboa/frontend_dist/assets/ParametersView-v0YS6WU4.js +0 -1
  302. modoboa/frontend_dist/assets/ProviderEditView-BaL9mKxJ.js +0 -1
  303. modoboa/frontend_dist/assets/ProviderGeneralForm-eoHMDO5v.js +0 -1
  304. modoboa/frontend_dist/assets/ProvidersView-Zu-G35u7.js +0 -1
  305. modoboa/frontend_dist/assets/QuarantineLayout-BHiiksnP.js +0 -1
  306. modoboa/frontend_dist/assets/QuarantineView-BOXSobRz.js +0 -1
  307. modoboa/frontend_dist/assets/ReplyEmailView-JmSiNWPy.js +0 -1
  308. modoboa/frontend_dist/assets/ResourcesForm-D0YBapCG.js +0 -1
  309. modoboa/frontend_dist/assets/SelfServiceLayout-DUmQzUDk.js +0 -1
  310. modoboa/frontend_dist/assets/SettingsView-DIvKGRBY.css +0 -1
  311. modoboa/frontend_dist/assets/SettingsView-DMuaKagM.js +0 -6
  312. modoboa/frontend_dist/assets/StatisticsView-CyY_CNcI.js +0 -1
  313. modoboa/frontend_dist/assets/TimeSerieChart-BTDBH33K.js +0 -1
  314. modoboa/frontend_dist/assets/TimeSerieChart-C3XHmlRd.css +0 -1
  315. modoboa/frontend_dist/assets/UserLayout-DwxcQr4L.js +0 -1
  316. modoboa/frontend_dist/assets/VApp-B0vRc3-6.js +0 -1
  317. modoboa/frontend_dist/assets/VAutocomplete-Huaz_-hf.js +0 -1
  318. modoboa/frontend_dist/assets/VAutocomplete-hzGuLlUI.css +0 -1
  319. modoboa/frontend_dist/assets/VAvatar-JdJfoK0C.js +0 -1
  320. modoboa/frontend_dist/assets/VBadge-Cg5iu5LG.js +0 -1
  321. modoboa/frontend_dist/assets/VCheckbox-CCLOJFKF.js +0 -1
  322. modoboa/frontend_dist/assets/VColorPicker-452uc3tY.js +0 -1
  323. modoboa/frontend_dist/assets/VDataTable-BkLLfcQK.js +0 -1
  324. modoboa/frontend_dist/assets/VDataTableServer-B_6fWBA3.js +0 -1
  325. modoboa/frontend_dist/assets/VDataTableVirtual-Ike7oqcC.js +0 -1
  326. modoboa/frontend_dist/assets/VDialog-DIUeAIpw.js +0 -1
  327. modoboa/frontend_dist/assets/VExpansionPanels-BeMgiS7A.js +0 -1
  328. modoboa/frontend_dist/assets/VFileInput-Dvj95FL5.js +0 -1
  329. modoboa/frontend_dist/assets/VForm-DkphQD3e.js +0 -1
  330. modoboa/frontend_dist/assets/VInput-CbqehdOu.js +0 -1
  331. modoboa/frontend_dist/assets/VMenu-B6SCtOwT.js +0 -1
  332. modoboa/frontend_dist/assets/VMenu-BEipA1lw.css +0 -1
  333. modoboa/frontend_dist/assets/VPicker-BUx70Wjg.js +0 -1
  334. modoboa/frontend_dist/assets/VPicker-ClSXs6kv.css +0 -1
  335. modoboa/frontend_dist/assets/VProgressCircular-DTDGzl2O.js +0 -1
  336. modoboa/frontend_dist/assets/VRadioGroup-CS9ULZ1c.js +0 -1
  337. modoboa/frontend_dist/assets/VSelect-D5IXDPEX.js +0 -1
  338. modoboa/frontend_dist/assets/VSheet-HeHw6g5_.js +0 -1
  339. modoboa/frontend_dist/assets/VSpacer-BUxSSJbH.js +0 -1
  340. modoboa/frontend_dist/assets/VSwitch-Dh_dserW.js +0 -1
  341. modoboa/frontend_dist/assets/VTabs-5XSICLQP.js +0 -1
  342. modoboa/frontend_dist/assets/VTabs-NzpINroH.css +0 -1
  343. modoboa/frontend_dist/assets/VTextField-Cow3HZvI.css +0 -1
  344. modoboa/frontend_dist/assets/VTextField-QXQFhKxm.js +0 -1
  345. modoboa/frontend_dist/assets/VTextarea-Bdy4UKmV.js +0 -1
  346. modoboa/frontend_dist/assets/VTextarea-DyGjqrlm.css +0 -1
  347. modoboa/frontend_dist/assets/VToolbar-CB2GrZpA.css +0 -1
  348. modoboa/frontend_dist/assets/VToolbar-CyrYSqJZ.js +0 -1
  349. modoboa/frontend_dist/assets/VWindowItem-kZpArwK1.js +0 -1
  350. modoboa/frontend_dist/assets/WebmailLayout-BzW0LWYp.css +0 -1
  351. modoboa/frontend_dist/assets/WebmailLayout-CNUHODnw.js +0 -1
  352. modoboa/frontend_dist/assets/domains.store-D4ZqCQ-O.js +0 -1
  353. modoboa/frontend_dist/assets/filter-DHKXX97M.js +0 -1
  354. modoboa/frontend_dist/assets/global.store-DYMmGKpn.js +0 -1
  355. modoboa/frontend_dist/assets/importExport-C-adpHJX.js +0 -1
  356. modoboa/frontend_dist/assets/index-B1bntsLR.js +0 -995
  357. modoboa/frontend_dist/assets/languages-CxjoT69j.js +0 -1
  358. modoboa/frontend_dist/assets/transports-ChdHV5hX.js +0 -1
  359. modoboa/frontend_dist/assets/webmail-B7MNMkv_.js +0 -1
  360. modoboa/frontend_dist/assets/webmail.store-Cm-h6hhE.js +0 -1
  361. {modoboa-2.6.4.data → modoboa-2.7.0.data}/scripts/modoboa-admin.py +0 -0
  362. {modoboa-2.6.4.dist-info → modoboa-2.7.0.dist-info}/entry_points.txt +0 -0
  363. {modoboa-2.6.4.dist-info → modoboa-2.7.0.dist-info}/licenses/LICENSE +0 -0
  364. {modoboa-2.6.4.dist-info → modoboa-2.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,11 @@
1
+ """Async jobs definition."""
2
+
3
+ from django.core.management import call_command
4
+
5
+
6
+ def logparser():
7
+ call_command("logparser")
8
+
9
+
10
+ def update_statistics():
11
+ call_command("update_statistics")
@@ -11,6 +11,7 @@ from django.test import override_settings
11
11
 
12
12
  from modoboa.admin import factories as admin_factories
13
13
  from modoboa.lib.tests import SimpleModoTestCase
14
+ from modoboa.maillog import jobs
14
15
 
15
16
 
16
17
  class RunCommandsMixin:
@@ -36,14 +37,14 @@ class RunCommandsMixin:
36
37
  with open(path, "w") as fp:
37
38
  fp.write(content)
38
39
  self.set_global_parameter("logfile", path)
39
- call_command("logparser")
40
+ jobs.logparser()
40
41
 
41
42
  def run_update_statistics(self, rebuild=False):
42
43
  """Run update_statistics command."""
43
- args = []
44
44
  if rebuild:
45
- args.append("--rebuild")
46
- call_command("update_statistics", *args)
45
+ call_command("update_statistics", ["--rebuild"])
46
+ else:
47
+ jobs.update_statistics()
47
48
 
48
49
 
49
50
  @override_settings(RRDTOOL_TEST_MODE=True)
@@ -18,7 +18,7 @@ class GlobalParametersAPITestCase(ModoAPITestCase):
18
18
  resp = self.client.get(url)
19
19
  self.assertEqual(resp.status_code, 200)
20
20
  # core, admin, limits, pdf credentials, ...
21
- self.assertEqual(len(resp.json()), 13)
21
+ self.assertEqual(len(resp.json()), 12)
22
22
 
23
23
  def test_get_structure(self):
24
24
  url = reverse("v2:parameter-global-structure")
@@ -31,7 +31,11 @@ class Command(BaseCommand):
31
31
 
32
32
  def handle(self, *args, **options):
33
33
  """Entry point."""
34
- loop = asyncio.get_event_loop()
34
+ try:
35
+ loop = asyncio.get_event_loop()
36
+ except RuntimeError:
37
+ loop = asyncio.new_event_loop()
38
+ asyncio.set_event_loop(loop)
35
39
  coro = asyncio.start_server(
36
40
  core.new_connection, options["host"], options["port"]
37
41
  )
modoboa/policyd/tests.py CHANGED
@@ -3,11 +3,11 @@
3
3
  import asyncio
4
4
  from aiosmtplib import send
5
5
  from unittest.mock import AsyncMock
6
+ import multiprocessing
6
7
  from multiprocessing import Process
7
8
  import socket
8
9
  import time
9
10
 
10
-
11
11
  from django import db
12
12
  from django.core.management import call_command
13
13
  from django.test import TransactionTestCase
@@ -21,6 +21,8 @@ from modoboa.policyd import core as policyd_core
21
21
 
22
22
  from . import constants
23
23
 
24
+ multiprocessing.set_start_method("fork")
25
+
24
26
 
25
27
  def start_policy_daemon():
26
28
  call_command("policy_daemon")
@@ -56,7 +58,7 @@ class PolicyDaemonTestCase(RedisTestCaseMixin, ParametersMixin, TransactionTestC
56
58
  self.process.daemon = True
57
59
  self.process.start()
58
60
  # Wait a bit for the daemon to start
59
- self.process.join(0.1)
61
+ self.process.join(1.0)
60
62
 
61
63
  def set_domain_limit(self, name, value):
62
64
  """Set daily limit for domain."""
modoboa/rspamd/tests.py CHANGED
@@ -2,12 +2,18 @@ import os
2
2
  import shutil
3
3
 
4
4
  from django.core import management
5
+ from django.test import modify_settings
5
6
  from django.urls import reverse
6
7
 
7
8
  from modoboa.admin import factories as admin_factories, models as admin_models
8
9
  from modoboa.lib.tests import ModoAPITestCase
9
10
 
10
11
 
12
+ @modify_settings(
13
+ INSTALLED_APPS={
14
+ "append": "modoboa.rspamd",
15
+ }
16
+ )
11
17
  class ManagementCommandTestCase(ModoAPITestCase):
12
18
 
13
19
  @classmethod
@@ -76,3 +82,17 @@ class ParametersAPITestCase(ModoAPITestCase):
76
82
  }
77
83
  response = self.client.put(url, data, format="json")
78
84
  self.assertEqual(response.status_code, 200)
85
+
86
+
87
+ @modify_settings(
88
+ MODOBOA_APPS={
89
+ "append": "modoboa.rspamd",
90
+ }
91
+ )
92
+ class CapabilitiesAPITestCase(ModoAPITestCase):
93
+
94
+ def test_get(self):
95
+ url = reverse("v2:capabilities")
96
+ response = self.client.get(url)
97
+ self.assertEqual(response.status_code, 200)
98
+ self.assertIn("rspamd", response.json()["capabilities"])
@@ -25,7 +25,7 @@
25
25
  <input type="hidden" id="id_next" name="next" value="{{ nextlocation }}">
26
26
  <div class="input-group{% if form.tfa_code.errors %} error{% endif %}">
27
27
  <label>{% translate "One-time password" %}</label>
28
- <input id="id_tfa_code" name="tfa_code" required>
28
+ <input id="id_tfa_code" name="tfa_code" required autofocus>
29
29
  <span class="error-message">
30
30
  {% for error in form.tfa_code.errors %}
31
31
  {{ error }}
@@ -107,6 +107,43 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict(
107
107
  ),
108
108
  },
109
109
  ),
110
+ (
111
+ "scheduling",
112
+ {
113
+ "label": gettext_lazy("Scheduling settings"),
114
+ "params": collections.OrderedDict(
115
+ [
116
+ (
117
+ "scheduling_smtp_server",
118
+ {
119
+ "label": gettext_lazy("Server address"),
120
+ "help_text": gettext_lazy(
121
+ "Address of your SMTP server"
122
+ ),
123
+ },
124
+ ),
125
+ (
126
+ "scheduling_smtp_secured_mode",
127
+ {
128
+ "label": gettext_lazy("Secured connection mode"),
129
+ "help_text": gettext_lazy(
130
+ "Use a secured connection to access SMTP server"
131
+ ),
132
+ },
133
+ ),
134
+ (
135
+ "scheduling_smtp_port",
136
+ {
137
+ "label": gettext_lazy("Server port"),
138
+ "help_text": gettext_lazy(
139
+ "Listening port of your SMTP server"
140
+ ),
141
+ },
142
+ ),
143
+ ]
144
+ ),
145
+ },
146
+ ),
110
147
  ]
111
148
  )
112
149
 
@@ -5,7 +5,27 @@ from enum import Enum
5
5
  from django.utils.translation import gettext_lazy as _
6
6
 
7
7
 
8
- MAILBOX_TYPES = ["inbox", "draft", "sent", "junk", "trash", "normal"]
8
+ MAILBOX_TYPES = ["inbox", "draft", "sent", "scheduled", "junk", "trash", "normal"]
9
+
10
+ MAILBOX_NAME_SCHEDULED = "Scheduled"
11
+
12
+ CUSTOM_HEADER_SCHEDULED_ID = "X-Scheduled-ID"
13
+ CUSTOM_HEADER_SCHEDULED_DATETIME = "X-Scheduled-Datetime"
14
+
15
+
16
+ class SchedulingState(Enum):
17
+ SCHEDULED = "scheduled"
18
+ SENDING = "sending"
19
+ SEND_ERROR = "send_error"
20
+ MOVE_ERROR = "move_error"
21
+
22
+
23
+ EMAIL_SCHEDULING_STATES = (
24
+ (SchedulingState.SCHEDULED.value, "scheduled"),
25
+ (SchedulingState.SENDING.value, "sending"),
26
+ (SchedulingState.SEND_ERROR.value, "send_error"),
27
+ (SchedulingState.MOVE_ERROR.value, "move_error"),
28
+ )
9
29
 
10
30
 
11
31
  class SmtpConnectionMode(Enum):
@@ -0,0 +1,19 @@
1
+ """Webmail factories."""
2
+
3
+ import factory
4
+
5
+ from modoboa.admin import factories as admin_factories
6
+
7
+ from modoboa.webmail import models
8
+
9
+
10
+ class ScheduledMessageFactory(factory.django.DjangoModelFactory):
11
+ """Scheduled message factory."""
12
+
13
+ class Meta:
14
+ model = models.ScheduledMessage
15
+
16
+ account = factory.SubFactory(admin_factories.UserFactory)
17
+ sender = "sender@test.com"
18
+ subject = "Scheduled Message"
19
+ to = "recipient@domain.test"
@@ -0,0 +1,27 @@
1
+ """Async (RQ) jobs definition."""
2
+
3
+ from django.utils import timezone
4
+
5
+ import django_rq
6
+
7
+ from modoboa.webmail import constants, models
8
+ from modoboa.webmail.lib import sendmail
9
+
10
+
11
+ def send_scheduled_message(message_id: int):
12
+ message = models.ScheduledMessage.objects.get(id=message_id)
13
+ if sendmail.send_scheduled_message(message):
14
+ message.delete()
15
+
16
+
17
+ def send_scheduled_messages():
18
+ now = timezone.now().replace(second=0, microsecond=0)
19
+ messages = models.ScheduledMessage.objects.filter(
20
+ scheduled_datetime__lte=now,
21
+ status=constants.SchedulingState.SCHEDULED.value,
22
+ )
23
+ queue = django_rq.get_queue("modoboa")
24
+ for message in messages:
25
+ message.status = constants.SchedulingState.SENDING.value
26
+ message.save()
27
+ queue.enqueue(send_scheduled_message, message_id=message.id)
@@ -7,7 +7,6 @@ from .imapemail import ImapEmail, ReplyModifier, ForwardModifier
7
7
  from .imaputils import BodyStructure, IMAPconnector, get_imapconnector, separate_mailbox
8
8
  from .signature import EmailSignature
9
9
  from .utils import decode_payload
10
- from .sendmail import send_mail
11
10
 
12
11
 
13
12
  __all__ = [
@@ -22,6 +21,5 @@ __all__ = [
22
21
  "decode_payload",
23
22
  "get_imapconnector",
24
23
  "save_attachment",
25
- "send_mail",
26
24
  "separate_mailbox",
27
25
  ]
@@ -18,6 +18,7 @@ from django.utils.translation import gettext as _
18
18
  from modoboa.contacts import models as contacts_models
19
19
  from modoboa.lib import u2u_decode
20
20
  from modoboa.lib.email_utils import Email
21
+ from modoboa.webmail import constants
21
22
 
22
23
  from . import imapheader
23
24
  from .attachments import get_storage_path
@@ -45,19 +46,24 @@ class ImapEmail(Email):
45
46
  self.imapc = self.imapc.__enter__()
46
47
  self.mbox, self.mailid = self.mailid.split(":")
47
48
  self.attachments: dict[str, str] = {}
49
+ self.To: list = []
48
50
 
49
51
  def __del__(self):
50
52
  self.imapc.__exit__()
51
53
 
52
54
  def fetch_headers(self, raw_addresses: bool = False) -> None:
53
55
  """Fetch message headers from server."""
56
+ requested_headers = self.headernames
57
+ if self.mbox == constants.MAILBOX_NAME_SCHEDULED:
58
+ requested_headers += [(constants.CUSTOM_HEADER_SCHEDULED_DATETIME, True)]
59
+ header_names = [header[0].upper() for header in requested_headers]
54
60
  msg = self.imapc.fetchmail(
55
- self.mbox, self.mailid, readonly=False, what=" ".join(self.headers_as_list)
61
+ self.mbox, self.mailid, readonly=False, what=" ".join(header_names)
56
62
  )
57
- headers = msg[f"BODY[HEADER.FIELDS ({self.headers_as_text})]"]
63
+ headers = msg[f"BODY[HEADER.FIELDS ({' '.join(header_names)})]"]
58
64
  self.fetch_body_structure(msg)
59
65
  msg = email.message_from_string(headers)
60
- for hdr in self.headernames:
66
+ for hdr in requested_headers:
61
67
  label = hdr[0]
62
68
  hdrvalue = self.get_header(msg, label, raw=raw_addresses)
63
69
  if not hdrvalue:
@@ -110,14 +116,6 @@ class ImapEmail(Email):
110
116
  self.dformat if self.dformat in self.bs.contents else fallback_fmt
111
117
  )
112
118
 
113
- @property
114
- def headers_as_list(self):
115
- return [hdr[0].upper() for hdr in self.headernames]
116
-
117
- @property
118
- def headers_as_text(self):
119
- return " ".join(self.headers_as_list)
120
-
121
119
  @property
122
120
  def subject(self):
123
121
  """Just a shortcut to return a subject in any case."""
@@ -27,19 +27,19 @@ __all__ = [
27
27
  # according to https://en.wikipedia.org/wiki/Date_format_by_country
28
28
  # and https://en.wikipedia.org/wiki/Date_and_time_representation_by_country
29
29
  DATETIME_FORMATS = {
30
- "cs": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
31
- "de": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
32
- "en": {"SHORT": "l, P", "LONG": "N j, Y P"},
33
- "es": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
34
- "fr": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
35
- "it": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
36
- "ja_JP": {"SHORT": "l, P", "LONG": "N j, Y P"},
37
- "nl": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
38
- "pl_PL": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
39
- "pt_PT": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
40
- "pt_BR": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
41
- "ru": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
42
- "sv": {"SHORT": "l, H:i", "LONG": "d. N Y H:i"},
30
+ "cs": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
31
+ "de": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
32
+ "en": {"SHORT": "l, P", "LONG": "N j, Y P", "TIME": "H:i"},
33
+ "es": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
34
+ "fr": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
35
+ "it": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
36
+ "ja_JP": {"SHORT": "l, P", "LONG": "N j, Y P", "TIME": "H:i"},
37
+ "nl": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
38
+ "pl_PL": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
39
+ "pt_PT": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
40
+ "pt_BR": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
41
+ "ru": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
42
+ "sv": {"SHORT": "l, H:i", "LONG": "d. N Y H:i", "TIME": "H:i"},
43
43
  }
44
44
 
45
45
 
@@ -123,6 +123,18 @@ def parse_date(value, **kwargs):
123
123
  )
124
124
 
125
125
 
126
+ def parse_scheduled_datetime(value: str, **kwargs) -> str:
127
+ result = datetime.datetime.fromisoformat(value)
128
+ current_language = get_request().user.language
129
+ if timezone.now().date().day != result.date().day:
130
+ fmt = "LONG"
131
+ else:
132
+ fmt = "TIME"
133
+ return date_format(
134
+ result, DATETIME_FORMATS.get(current_language, DATETIME_FORMATS.get("en"))[fmt]
135
+ )
136
+
137
+
126
138
  def parse_message_id(value, **kwargs):
127
139
  """Parse a Message-ID: header."""
128
140
  return value.strip("\n")
@@ -13,6 +13,7 @@ from modoboa.lib import imap_utf7 # noqa
13
13
  from modoboa.lib import oauth2
14
14
  from modoboa.lib.exceptions import InternalError
15
15
  from modoboa.parameters import tools as param_tools
16
+ from modoboa.webmail import constants
16
17
 
17
18
  from ..exceptions import ImapError, WebmailInternalError
18
19
  from .fetch_parser import FetchResponseParser
@@ -292,16 +293,21 @@ class IMAPconnector:
292
293
  return f"OR {old} {c}"
293
294
 
294
295
  if criterion == "both":
295
- criterion = "from_addr, subject"
296
- criterions = ""
297
- for c in criterion.split(","):
298
- if c == "from_addr":
299
- key = "FROM"
300
- elif c == "subject":
301
- key = "SUBJECT"
302
- else:
303
- continue
304
- criterions = or_criterion(criterions, f'({key} "{pattern}")')
296
+ criterion = "from_addr,subject"
297
+
298
+ if not pattern:
299
+ criterions = "ALL"
300
+ else:
301
+ criterions = ""
302
+ for c in criterion.split(","):
303
+ if c == "from_addr":
304
+ key = "FROM"
305
+ elif c == "subject":
306
+ key = "SUBJECT"
307
+ else:
308
+ continue
309
+ criterions = or_criterion(criterions, f'({key} "{pattern}")')
310
+
305
311
  self.criterions = [bytearray(criterions, "utf8")]
306
312
 
307
313
  def messages_count(self, **kwargs) -> int:
@@ -485,6 +491,16 @@ class IMAPconnector:
485
491
  "type": "sent",
486
492
  "label": _("Sent"),
487
493
  },
494
+ ]
495
+ if user.scheduledmessage_set.exists():
496
+ md_mailboxes += [
497
+ {
498
+ "name": constants.MAILBOX_NAME_SCHEDULED,
499
+ "type": "scheduled",
500
+ "label": _("Scheduled"),
501
+ },
502
+ ]
503
+ md_mailboxes += [
488
504
  {
489
505
  "name": user.parameters.get_value("trash_folder"),
490
506
  "type": "trash",
@@ -576,10 +592,30 @@ class IMAPconnector:
576
592
  self._cmd("COPY", msgset, self._encode_mbox_name(newmailbox))
577
593
  self._cmd("STORE", msgset, "+FLAGS", r"(\Deleted \Seen)")
578
594
 
579
- def push_mail(self, mbox: str, msg):
595
+ def push_mail(self, mbox: str, msg) -> int:
596
+ """
597
+ Append a new message to a mailbox.
598
+
599
+ Return the UID of the created message.
600
+ """
580
601
  now = imaplib.Time2Internaldate(time.time())
581
602
  msg = bytes(msg)
582
- return self.m.append(self._encode_mbox_name(mbox), r"(\Seen)", now, msg)
603
+ typ, data = self.m.append(self._encode_mbox_name(mbox), r"(\Seen)", now, msg)
604
+ response = data[0].decode()
605
+ m = re.match(r"\[APPENDUID \d+ (\d+)\].+", response)
606
+ if m:
607
+ return int(m.group(1))
608
+ raise WebmailInternalError("Failed to retrieve message UID")
609
+
610
+ def delete_mail(self, mbox: str, uid: int) -> None:
611
+ """
612
+ Delete given message (set \Deleted flag) and expunge mailbox.
613
+
614
+ Do not use directly.
615
+ """
616
+ self.select_mailbox(mbox, False)
617
+ self._cmd("STORE", f"{uid}".encode(), "+FLAGS", r"(\Deleted)")
618
+ self._cmd("EXPUNGE")
583
619
 
584
620
  def empty(self, mbox: str):
585
621
  self.select_mailbox(mbox, False)
@@ -605,7 +641,7 @@ class IMAPconnector:
605
641
  name = f"{parent}{self.hdelimiter}{name}"
606
642
  typ, data = self.m.create(self._encode_mbox_name(name))
607
643
  if typ == "NO":
608
- raise WebmailInternalError(data[0])
644
+ raise WebmailInternalError(str(data[0]))
609
645
  return True
610
646
 
611
647
  def rename_folder(self, oldname: str, newname: str) -> bool:
@@ -694,6 +730,8 @@ class IMAPconnector:
694
730
  submessages = [start]
695
731
  mrange = str(start)
696
732
  headers = "DATE FROM TO CC SUBJECT"
733
+ if mbox == constants.MAILBOX_NAME_SCHEDULED:
734
+ headers += " X-SCHEDULED-ID X-SCHEDULED-DATETIME"
697
735
  query = (
698
736
  f"(FLAGS BODYSTRUCTURE RFC822.SIZE BODY.PEEK[HEADER.FIELDS ({headers})])"
699
737
  )