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,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
- call_command("amnotify")
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>plain</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>plain</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
 
@@ -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, 404)
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)
@@ -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
- raise Http404
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": True,
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": True,
105
+ "OutgoingMailServerUseSSL": smtp_settings["SOCKET_TYPE"].upper()
106
+ == "SSL", # `false` means StartTLS,
99
107
  "OutgoingMailServerUsername": emailaddress,
100
108
  "OutgoingPasswordSameAsIncomingPassword": True,
101
109
  }
@@ -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
- fromdate, tz = fromdate.split("+")
42
- tz = f"+{tz.replace(':', '')}"
43
- condition = [("currentdate", ":zone", tz, ":value", "ge", "iso8601", fromdate)]
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().split("+")[0],
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
- cal.vevent.add("dtstart").value = data["start"]
89
- cal.vevent.add("dtend").value = data["end"]
87
+ dtstart = data["start"]
88
+ dtend = data["end"]
90
89
  else:
91
- cal.vevent.add("dtstart").value = data["start"].date()
92
- cal.vevent.add("dtend").value = data["end"].date()
93
- self.remote_cal.add_event(cal)
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["start"].date()
106
- data["end"] = data["end"].date()
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.date_search(start, end)
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))
@@ -0,0 +1,7 @@
1
+ """Async jobs definition."""
2
+
3
+ from django.core.management import call_command
4
+
5
+
6
+ def generate_rights():
7
+ call_command("generate_rights")
@@ -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 date_search(self, start, end):
59
+ def search(self, **kwargs):
57
60
  return [
58
61
  objects.Event(data=EV1, parent=self),
59
62
  objects.Event(data=EV2, parent=self),
@@ -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(EventSerializer, self).__init__(*args, **kwargs)
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" in data:
174
- if "start" not in data:
175
- errors["start"] = _("This field is required.")
176
- if "end" not in data:
177
- errors["end"] = _("This field is required.")
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
@@ -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
- management.call_command("generate_rights", verbosity=False)
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
- "start": "2018-03-27T00:00:00Z",
432
- "end": "2018-03-28T00:00:00Z",
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
- "start": "2018-03-27T00:00:00Z",
465
- "end": "2018-03-28T00:00:00Z",
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
- "start": "2018-03-27T00:00:00Z",
487
- "end": "2018-03-28T00:00:00Z",
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
- "start": "2018-03-27T00:00:00Z",
500
- "end": "2018-03-28T00:00:00Z",
500
+ "start_date": "2018-03-27",
501
+ "end_date": "2018-03-28",
501
502
  "allDay": True,
502
503
  "color": "#ffdddd",
503
504
  "description": "Description",
@@ -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
- return dateutil.parser.parse(value)
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
+ ]
@@ -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
 
@@ -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(request, addressbook):
52
+ def sync_addressbook_from_cdav(addressbook_id: int, access_token: str):
53
53
  """Fetch changes from CardDAV server."""
54
- clt = get_cdav_client_from_request(request, addressbook)
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.save(update_fields=["last_sync", "sync_token"])
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 manually
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
- self.user.addressbook_set.update(last_sync=timezone.now())
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."""
@@ -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
- tasks.sync_addressbook_from_cdav(request, abook)
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
 
@@ -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):