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.
Files changed (359) 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/calendars/backends/caldav_.py +17 -13
  16. modoboa/calendars/jobs.py +7 -0
  17. modoboa/calendars/mocks.py +4 -1
  18. modoboa/calendars/serializers.py +14 -9
  19. modoboa/calendars/tests.py +10 -9
  20. modoboa/calendars/viewsets.py +3 -1
  21. modoboa/contacts/migrations/0008_addressbook_syncing.py +18 -0
  22. modoboa/contacts/models.py +1 -0
  23. modoboa/contacts/serializers.py +5 -2
  24. modoboa/contacts/tasks.py +9 -3
  25. modoboa/contacts/tests.py +32 -6
  26. modoboa/contacts/viewsets.py +7 -1
  27. modoboa/core/api/v2/serializers.py +0 -7
  28. modoboa/core/api/v2/tests.py +0 -10
  29. modoboa/core/app_settings.py +0 -22
  30. modoboa/core/commands/deploy.py +13 -0
  31. modoboa/core/commands/templates/cron_config.py.tpl +33 -0
  32. modoboa/core/commands/templates/settings.py.tpl +21 -0
  33. modoboa/core/jobs.py +34 -0
  34. modoboa/core/management/commands/load_initial_data.py +1 -1
  35. modoboa/core/password_hashers/base.py +4 -1
  36. modoboa/core/tests/test_core.py +0 -14
  37. modoboa/core/tests/test_jobs.py +40 -0
  38. modoboa/frontend_dist/assets/{AccountAliasForm-B-hTKSFr.js → AccountAliasForm-DO6DwfjE.js} +1 -1
  39. modoboa/frontend_dist/assets/{AccountEditView-LkP_qhf_.js → AccountEditView-CCuN9mGB.js} +1 -1
  40. modoboa/frontend_dist/assets/AccountLayout-Ge7fzuZg.js +1 -0
  41. modoboa/frontend_dist/assets/AccountPasswordSubForm-PkFPblkR.js +1 -0
  42. modoboa/frontend_dist/assets/AccountView-dgseekZ8.js +1 -0
  43. modoboa/frontend_dist/assets/AddressBook-DZWOHOJj.js +1 -0
  44. modoboa/frontend_dist/assets/AdminLayout-itB_mmH_.js +1 -0
  45. modoboa/frontend_dist/assets/AlarmsView-King6zb6.js +1 -0
  46. modoboa/frontend_dist/assets/{AliasEditView-BVjNJekt.js → AliasEditView-CTjrXYPf.js} +1 -1
  47. modoboa/frontend_dist/assets/AliasRecipientForm-CI7bXqVp.js +1 -0
  48. modoboa/frontend_dist/assets/{AliasView-DvvI6cV9.js → AliasView-BN13MNN_.js} +1 -1
  49. modoboa/frontend_dist/assets/AuditTrailView-C1jQwgoo.js +1 -0
  50. modoboa/frontend_dist/assets/CalendarView-juHVIHU5.css +1 -0
  51. modoboa/frontend_dist/assets/CalendarView-rRSzqxrH.js +1 -0
  52. modoboa/frontend_dist/assets/ChoiceField-DCr12shR.js +1 -0
  53. modoboa/frontend_dist/assets/ComposeEmailForm-BmtKwFb1.js +2 -0
  54. modoboa/frontend_dist/assets/ComposeEmailForm-CVNDl-Mq.css +1 -0
  55. modoboa/frontend_dist/assets/ComposeEmailView-JW22Phrb.js +1 -0
  56. modoboa/frontend_dist/assets/ConfirmDialog-CWpdwSQ6.js +1 -0
  57. modoboa/frontend_dist/assets/ConnectedLayout-BP8pO27H.js +1 -0
  58. modoboa/frontend_dist/assets/{ConnectedLayout-C6HNXWkp.css → ConnectedLayout-Ddpb_6yT.css} +1 -1
  59. modoboa/frontend_dist/assets/CreationForm-CzelJsVQ.js +1 -0
  60. modoboa/frontend_dist/assets/DashboardView-BBkBodVj.js +1 -0
  61. modoboa/frontend_dist/assets/{DomainAdminList-BO8_4Cwt.js → DomainAdminList-DD7p3i6F.js} +1 -1
  62. modoboa/frontend_dist/assets/{DomainEditView-BXylM4aB.js → DomainEditView-DoIoNYC4.js} +1 -1
  63. modoboa/frontend_dist/assets/{DomainTransportForm-Ccw5BXyM.js → DomainTransportForm-CbiJF9z5.js} +1 -1
  64. modoboa/frontend_dist/assets/{DomainView-Bd5XQI-1.js → DomainView-BXvznFYz.js} +3 -3
  65. modoboa/frontend_dist/assets/DomainsView-ffYjiffp.js +1 -0
  66. modoboa/frontend_dist/assets/EmailField-KhhJYA4D.js +1 -0
  67. modoboa/frontend_dist/assets/EmailSchedulingForm-CQL5Vfdr.js +1 -0
  68. modoboa/frontend_dist/assets/EmailView-Bq2bHZBO.js +1 -0
  69. modoboa/frontend_dist/assets/EmptyLayout-NrTftp18.js +1 -0
  70. modoboa/frontend_dist/assets/FiltersView-SOQy0U_3.js +1 -0
  71. modoboa/frontend_dist/assets/ForwardEmailView-kEPDjWUw.js +1 -0
  72. modoboa/frontend_dist/assets/{HtmlEditor-DNn3CY-w.js → HtmlEditor-CyBl5wj2.js} +15 -15
  73. modoboa/frontend_dist/assets/IdentitiesView-CZFf4oR9.js +1 -0
  74. modoboa/frontend_dist/assets/InformationView-CAtneAqM.js +1 -0
  75. modoboa/frontend_dist/assets/{LoadingData-CnQ_aVqa.js → LoadingData-C2txD49L.js} +1 -1
  76. modoboa/frontend_dist/assets/{LoginCallbackView-Nh_gmh8e.js → LoginCallbackView-DAvty3CI.js} +1 -1
  77. modoboa/frontend_dist/assets/{LoginView-BtC9BHuJ.js → LoginView-DHYITkn4.js} +1 -1
  78. modoboa/frontend_dist/assets/MailboxView-BNg2v7mi.css +1 -0
  79. modoboa/frontend_dist/assets/MailboxView-DHg5A78D.js +5 -0
  80. modoboa/frontend_dist/assets/MenuItems-C9p70bSC.js +1 -0
  81. modoboa/frontend_dist/assets/{MessageView-C1Goz-hY.js → MessageView-CrrEJpNI.js} +1 -1
  82. modoboa/frontend_dist/assets/MessagesView-CyecRd4I.js +1 -0
  83. modoboa/frontend_dist/assets/MigrationsView-DXGKKz2h.js +1 -0
  84. modoboa/frontend_dist/assets/{ParametersForm-BegEnkQM.js → ParametersForm-DFKYkPAs.js} +1 -1
  85. modoboa/frontend_dist/assets/ParametersView-B1wT5oyt.js +1 -0
  86. modoboa/frontend_dist/assets/ParametersView-CfvVzKea.js +1 -0
  87. modoboa/frontend_dist/assets/ProviderEditView-De-8ggBp.js +1 -0
  88. modoboa/frontend_dist/assets/{ProviderGeneralForm-BAELnXh6.js → ProviderGeneralForm-DO-IpJMQ.js} +1 -1
  89. modoboa/frontend_dist/assets/ProvidersView-Bhk7sCz4.js +1 -0
  90. modoboa/frontend_dist/assets/QuarantineLayout-C1YszNmP.js +1 -0
  91. modoboa/frontend_dist/assets/QuarantineView-cElD_rS-.js +1 -0
  92. modoboa/frontend_dist/assets/ReplyEmailView-BvuOZdLl.js +1 -0
  93. modoboa/frontend_dist/assets/ResourcesForm-FWxrmVwo.js +1 -0
  94. modoboa/frontend_dist/assets/SelfServiceLayout-JHRvfvQf.js +1 -0
  95. modoboa/frontend_dist/assets/SettingsView-SIsKAXtQ.css +1 -0
  96. modoboa/frontend_dist/assets/SettingsView-s2l2Xl1L.js +6 -0
  97. modoboa/frontend_dist/assets/StatisticsView-FiidWvad.js +1 -0
  98. modoboa/frontend_dist/assets/TimeSerieChart-CdqUHiO_.js +1 -0
  99. modoboa/frontend_dist/assets/TimeSerieChart-nLIFGI0y.css +1 -0
  100. modoboa/frontend_dist/assets/UserLayout-DyvI8duf.js +1 -0
  101. modoboa/frontend_dist/assets/{VAlert-Buv8Z5G4.js → VAlert-MDbeolOo.js} +1 -1
  102. modoboa/frontend_dist/assets/VApp-DM1KLQfQ.js +1 -0
  103. modoboa/frontend_dist/assets/VAutocomplete-C9NL5_uo.css +1 -0
  104. modoboa/frontend_dist/assets/VAutocomplete-CQsWYWNX.js +1 -0
  105. modoboa/frontend_dist/assets/VAvatar-CXV_FqpP.js +1 -0
  106. modoboa/frontend_dist/assets/VBadge-7tDx7aI3.js +1 -0
  107. modoboa/frontend_dist/assets/{VCard-D7AwBs4w.js → VCard-CBtX8JF-.js} +1 -1
  108. modoboa/frontend_dist/assets/VCheckbox-zY1MApOy.js +1 -0
  109. modoboa/frontend_dist/assets/{VCheckboxBtn-B5evxN9K.js → VCheckboxBtn-B0mIT3E0.js} +1 -1
  110. modoboa/frontend_dist/assets/VColorPicker-BbCHvk6K.js +1 -0
  111. modoboa/frontend_dist/assets/{VColorPicker-B_lVDaYR.css → VColorPicker-C9m8L-6U.css} +1 -1
  112. modoboa/frontend_dist/assets/{VContainer-DY9021j4.js → VContainer-Cn-vKB3s.js} +1 -1
  113. modoboa/frontend_dist/assets/VDataTable-CNT9KOSp.js +1 -0
  114. modoboa/frontend_dist/assets/VDataTableServer-_yn4Ry6U.js +1 -0
  115. modoboa/frontend_dist/assets/VDataTableVirtual-Csxh3Gp6.js +1 -0
  116. modoboa/frontend_dist/assets/VDatePicker-_OUDqShN.css +1 -0
  117. modoboa/frontend_dist/assets/VDatePicker-iFu13xIP.js +2 -0
  118. modoboa/frontend_dist/assets/VDialog-lKtBdqnp.js +1 -0
  119. modoboa/frontend_dist/assets/VExpansionPanels-CaIvk9iF.js +1 -0
  120. modoboa/frontend_dist/assets/VFileInput-C7J_qVmk.js +1 -0
  121. modoboa/frontend_dist/assets/VForm-fdQ5d-CH.js +1 -0
  122. modoboa/frontend_dist/assets/VInput-BwHvhzAe.js +1 -0
  123. modoboa/frontend_dist/assets/VMenu-BpmJf4X2.js +1 -0
  124. modoboa/frontend_dist/assets/VMenu-C5A_5Hs5.css +1 -0
  125. modoboa/frontend_dist/assets/VPicker-B7cB3kJg.css +1 -0
  126. modoboa/frontend_dist/assets/VPicker-BZho70wU.js +1 -0
  127. modoboa/frontend_dist/assets/VProgressCircular-DRw_-iNj.js +1 -0
  128. modoboa/frontend_dist/assets/VRadioGroup--eiP5xtJ.js +1 -0
  129. modoboa/frontend_dist/assets/{VRow-DyK_Mj7R.js → VRow-83Qnr5iB.js} +1 -1
  130. modoboa/frontend_dist/assets/VSelect-BcmGFGif.js +1 -0
  131. modoboa/frontend_dist/assets/{VSelectionControl-Dkms9_P5.js → VSelectionControl-Cqi1xt-q.js} +1 -1
  132. modoboa/frontend_dist/assets/VSheet-C3MaHhtw.js +1 -0
  133. modoboa/frontend_dist/assets/VSpacer-CuSkdJZL.js +1 -0
  134. modoboa/frontend_dist/assets/VSwitch-XumUl685.js +1 -0
  135. modoboa/frontend_dist/assets/{VTable-D8ZwwC_B.js → VTable-f7wcr2AZ.js} +1 -1
  136. modoboa/frontend_dist/assets/VTabs-CDfdejXj.css +1 -0
  137. modoboa/frontend_dist/assets/VTabs-y4wNP4im.js +1 -0
  138. modoboa/frontend_dist/assets/VTextField-BPIvtrn4.js +1 -0
  139. modoboa/frontend_dist/assets/VTextField-DjbYGlzs.css +1 -0
  140. modoboa/frontend_dist/assets/VTextarea-B6bGYcC3.js +1 -0
  141. modoboa/frontend_dist/assets/VTextarea-f6vTjzFy.css +1 -0
  142. modoboa/frontend_dist/assets/VToolbar-BYDPtwf0.css +1 -0
  143. modoboa/frontend_dist/assets/VToolbar-CB6wwtYc.js +1 -0
  144. modoboa/frontend_dist/assets/VWindowItem-CDtLLEkg.js +1 -0
  145. modoboa/frontend_dist/assets/WebmailLayout-CQQAolnl.css +1 -0
  146. modoboa/frontend_dist/assets/WebmailLayout-tosQSHLS.js +1 -0
  147. modoboa/frontend_dist/assets/{accounts-BZBqbAmd.js → accounts-KUsk6LHW.js} +1 -1
  148. modoboa/frontend_dist/assets/{admin-uCrKjpln.js → admin-BW9cZW0P.js} +1 -1
  149. modoboa/frontend_dist/assets/{aliases-CFwFkX45.js → aliases-Ge0hjIsH.js} +1 -1
  150. modoboa/frontend_dist/assets/{amavis-DCy0U0TD.js → amavis-BbFeFfsk.js} +1 -1
  151. modoboa/frontend_dist/assets/{amavis-B4ScfQsG.js → amavis-DtuzP_CS.js} +1 -1
  152. modoboa/frontend_dist/assets/{contacts-DKmh2q-4.js → contacts-DMJlQTe0.js} +1 -1
  153. modoboa/frontend_dist/assets/{domains-CDE4_LDF.js → domains-Du64lcXT.js} +1 -1
  154. modoboa/frontend_dist/assets/{domains.store-a9Y5UlDV.js → domains.store-1U61jeCV.js} +1 -1
  155. modoboa/frontend_dist/assets/events-BM3in65C.js +1 -0
  156. modoboa/frontend_dist/assets/filter-Dihm6o59.js +1 -0
  157. modoboa/frontend_dist/assets/importExport-HGcNGWOm.js +1 -0
  158. modoboa/frontend_dist/assets/{index-B9q1vO3K.css → index-B1EK3MQe.css} +1 -1
  159. modoboa/frontend_dist/assets/{index-Dwg6jPTX.js → index-Dv00bmw9.js} +1 -1
  160. modoboa/frontend_dist/assets/{index-BjibLozh.js → index-jui3edpn.js} +47 -41
  161. modoboa/frontend_dist/assets/{language.store-DGuI8jG0.js → language.store-OcfdXL_-.js} +1 -1
  162. modoboa/frontend_dist/assets/languages-CF8hxo7x.js +1 -0
  163. modoboa/frontend_dist/assets/{layout-5PvsmkGR.js → layout-DOO7TRTJ.js} +1 -1
  164. modoboa/frontend_dist/assets/{layout.store-DJVcXtxk.js → layout.store-C0g-piJn.js} +1 -1
  165. modoboa/frontend_dist/assets/{logos-CMmd0hOG.js → logos-Dz2Gzei-.js} +1 -1
  166. modoboa/frontend_dist/assets/{logs-D2GOydPZ.js → logs-CLm32Weu.js} +1 -1
  167. modoboa/frontend_dist/assets/{parameters-DJdlq9sh.js → parameters-DMIAQ7cd.js} +1 -1
  168. modoboa/frontend_dist/assets/{parameters.store-DH3pPlaT.js → parameters.store-DLnFzCwV.js} +1 -1
  169. modoboa/frontend_dist/assets/{permissions-CgFJFhCp.js → permissions-CrpE0b4w.js} +1 -1
  170. modoboa/frontend_dist/assets/{ssrBoot-ChTB-PYP.js → ssrBoot-B7cr7q9U.js} +1 -1
  171. modoboa/frontend_dist/assets/{tag-CvrHE14f.js → tag-WF93n81Q.js} +1 -1
  172. modoboa/frontend_dist/assets/{theme-DFzlhSh5.js → theme-_0oOYChG.js} +1 -1
  173. modoboa/frontend_dist/assets/transports-CS61syt-.js +1 -0
  174. modoboa/frontend_dist/assets/webmail-CYDXU0DS.js +1 -0
  175. modoboa/frontend_dist/assets/webmail.store-BvHCQSjM.js +1 -0
  176. modoboa/frontend_dist/index.html +2 -2
  177. modoboa/imap_migration/models.py +3 -3
  178. modoboa/imap_migration/templates/imap_migration/offlineimap.conf +2 -2
  179. modoboa/imap_migration/tests.py +21 -0
  180. modoboa/lib/cryptutils.py +2 -2
  181. modoboa/lib/sysutils.py +1 -1
  182. modoboa/lib/tests/__init__.py +0 -1
  183. modoboa/locale/br/LC_MESSAGES/django.mo +0 -0
  184. modoboa/locale/br/LC_MESSAGES/django.po +301 -273
  185. modoboa/locale/cs/LC_MESSAGES/django.po +286 -269
  186. modoboa/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  187. modoboa/locale/cs_CZ/LC_MESSAGES/django.po +303 -275
  188. modoboa/locale/de/LC_MESSAGES/django.mo +0 -0
  189. modoboa/locale/de/LC_MESSAGES/django.po +297 -269
  190. modoboa/locale/de_DE/LC_MESSAGES/django.mo +0 -0
  191. modoboa/locale/de_DE/LC_MESSAGES/django.po +303 -275
  192. modoboa/locale/el_GR/LC_MESSAGES/django.mo +0 -0
  193. modoboa/locale/el_GR/LC_MESSAGES/django.po +303 -275
  194. modoboa/locale/en/LC_MESSAGES/django.po +286 -269
  195. modoboa/locale/es/LC_MESSAGES/django.mo +0 -0
  196. modoboa/locale/es/LC_MESSAGES/django.po +302 -274
  197. modoboa/locale/es_MX/LC_MESSAGES/django.po +286 -269
  198. modoboa/locale/fi/LC_MESSAGES/django.mo +0 -0
  199. modoboa/locale/fi/LC_MESSAGES/django.po +299 -271
  200. modoboa/locale/fr/LC_MESSAGES/django.mo +0 -0
  201. modoboa/locale/fr/LC_MESSAGES/django.po +288 -271
  202. modoboa/locale/hu/LC_MESSAGES/django.po +286 -269
  203. modoboa/locale/it/LC_MESSAGES/django.mo +0 -0
  204. modoboa/locale/it/LC_MESSAGES/django.po +303 -275
  205. modoboa/locale/ja_JP/LC_MESSAGES/django.mo +0 -0
  206. modoboa/locale/ja_JP/LC_MESSAGES/django.po +390 -413
  207. modoboa/locale/ka/LC_MESSAGES/django.po +286 -269
  208. modoboa/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
  209. modoboa/locale/nl_NL/LC_MESSAGES/django.po +299 -271
  210. modoboa/locale/no/LC_MESSAGES/django.po +286 -269
  211. modoboa/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
  212. modoboa/locale/pl_PL/LC_MESSAGES/django.po +297 -269
  213. modoboa/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
  214. modoboa/locale/pt_BR/LC_MESSAGES/django.po +298 -270
  215. modoboa/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
  216. modoboa/locale/pt_PT/LC_MESSAGES/django.po +303 -275
  217. modoboa/locale/ro_RO/LC_MESSAGES/django.mo +0 -0
  218. modoboa/locale/ro_RO/LC_MESSAGES/django.po +299 -271
  219. modoboa/locale/ru/LC_MESSAGES/django.mo +0 -0
  220. modoboa/locale/ru/LC_MESSAGES/django.po +993 -1096
  221. modoboa/locale/si/LC_MESSAGES/django.po +286 -269
  222. modoboa/locale/sk/LC_MESSAGES/django.po +286 -269
  223. modoboa/locale/sk_SK/LC_MESSAGES/django.po +286 -269
  224. modoboa/locale/sl_SI/LC_MESSAGES/django.po +288 -269
  225. modoboa/locale/sv/LC_MESSAGES/django.mo +0 -0
  226. modoboa/locale/sv/LC_MESSAGES/django.po +297 -269
  227. modoboa/locale/tr/LC_MESSAGES/django.mo +0 -0
  228. modoboa/locale/tr/LC_MESSAGES/django.po +301 -272
  229. modoboa/locale/tr_TR/LC_MESSAGES/django.mo +0 -0
  230. modoboa/locale/tr_TR/LC_MESSAGES/django.po +291 -269
  231. modoboa/locale/uk/LC_MESSAGES/django.po +286 -269
  232. modoboa/locale/zh/LC_MESSAGES/django.po +286 -269
  233. modoboa/locale/zh_CN/LC_MESSAGES/django.po +286 -269
  234. modoboa/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  235. modoboa/locale/zh_TW/LC_MESSAGES/django.po +295 -273
  236. modoboa/maillog/jobs.py +11 -0
  237. modoboa/maillog/tests/test_views.py +5 -4
  238. modoboa/parameters/api/v2/tests.py +1 -1
  239. modoboa/policyd/management/commands/policy_daemon.py +5 -1
  240. modoboa/policyd/tests.py +4 -2
  241. modoboa/rspamd/tests.py +20 -0
  242. modoboa/templates/registration/twofactor_code_verify.html +1 -1
  243. modoboa/webmail/app_settings.py +37 -0
  244. modoboa/webmail/constants.py +21 -1
  245. modoboa/webmail/factories.py +19 -0
  246. modoboa/webmail/jobs.py +27 -0
  247. modoboa/webmail/lib/__init__.py +0 -2
  248. modoboa/webmail/lib/imapemail.py +9 -11
  249. modoboa/webmail/lib/imapheader.py +25 -13
  250. modoboa/webmail/lib/imaputils.py +51 -13
  251. modoboa/webmail/lib/sendmail.py +88 -105
  252. modoboa/webmail/lib/utils.py +109 -0
  253. modoboa/webmail/migrations/0001_initial.py +90 -0
  254. modoboa/webmail/migrations/__init__.py +0 -0
  255. modoboa/webmail/mocks.py +12 -3
  256. modoboa/webmail/models.py +102 -0
  257. modoboa/webmail/serializers.py +84 -4
  258. modoboa/webmail/tests/data.py +21 -0
  259. modoboa/webmail/tests/test_lib_imaputils.py +33 -0
  260. modoboa/webmail/tests/test_viewsets.py +108 -0
  261. modoboa/webmail/urls.py +5 -0
  262. modoboa/webmail/viewsets.py +39 -9
  263. {modoboa-2.6.5.dist-info → modoboa-2.7.0.dist-info}/METADATA +16 -13
  264. {modoboa-2.6.5.dist-info → modoboa-2.7.0.dist-info}/RECORD +269 -252
  265. {modoboa-2.6.5.dist-info → modoboa-2.7.0.dist-info}/WHEEL +1 -1
  266. modoboa/admin/management/commands/handle_mailbox_operations.py +0 -103
  267. modoboa/core/management/commands/cleanlogs.py +0 -53
  268. modoboa/frontend_dist/assets/AccountLayout-APeC9bsN.js +0 -1
  269. modoboa/frontend_dist/assets/AccountPasswordSubForm-D4vB2yQK.js +0 -1
  270. modoboa/frontend_dist/assets/AccountView-CloBP1BY.js +0 -1
  271. modoboa/frontend_dist/assets/AddressBook-C03MXg3y.js +0 -1
  272. modoboa/frontend_dist/assets/AdminLayout-DFmyKOo6.js +0 -1
  273. modoboa/frontend_dist/assets/AlarmsView-CFhH0I5d.js +0 -1
  274. modoboa/frontend_dist/assets/AliasRecipientForm-CZ_SvjtG.js +0 -1
  275. modoboa/frontend_dist/assets/AuditTrailView-Cyq25Jle.js +0 -1
  276. modoboa/frontend_dist/assets/CalendarView-C_dSXRp9.js +0 -1
  277. modoboa/frontend_dist/assets/ChoiceField-BPhUvoyr.js +0 -1
  278. modoboa/frontend_dist/assets/ComposeEmailForm-D3yUbBJX.css +0 -1
  279. modoboa/frontend_dist/assets/ComposeEmailForm-Ns5SYs4J.js +0 -1
  280. modoboa/frontend_dist/assets/ComposeEmailView-DgUfiWEH.js +0 -1
  281. modoboa/frontend_dist/assets/ConfirmDialog-BMIg94-U.js +0 -1
  282. modoboa/frontend_dist/assets/ConnectedLayout-CSXdVJtV.js +0 -1
  283. modoboa/frontend_dist/assets/CreationForm-CaAZYATm.js +0 -1
  284. modoboa/frontend_dist/assets/DashboardView-DErmyUoy.js +0 -1
  285. modoboa/frontend_dist/assets/DomainsView-CC1EwRId.js +0 -1
  286. modoboa/frontend_dist/assets/EmailField-BFgga4zp.js +0 -1
  287. modoboa/frontend_dist/assets/EmailView-DPWTch_6.js +0 -1
  288. modoboa/frontend_dist/assets/EmptyLayout-DAw7fAab.js +0 -1
  289. modoboa/frontend_dist/assets/FiltersView-Cg9lkLw0.js +0 -1
  290. modoboa/frontend_dist/assets/ForwardEmailView-Dd1sxgaP.js +0 -1
  291. modoboa/frontend_dist/assets/IdentitiesView-CiHIQ9Qt.js +0 -1
  292. modoboa/frontend_dist/assets/InformationView-Cc0aBA5j.js +0 -1
  293. modoboa/frontend_dist/assets/MailboxView-DIVABvk5.css +0 -1
  294. modoboa/frontend_dist/assets/MailboxView-DplC0IXR.js +0 -5
  295. modoboa/frontend_dist/assets/MenuItems-efx2rqwT.js +0 -1
  296. modoboa/frontend_dist/assets/MessagesView-CesYYZ9f.js +0 -1
  297. modoboa/frontend_dist/assets/MigrationsView-DSBk8T0y.js +0 -1
  298. modoboa/frontend_dist/assets/ParametersView-B8W_PAiz.js +0 -1
  299. modoboa/frontend_dist/assets/ParametersView-Dsx2M_g9.js +0 -1
  300. modoboa/frontend_dist/assets/ProviderEditView-PEAr7MA1.js +0 -1
  301. modoboa/frontend_dist/assets/ProvidersView-CHvQf3Vv.js +0 -1
  302. modoboa/frontend_dist/assets/QuarantineLayout-BWEaBK48.js +0 -1
  303. modoboa/frontend_dist/assets/QuarantineView-BzjeDhTm.js +0 -1
  304. modoboa/frontend_dist/assets/ReplyEmailView-CwQ9jsce.js +0 -1
  305. modoboa/frontend_dist/assets/ResourcesForm-noC3AXd9.js +0 -1
  306. modoboa/frontend_dist/assets/SelfServiceLayout-DQgRMu0L.js +0 -1
  307. modoboa/frontend_dist/assets/SettingsView-DJTdIeRK.css +0 -1
  308. modoboa/frontend_dist/assets/SettingsView-Dgwpfd8O.js +0 -6
  309. modoboa/frontend_dist/assets/StatisticsView-BZkVSh5o.js +0 -1
  310. modoboa/frontend_dist/assets/TimeSerieChart-BXJwhTMO.js +0 -1
  311. modoboa/frontend_dist/assets/TimeSerieChart-C3XHmlRd.css +0 -1
  312. modoboa/frontend_dist/assets/UserLayout-CWK9ICfE.js +0 -1
  313. modoboa/frontend_dist/assets/VApp-CiIBWB_Q.js +0 -1
  314. modoboa/frontend_dist/assets/VAutocomplete-DcNqTIie.js +0 -1
  315. modoboa/frontend_dist/assets/VAutocomplete-hzGuLlUI.css +0 -1
  316. modoboa/frontend_dist/assets/VAvatar-BsHplcRP.js +0 -1
  317. modoboa/frontend_dist/assets/VBadge-CavP_E2g.js +0 -1
  318. modoboa/frontend_dist/assets/VCheckbox-CKW9lz5i.js +0 -1
  319. modoboa/frontend_dist/assets/VColorPicker-Bsrz8yif.js +0 -1
  320. modoboa/frontend_dist/assets/VDataTable-ocZo8ju0.js +0 -1
  321. modoboa/frontend_dist/assets/VDataTableServer-CJfpYXQW.js +0 -1
  322. modoboa/frontend_dist/assets/VDataTableVirtual-Cr8yw49k.js +0 -1
  323. modoboa/frontend_dist/assets/VDialog-CUMh3paI.js +0 -1
  324. modoboa/frontend_dist/assets/VExpansionPanels-CkwHiKsA.js +0 -1
  325. modoboa/frontend_dist/assets/VFileInput-D5qlksIG.js +0 -1
  326. modoboa/frontend_dist/assets/VForm-ClJU5x_v.js +0 -1
  327. modoboa/frontend_dist/assets/VInput-DhzSMFXh.js +0 -1
  328. modoboa/frontend_dist/assets/VMenu-B53a3gm6.js +0 -1
  329. modoboa/frontend_dist/assets/VMenu-BEipA1lw.css +0 -1
  330. modoboa/frontend_dist/assets/VPicker-B928ZVJQ.js +0 -1
  331. modoboa/frontend_dist/assets/VPicker-ClSXs6kv.css +0 -1
  332. modoboa/frontend_dist/assets/VProgressCircular-viI3jDXe.js +0 -1
  333. modoboa/frontend_dist/assets/VRadioGroup-C0bqFrYg.js +0 -1
  334. modoboa/frontend_dist/assets/VSelect-y0E5_vPn.js +0 -1
  335. modoboa/frontend_dist/assets/VSheet-DQkh_hsX.js +0 -1
  336. modoboa/frontend_dist/assets/VSpacer-CBqB9uSt.js +0 -1
  337. modoboa/frontend_dist/assets/VSwitch-D4xdR9jz.js +0 -1
  338. modoboa/frontend_dist/assets/VTabs-D2c0KeF2.js +0 -1
  339. modoboa/frontend_dist/assets/VTabs-NzpINroH.css +0 -1
  340. modoboa/frontend_dist/assets/VTextField-Cow3HZvI.css +0 -1
  341. modoboa/frontend_dist/assets/VTextField-DvvH1ciR.js +0 -1
  342. modoboa/frontend_dist/assets/VTextarea-DHtXtzqP.js +0 -1
  343. modoboa/frontend_dist/assets/VTextarea-DyGjqrlm.css +0 -1
  344. modoboa/frontend_dist/assets/VToolbar-CB2GrZpA.css +0 -1
  345. modoboa/frontend_dist/assets/VToolbar-pvLQtmbU.js +0 -1
  346. modoboa/frontend_dist/assets/VWindowItem-DErNPSH8.js +0 -1
  347. modoboa/frontend_dist/assets/WebmailLayout-BzW0LWYp.css +0 -1
  348. modoboa/frontend_dist/assets/WebmailLayout-D7J-QJ-0.js +0 -1
  349. modoboa/frontend_dist/assets/filter-BVvMTmPG.js +0 -1
  350. modoboa/frontend_dist/assets/global.store-Bha4Z76j.js +0 -1
  351. modoboa/frontend_dist/assets/importExport-EgijWlC1.js +0 -1
  352. modoboa/frontend_dist/assets/languages-Cge6pECg.js +0 -1
  353. modoboa/frontend_dist/assets/transports-BUSYxSF4.js +0 -1
  354. modoboa/frontend_dist/assets/webmail-HVg3ZHC0.js +0 -1
  355. modoboa/frontend_dist/assets/webmail.store-D-d88s4w.js +0 -1
  356. {modoboa-2.6.5.data → modoboa-2.7.0.data}/scripts/modoboa-admin.py +0 -0
  357. {modoboa-2.6.5.dist-info → modoboa-2.7.0.dist-info}/entry_points.txt +0 -0
  358. {modoboa-2.6.5.dist-info → modoboa-2.7.0.dist-info}/licenses/LICENSE +0 -0
  359. {modoboa-2.6.5.dist-info → modoboa-2.7.0.dist-info}/top_level.txt +0 -0
@@ -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 SendEmailSerializer(serializers.Serializer):
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
- def validate_sender(self, value):
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
@@ -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
@@ -13,5 +13,10 @@ router.register(
13
13
  viewsets.ComposeSessionViewSet,
14
14
  basename="webmail-compose-session",
15
15
  )
16
+ router.register(
17
+ "scheduled-messages",
18
+ viewsets.ScheduledMessageViewSet,
19
+ basename="webmail-scheduled-message",
20
+ )
16
21
 
17
22
  urlpatterns = router.urls
@@ -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": page_num * messages_per_page,
163
- "last_index": (page_num * messages_per_page) + len(content),
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
- status, error = lib.send_mail(
380
- request, serializer.validated_data, manager.get_content(pk)["attachments"]
381
- )
382
- if status:
383
- attachments.remove_attachments_and_session(manager, pk)
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.6.5
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.8
43
+ Requires-Dist: django==5.2.11
44
44
  Requires-Dist: django-phonenumber-field[phonenumbers]==8.4.0
45
- Requires-Dist: django-reversion==6.0.0
45
+ Requires-Dist: django-reversion==6.1.0
46
46
  Requires-Dist: django-xforwardedfor-middleware==2.0
47
- Requires-Dist: django-otp==1.6.3
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: passlib~=1.7.4
58
- Requires-Dist: bcrypt==4.3.0
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.0.0
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>=4.2.0rc1
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>=1.13.0
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.1.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