modoboa 2.4.9__py3-none-any.whl → 2.4.11__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 (183) hide show
  1. modoboa/admin/api/v1/serializers.py +8 -1
  2. modoboa/admin/api/v2/serializers.py +5 -3
  3. modoboa/admin/api/v2/tests.py +11 -1
  4. modoboa/admin/lib.py +1 -1
  5. modoboa/admin/models/mailbox.py +15 -13
  6. modoboa/admin/models/mxrecord.py +4 -0
  7. modoboa/admin/tests/test_import_.py +11 -11
  8. modoboa/core/api/v2/serializers.py +43 -15
  9. modoboa/core/api/v2/tests.py +18 -0
  10. modoboa/core/api/v2/urls.py +5 -0
  11. modoboa/core/api/v2/views.py +20 -1
  12. modoboa/core/app_settings.py +12 -1
  13. modoboa/core/fido2_auth.py +1 -2
  14. modoboa/core/management/commands/cleanlogs.py +9 -0
  15. modoboa/core/management/commands/load_initial_data.py +7 -15
  16. modoboa/core/migrations/0025_rename_user_email_is_active_core_user_email_c0c03f_idx.py +5 -5
  17. modoboa/core/tests/test_core.py +5 -0
  18. modoboa/core/utils.py +3 -0
  19. modoboa/dnstools/api/v2/serializers.py +9 -11
  20. modoboa/frontend_dist/assets/{AccountAliasForm-DO4IpT1M.js → AccountAliasForm-BV6KvTu6.js} +1 -1
  21. modoboa/frontend_dist/assets/{AccountEditView-BLA0CnKd.js → AccountEditView-DDOFyfBD.js} +1 -1
  22. modoboa/frontend_dist/assets/AccountLayout-rX51xgxT.js +1 -0
  23. modoboa/frontend_dist/assets/{AccountPasswordSubForm-D9xU4Or7.js → AccountPasswordSubForm-D9S6LaeH.js} +1 -1
  24. modoboa/frontend_dist/assets/AccountView-cmvaZNq3.js +1 -0
  25. modoboa/frontend_dist/assets/{AddressBook-CJM-ajGg.js → AddressBook-DCJxL8SU.js} +1 -1
  26. modoboa/frontend_dist/assets/AdminLayout-r0wfG2lO.js +1 -0
  27. modoboa/frontend_dist/assets/{AlarmsView-CpIExehE.js → AlarmsView-C0bqC4PA.js} +1 -1
  28. modoboa/frontend_dist/assets/{AliasEditView-Bu0rnLCQ.js → AliasEditView-DVoWoCGY.js} +1 -1
  29. modoboa/frontend_dist/assets/{AliasRecipientForm-CGuaOz6K.js → AliasRecipientForm-IOae6sjF.js} +1 -1
  30. modoboa/frontend_dist/assets/{AliasView-Cdwd7AvE.js → AliasView-DrONZXOh.js} +1 -1
  31. modoboa/frontend_dist/assets/{AuditTrailView-Dn1B2S23.js → AuditTrailView-OTkoZaMU.js} +1 -1
  32. modoboa/frontend_dist/assets/{CalendarView-CV-DAWtd.js → CalendarView-CqF4_Ui9.js} +1 -1
  33. modoboa/frontend_dist/assets/{ChoiceField-BS45Bjds.js → ChoiceField-DJ_c78Cm.js} +1 -1
  34. modoboa/frontend_dist/assets/{ComposeEmailForm-22JKKl9M.js → ComposeEmailForm-DO5_GB3e.js} +1 -1
  35. modoboa/frontend_dist/assets/ComposeEmailView-A91HCBsN.js +1 -0
  36. modoboa/frontend_dist/assets/{ConfirmDialog-BLu1kBWp.js → ConfirmDialog-BBcgdAnO.js} +1 -1
  37. modoboa/frontend_dist/assets/ConnectedLayout-1oRW-Rql.js +1 -0
  38. modoboa/frontend_dist/assets/{ConnectedLayout-AhwwAZvE.css → ConnectedLayout-Dvwmicnc.css} +1 -1
  39. modoboa/frontend_dist/assets/CreationForm-71YJbjsA.js +1 -0
  40. modoboa/frontend_dist/assets/CreationForm-HaiQhqwK.css +1 -0
  41. modoboa/frontend_dist/assets/DashboardView-CdLpSfUl.js +1 -0
  42. modoboa/frontend_dist/assets/{DashboardView-BLlMi6Qb.css → DashboardView-gwwVAPvt.css} +1 -1
  43. modoboa/frontend_dist/assets/DomainAdminList-BjC4KsqI.js +1 -0
  44. modoboa/frontend_dist/assets/DomainEditView-CQjKwYxl.js +1 -0
  45. modoboa/frontend_dist/assets/DomainTransportForm-C2xo0Yd7.js +1 -0
  46. modoboa/frontend_dist/assets/{DomainView-DCfSLvj-.css → DomainView-BDKoBFYr.css} +1 -1
  47. modoboa/frontend_dist/assets/DomainView-BhhuZI_N.js +5 -0
  48. modoboa/frontend_dist/assets/DomainsView-Cft4BP8Z.js +1 -0
  49. modoboa/frontend_dist/assets/{DomainsView-BUgJLN9o.css → DomainsView-DasJ0NdZ.css} +1 -1
  50. modoboa/frontend_dist/assets/{EmailField-phdi2rhQ.js → EmailField-C8umy0EU.js} +1 -1
  51. modoboa/frontend_dist/assets/{EmailView-B8eqs4jh.js → EmailView-ki7uEQPD.js} +1 -1
  52. modoboa/frontend_dist/assets/EmptyLayout-DaA1XH9n.js +1 -0
  53. modoboa/frontend_dist/assets/{FiltersView-Dq_lXOZQ.js → FiltersView-FYFZxG4B.js} +1 -1
  54. modoboa/frontend_dist/assets/ForwardEmailView-cUbnSYCF.js +1 -0
  55. modoboa/frontend_dist/assets/{HtmlEditor-KH0pmxrK.js → HtmlEditor-CJ9umKeO.js} +1 -1
  56. modoboa/frontend_dist/assets/IdentitiesView-0ziuQ5s-.css +1 -0
  57. modoboa/frontend_dist/assets/IdentitiesView-njNo8N5n.js +1 -0
  58. modoboa/frontend_dist/assets/{InformationView-DlwtlpvG.js → InformationView-D1h38POt.js} +1 -1
  59. modoboa/frontend_dist/assets/{LoadingData-BYDZn1pb.js → LoadingData-CYwX3Jpn.js} +1 -1
  60. modoboa/frontend_dist/assets/{LoginCallbackView-xSv3LV4_.js → LoginCallbackView-E01qkKn0.js} +1 -1
  61. modoboa/frontend_dist/assets/{LoginView-R5FtOIzW.js → LoginView-Cy4uFV9h.js} +1 -1
  62. modoboa/frontend_dist/assets/{MailboxView-esEw0ueS.js → MailboxView-IlrLWm_H.js} +1 -1
  63. modoboa/frontend_dist/assets/{MenuItems-Bck8ixGf.js → MenuItems-BAtHWzAE.js} +1 -1
  64. modoboa/frontend_dist/assets/MessagesView-OSpjixFq.js +1 -0
  65. modoboa/frontend_dist/assets/{MigrationsView-BHTt1qeH.js → MigrationsView-DKNOsVzF.js} +1 -1
  66. modoboa/frontend_dist/assets/Modoboa_RVB-BLEU-SANS-pKrnjsR_.png +0 -0
  67. modoboa/frontend_dist/assets/{ParametersForm-Bx5J7UU0.js → ParametersForm-BZM0QSvg.js} +1 -1
  68. modoboa/frontend_dist/assets/ParametersView-C4bXASiq.js +1 -0
  69. modoboa/frontend_dist/assets/ParametersView-CYXgNmc1.js +1 -0
  70. modoboa/frontend_dist/assets/{ProviderEditView-d2fBym_r.js → ProviderEditView-CyxCWTST.js} +1 -1
  71. modoboa/frontend_dist/assets/{ProviderGeneralForm-C9jkYxB7.js → ProviderGeneralForm-BYPjNHqB.js} +1 -1
  72. modoboa/frontend_dist/assets/ProvidersView-CxrMkRyk.js +1 -0
  73. modoboa/frontend_dist/assets/ReplyEmailView-Dkw9-N26.js +1 -0
  74. modoboa/frontend_dist/assets/{ResourcesForm-RZNnt_x1.js → ResourcesForm-CuUvrOdY.js} +1 -1
  75. modoboa/frontend_dist/assets/SettingsView-BxLJBFY0.js +6 -0
  76. modoboa/frontend_dist/assets/{StatisticsView-CABa0tgU.js → StatisticsView-BN7QsZMT.js} +1 -1
  77. modoboa/frontend_dist/assets/{TimeSerieChart-BjM9PGIT.js → TimeSerieChart-BMN8BeFZ.js} +1 -1
  78. modoboa/frontend_dist/assets/UserLayout-B6-JQg4F.js +1 -0
  79. modoboa/frontend_dist/assets/{VAlert-DSMD0lqo.js → VAlert-DIQTrRif.js} +1 -1
  80. modoboa/frontend_dist/assets/{VApp-D2FuKvLN.js → VApp-CpkYA7js.js} +1 -1
  81. modoboa/frontend_dist/assets/{VAutocomplete-CQ5LL_Nk.js → VAutocomplete-C4IpXyl8.js} +1 -1
  82. modoboa/frontend_dist/assets/{VAvatar-DbAmPjyA.js → VAvatar-Lpb-Dion.js} +1 -1
  83. modoboa/frontend_dist/assets/{VCard-BGXTuaE-.js → VCard-er_isjE_.js} +1 -1
  84. modoboa/frontend_dist/assets/{VCheckbox-BM1QzY5m.js → VCheckbox-D-u8JXP1.js} +1 -1
  85. modoboa/frontend_dist/assets/{VCheckboxBtn-DdHQE-gt.js → VCheckboxBtn-Dt810gWf.js} +1 -1
  86. modoboa/frontend_dist/assets/{VChip-Bm7znPZ6.js → VChip-B4iSpj8_.js} +1 -1
  87. modoboa/frontend_dist/assets/{VColorPicker-CYCnVu_Z.js → VColorPicker-BAjGDsXv.js} +1 -1
  88. modoboa/frontend_dist/assets/VContainer-DvTbsotR.js +1 -0
  89. modoboa/frontend_dist/assets/{VDataTable-Etpc99CI.js → VDataTable-4JRjbtgF.js} +1 -1
  90. modoboa/frontend_dist/assets/{VDataTableServer-BNNG6dBx.js → VDataTableServer-tIDT1m3-.js} +1 -1
  91. modoboa/frontend_dist/assets/{VDataTableVirtual-D9Km3t6e.js → VDataTableVirtual-BlnO18u_.js} +1 -1
  92. modoboa/frontend_dist/assets/{VDialog-0NvxFg1X.js → VDialog-Bk6EWNhz.js} +1 -1
  93. modoboa/frontend_dist/assets/{VExpansionPanels-eED_hKBs.js → VExpansionPanels-CwGtXDhr.js} +1 -1
  94. modoboa/frontend_dist/assets/{VFileInput-COIdDRxq.js → VFileInput-D1_7ZkO_.js} +1 -1
  95. modoboa/frontend_dist/assets/{VForm-DwwrfSN8.js → VForm-DAkW4nfy.js} +1 -1
  96. modoboa/frontend_dist/assets/{VMenu-CTyyRlRb.js → VMenu-BPFJwj2f.js} +1 -1
  97. modoboa/frontend_dist/assets/{VPicker-3NOHH3h1.js → VPicker-CfT82M8N.js} +1 -1
  98. modoboa/frontend_dist/assets/{VProgressCircular-1VQzh5PK.js → VProgressCircular-w75-3ogi.js} +1 -1
  99. modoboa/frontend_dist/assets/{VRadioGroup-CnRLqIcd.js → VRadioGroup-0j6DNC_k.js} +1 -1
  100. modoboa/frontend_dist/assets/{VRow-DgX40uiD.js → VRow-BF35mT1S.js} +1 -1
  101. modoboa/frontend_dist/assets/{VSelect-CeY-emKK.js → VSelect-Cs4ARbAS.js} +1 -1
  102. modoboa/frontend_dist/assets/{VSelectionControl-BYdVI5Q9.js → VSelectionControl-Dg-XyRRS.js} +1 -1
  103. modoboa/frontend_dist/assets/VSheet-Btq_Mu4s.js +1 -0
  104. modoboa/frontend_dist/assets/VSpacer-C7xukQmu.js +1 -0
  105. modoboa/frontend_dist/assets/{VSwitch-BSifpScd.js → VSwitch-Cs1NQrmk.js} +1 -1
  106. modoboa/frontend_dist/assets/{VTable-BeyU0Uhj.js → VTable-CNz2SGk4.js} +1 -1
  107. modoboa/frontend_dist/assets/{VTabs-Dzq8prKm.js → VTabs-B1fyVn4M.js} +1 -1
  108. modoboa/frontend_dist/assets/{VTextField-FBM5XwC5.js → VTextField-BdyvgvkG.js} +1 -1
  109. modoboa/frontend_dist/assets/{VTextarea-BwkVlXPS.js → VTextarea-DnOMpe0Q.js} +1 -1
  110. modoboa/frontend_dist/assets/{VToolbar-AQXTYlxS.js → VToolbar-BiCiBxBJ.js} +1 -1
  111. modoboa/frontend_dist/assets/{VWindowItem-p9SJ1V9Y.js → VWindowItem-ChWm_kz3.js} +1 -1
  112. modoboa/frontend_dist/assets/WebmailLayout-o4uEkp9e.js +1 -0
  113. modoboa/frontend_dist/assets/admin-o-HRGnmT.js +1 -0
  114. modoboa/frontend_dist/assets/{aliases-C-hC9If-.js → aliases-DDVeehyg.js} +1 -1
  115. modoboa/frontend_dist/assets/{contacts-DZDbAN4o.js → contacts-C84DY-Q1.js} +1 -1
  116. modoboa/frontend_dist/assets/{domains-CWdc-ODs.js → domains-Bgn4ixHL.js} +1 -1
  117. modoboa/frontend_dist/assets/{domains.store-B6sc48r9.js → domains.store-DTE-V7Y1.js} +1 -1
  118. modoboa/frontend_dist/assets/{filter-BSw3r225.js → filter-CnffiQAW.js} +1 -1
  119. modoboa/frontend_dist/assets/{forwardRefs-Cn7kyt_f.js → forwardRefs-Dvjn_Xq4.js} +1 -1
  120. modoboa/frontend_dist/assets/{global.store-IwCGIFoO.js → global.store-BaiD63EN.js} +1 -1
  121. modoboa/frontend_dist/assets/importExport-BlQYb0NO.js +1 -0
  122. modoboa/frontend_dist/assets/index-DuzUMVLM.js +1 -0
  123. modoboa/frontend_dist/assets/index-I1VDlN4g.js +984 -0
  124. modoboa/frontend_dist/assets/layout-D8ZJPiJ_.js +1 -0
  125. modoboa/frontend_dist/assets/{layout.store-CSmWF8Cf.js → layout.store-DkjrAoXt.js} +1 -1
  126. modoboa/frontend_dist/assets/logos-q8SEyAa4.js +1 -0
  127. modoboa/frontend_dist/assets/{logs-DIXCC1C0.js → logs-B7IJ7LBa.js} +1 -1
  128. modoboa/frontend_dist/assets/{parameters-u6VE5cq8.js → parameters-A6iBEYQq.js} +1 -1
  129. modoboa/frontend_dist/assets/{parameters.store-D4x8W7LS.js → parameters.store-BiXS4_6w.js} +1 -1
  130. modoboa/frontend_dist/assets/{permissions-C7XVO62l.js → permissions-CITHLHVg.js} +1 -1
  131. modoboa/frontend_dist/assets/{ssrBoot-B-x5LB2_.js → ssrBoot-AzTdjPjk.js} +1 -1
  132. modoboa/frontend_dist/assets/{tag-DCSJ2lri.js → tag-BnSYRTcD.js} +1 -1
  133. modoboa/frontend_dist/assets/transports-Dz7c6kIy.js +1 -0
  134. modoboa/frontend_dist/assets/{webmail-D2rioayn.js → webmail-CSH_3l6R.js} +1 -1
  135. modoboa/frontend_dist/config.json +1 -3
  136. modoboa/frontend_dist/index.html +1 -1
  137. modoboa/lib/redis.py +1 -5
  138. modoboa/locale/ja_JP/LC_MESSAGES/django.mo +0 -0
  139. modoboa/locale/ja_JP/LC_MESSAGES/django.po +122 -263
  140. modoboa/maillog/management/commands/logparser.py +1 -0
  141. modoboa/pdfcredentials/api/v2/serializers.py +17 -5
  142. modoboa/pdfcredentials/api/v2/tests.py +28 -0
  143. modoboa/pdfcredentials/app_settings.py +1 -1
  144. {modoboa-2.4.9.dist-info → modoboa-2.4.11.dist-info}/METADATA +2 -2
  145. {modoboa-2.4.9.dist-info → modoboa-2.4.11.dist-info}/RECORD +150 -147
  146. modoboa/frontend_dist/assets/AccountLayout-DMwguvdC.js +0 -1
  147. modoboa/frontend_dist/assets/AccountView-DegeD2uG.js +0 -1
  148. modoboa/frontend_dist/assets/AdminLayout-CTbzqJSq.js +0 -1
  149. modoboa/frontend_dist/assets/ComposeEmailView-B6Tbw3MS.js +0 -1
  150. modoboa/frontend_dist/assets/ConnectedLayout-CNzvhsb9.js +0 -1
  151. modoboa/frontend_dist/assets/CreationForm-ChgnxngD.js +0 -1
  152. modoboa/frontend_dist/assets/CreationForm-DLumMdUy.css +0 -1
  153. modoboa/frontend_dist/assets/DashboardView-CubvMqxt.js +0 -1
  154. modoboa/frontend_dist/assets/DomainAdminList-B0z8fRmf.js +0 -1
  155. modoboa/frontend_dist/assets/DomainEditView-DrZ9__pt.js +0 -1
  156. modoboa/frontend_dist/assets/DomainTransportForm-DEsm5zOa.js +0 -1
  157. modoboa/frontend_dist/assets/DomainView-RmJalGk7.js +0 -5
  158. modoboa/frontend_dist/assets/DomainsView-DzL9wtff.js +0 -1
  159. modoboa/frontend_dist/assets/EmptyLayout-FS-cJHKI.js +0 -1
  160. modoboa/frontend_dist/assets/ForwardEmailView-Lpw9sqB4.js +0 -1
  161. modoboa/frontend_dist/assets/IdentitiesView-7nHABB9n.js +0 -1
  162. modoboa/frontend_dist/assets/IdentitiesView-cvIq8q24.css +0 -1
  163. modoboa/frontend_dist/assets/MessagesView-Dyg8Y4bi.js +0 -1
  164. modoboa/frontend_dist/assets/ParametersView-Bkw5_CyJ.js +0 -1
  165. modoboa/frontend_dist/assets/ParametersView-CyAAGtfU.js +0 -1
  166. modoboa/frontend_dist/assets/ProvidersView-CjKsqSYV.js +0 -1
  167. modoboa/frontend_dist/assets/ReplyEmailView-DcUkD2HR.js +0 -1
  168. modoboa/frontend_dist/assets/SettingsView-TStQqZWd.js +0 -6
  169. modoboa/frontend_dist/assets/UserLayout-C6_Zjp6P.js +0 -1
  170. modoboa/frontend_dist/assets/VContainer-B8HjqhNU.js +0 -1
  171. modoboa/frontend_dist/assets/VSheet-C8P69LA8.js +0 -1
  172. modoboa/frontend_dist/assets/VSpacer-pLqQLLwB.js +0 -1
  173. modoboa/frontend_dist/assets/WebmailLayout-E3DJFEW2.js +0 -1
  174. modoboa/frontend_dist/assets/admin-B0fFM9gW.js +0 -1
  175. modoboa/frontend_dist/assets/importExport-B7mBfHxZ.js +0 -1
  176. modoboa/frontend_dist/assets/index-BCUdq-Zu.js +0 -852
  177. modoboa/frontend_dist/assets/index-CJ87ZZG1.js +0 -1
  178. modoboa/frontend_dist/assets/layout-CLqGxWz4.js +0 -1
  179. {modoboa-2.4.9.data → modoboa-2.4.11.data}/scripts/modoboa-admin.py +0 -0
  180. {modoboa-2.4.9.dist-info → modoboa-2.4.11.dist-info}/WHEEL +0 -0
  181. {modoboa-2.4.9.dist-info → modoboa-2.4.11.dist-info}/entry_points.txt +0 -0
  182. {modoboa-2.4.9.dist-info → modoboa-2.4.11.dist-info}/licenses/LICENSE +0 -0
  183. {modoboa-2.4.9.dist-info → modoboa-2.4.11.dist-info}/top_level.txt +0 -0
@@ -68,12 +68,18 @@ class DomainSerializer(serializers.ModelSerializer):
68
68
  "message_limit",
69
69
  "creation",
70
70
  "last_modification",
71
+ "allocated_quota_in_percent",
72
+ "mailbox_count",
73
+ "mbalias_count",
74
+ "domainalias_count",
75
+ "enable_dns_checks",
71
76
  )
72
77
  read_only_fields = (
73
78
  "pk",
74
79
  "dkim_public_key",
75
80
  "dns_global_status",
76
- "allocated_quota_in_percent" "mailbox_count",
81
+ "allocated_quota_in_percent",
82
+ "mailbox_count",
77
83
  "mbalias_count",
78
84
  "domainalias_count",
79
85
  "enable_dns_checks",
@@ -202,6 +208,7 @@ class MailboxSerializer(serializers.ModelSerializer):
202
208
  "message_limit",
203
209
  "quota_usage",
204
210
  "is_send_only",
211
+ "sent_messages_in_percent",
205
212
  )
206
213
 
207
214
  def get_quota_usage(self, mb) -> int:
@@ -61,6 +61,8 @@ class DomainSerializer(v1_serializers.DomainSerializer):
61
61
  "domain_admin",
62
62
  "transport",
63
63
  "opened_alarms_count",
64
+ "sent_messages",
65
+ "sent_messages_in_percent",
64
66
  )
65
67
 
66
68
  def __init__(self, *args, **kwargs):
@@ -221,9 +223,9 @@ class AdminGlobalParametersSerializer(serializers.Serializer):
221
223
  enable_dkim_checks = serializers.BooleanField(default=True)
222
224
  enable_dmarc_checks = serializers.BooleanField(default=True)
223
225
  enable_autoconfig_checks = serializers.BooleanField(default=True)
224
- custom_dns_server = serializers.IPAddressField(allow_blank=True)
226
+ custom_dns_server = serializers.IPAddressField(allow_blank=True, allow_null=True)
225
227
  enable_dnsbl_checks = serializers.BooleanField(default=True)
226
- dkim_keys_storage_dir = serializers.CharField(allow_blank=True)
228
+ dkim_keys_storage_dir = serializers.CharField(allow_blank=True, allow_null=True)
227
229
  dkim_default_key_length = serializers.ChoiceField(
228
230
  default=2048, choices=constants.DKIM_KEY_LENGTHS
229
231
  )
@@ -729,7 +731,7 @@ class CSVImportSerializer(serializers.Serializer):
729
731
  class CSVIdentityImportSerializer(CSVImportSerializer):
730
732
  """Custom serializer for identity import."""
731
733
 
732
- crypt_password = serializers.BooleanField()
734
+ crypt_passwords = serializers.BooleanField()
733
735
 
734
736
 
735
737
  class AlarmSerializer(serializers.ModelSerializer):
@@ -331,6 +331,16 @@ class AccountViewSetTestCase(ModoAPITestCase):
331
331
  resp = self.client.patch(url, data, format="json")
332
332
  self.assertEqual(resp.status_code, 200)
333
333
 
334
+ def test_update_quota(self):
335
+ account = core_models.User.objects.get(username="user@test.com")
336
+ url = reverse("v2:account-detail", args=[account.pk])
337
+ data = {
338
+ "mailbox": {"quota": 1},
339
+ }
340
+ resp = self.client.patch(url, data, format="json")
341
+ self.assertEqual(resp.status_code, 200)
342
+ self.assertEqual(account.mailbox.quota, data["mailbox"]["quota"])
343
+
334
344
  def test_user_updates_password(self):
335
345
  account = core_models.User.objects.get(username="user@test.com")
336
346
  self.authenticate_user(account)
@@ -583,7 +593,7 @@ dlist; dlist@test.com; True; user1@test.com; user@extdomain.com
583
593
  ) # NOQA:E501
584
594
  self.client.post(
585
595
  reverse("v2:identities-import-from-csv"),
586
- {"sourcefile": f, "crypt_password": True},
596
+ {"sourcefile": f, "crypt_passwords": True},
587
597
  )
588
598
  admin = core_models.User.objects.get(username="admin")
589
599
  u1 = core_models.User.objects.get(username="user1@test.com")
modoboa/admin/lib.py CHANGED
@@ -138,7 +138,7 @@ def import_domainalias(user, row, formopts):
138
138
  def import_account(user, row, formopts):
139
139
  """Specific code for accounts import"""
140
140
  account = User()
141
- account.from_csv(user, row, formopts["crypt_password"])
141
+ account.from_csv(user, row, formopts["crypt_passwords"])
142
142
 
143
143
 
144
144
  def _import_alias(user, row, **kwargs):
@@ -295,19 +295,21 @@ class Mailbox(mixins.MessageLimitMixin, AdminObject):
295
295
  def update_from_dict(self, user, values):
296
296
  """Update mailbox from a dictionary."""
297
297
  newaddress = None
298
- if values["email"] != self.full_address:
299
- newaddress = values["email"]
300
- elif (
301
- self.user.role == "SimpleUsers" and self.user.username != self.full_address
302
- ):
303
- newaddress = self.user.username
304
- if newaddress is not None:
305
- local_part, domname = split_mailbox(newaddress)
306
- domain = Domain.objects.filter(name=domname).first()
307
- if domain is None:
308
- raise lib_exceptions.NotFound(_("Domain does not exist"))
309
- if not user.can_access(domain):
310
- raise lib_exceptions.PermDeniedException
298
+ if "email" in values:
299
+ if values["email"] != self.full_address:
300
+ newaddress = values["email"]
301
+ elif (
302
+ self.user.role == "SimpleUsers"
303
+ and self.user.username != self.full_address
304
+ ):
305
+ newaddress = self.user.username
306
+ if newaddress is not None:
307
+ local_part, domname = split_mailbox(newaddress)
308
+ domain = Domain.objects.filter(name=domname).first()
309
+ if domain is None:
310
+ raise lib_exceptions.NotFound(_("Domain does not exist"))
311
+ if not user.can_access(domain):
312
+ raise lib_exceptions.PermDeniedException
311
313
  if "use_domain_quota" in values:
312
314
  self.use_domain_quota = values["use_domain_quota"]
313
315
  if "use_domain_quota" in values or "quota" in values:
@@ -71,6 +71,10 @@ class MXRecord(models.Model):
71
71
  def __str__(self):
72
72
  return f"{self.name} ({self.address}) for {self.domain} "
73
73
 
74
+ @property
75
+ def active_dnsbl_results(self):
76
+ return self.dnsblresult_set.exclude(status="")
77
+
74
78
 
75
79
  class DNSBLQuerySet(models.QuerySet):
76
80
  """Custom manager for DNSBLResultManager."""
@@ -88,7 +88,7 @@ account; user1@nonlocal.com; toto; User; One; True; SimpleUsers; user1@nonlocal.
88
88
  name="identities.csv",
89
89
  ) # NOQA:E501
90
90
  url = reverse("v2:identities-import-from-csv")
91
- self.client.post(url, {"sourcefile": f, "crypt_password": True})
91
+ self.client.post(url, {"sourcefile": f, "crypt_passwords": True})
92
92
  self.assertFalse(User.objects.filter(username="user1@nonlocal.com").exists())
93
93
 
94
94
  def test_import_invalid_quota(self):
@@ -99,7 +99,7 @@ account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; ; t
99
99
  name="identities.csv",
100
100
  ) # NOQA:E501
101
101
  url = reverse("v2:identities-import-from-csv")
102
- resp = self.client.post(url, {"sourcefile": f, "crypt_password": True})
102
+ resp = self.client.post(url, {"sourcefile": f, "crypt_passwords": True})
103
103
  self.assertIn("wrong quota value", resp.content.decode())
104
104
 
105
105
  def test_import_domain_by_domainadmin(self):
@@ -114,7 +114,7 @@ domain; domain2.com; 1000; 200; False
114
114
  name="identities.csv",
115
115
  )
116
116
  url = reverse("v2:identities-import-from-csv")
117
- resp = self.client.post(url, {"sourcefile": f, "crypt_password": True})
117
+ resp = self.client.post(url, {"sourcefile": f, "crypt_passwords": True})
118
118
  self.assertIn("You are not allowed to import domains", resp.content.decode())
119
119
  f = ContentFile(
120
120
  b"""
@@ -122,7 +122,7 @@ domainalias; domalias1.com; test.com; True
122
122
  """,
123
123
  name="identities.csv",
124
124
  )
125
- resp = self.client.post(url, {"sourcefile": f, "crypt_password": True})
125
+ resp = self.client.post(url, {"sourcefile": f, "crypt_passwords": True})
126
126
  self.assertIn(
127
127
  "You are not allowed to import domain aliases", resp.content.decode()
128
128
  )
@@ -138,7 +138,7 @@ account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; 40
138
138
  )
139
139
  resp = self.client.post(
140
140
  reverse("v2:identities-import-from-csv"),
141
- {"sourcefile": f, "crypt_password": True},
141
+ {"sourcefile": f, "crypt_passwords": True},
142
142
  )
143
143
  self.assertIn("test.com: domain quota exceeded", resp.content.decode())
144
144
 
@@ -151,7 +151,7 @@ account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com
151
151
  )
152
152
  self.client.post(
153
153
  reverse("v2:identities-import-from-csv"),
154
- {"sourcefile": f, "crypt_password": True},
154
+ {"sourcefile": f, "crypt_passwords": True},
155
155
  )
156
156
  account = User.objects.get(username="user1@test.com")
157
157
  self.assertEqual(
@@ -168,7 +168,7 @@ account; truc@test.com; toto; René; Truc; True; DomainAdmins; truc@test.com; 0;
168
168
  ) # NOQA:E501
169
169
  self.client.post(
170
170
  reverse("v2:identities-import-from-csv"),
171
- {"sourcefile": f, "crypt_password": True, "continue_if_exists": True},
171
+ {"sourcefile": f, "crypt_passwords": True, "continue_if_exists": True},
172
172
  )
173
173
  admin = User.objects.get(username="admin")
174
174
  u1 = User.objects.get(username="truc@test.com")
@@ -189,7 +189,7 @@ account; sa@test.com; toto; Super; Admin; True; SuperAdmins; superadmin@test.com
189
189
  ) # NOQA:E501
190
190
  self.client.post(
191
191
  reverse("v2:identities-import-from-csv"),
192
- {"sourcefile": f, "crypt_password": True, "continue_if_exists": True},
192
+ {"sourcefile": f, "crypt_passwords": True, "continue_if_exists": True},
193
193
  )
194
194
  with self.assertRaises(User.DoesNotExist):
195
195
  User.objects.get(username="sa@test.com")
@@ -203,7 +203,7 @@ alias;user.alias@test.com;True;user@test.com;;;;;;;;;;;;;;;;
203
203
  )
204
204
  self.client.post(
205
205
  reverse("v2:identities-import-from-csv"),
206
- {"sourcefile": f, "crypt_password": True, "continue_if_exists": True},
206
+ {"sourcefile": f, "crypt_passwords": True, "continue_if_exists": True},
207
207
  )
208
208
  alias = Alias.objects.get(address="user.alias@test.com")
209
209
  self.assertEqual(alias.type, "alias")
@@ -218,7 +218,7 @@ alias;user@test.com;True;admin@test.com
218
218
  )
219
219
  self.client.post(
220
220
  reverse("v2:identities-import-from-csv"),
221
- {"sourcefile": f, "crypt_password": True},
221
+ {"sourcefile": f, "crypt_passwords": True},
222
222
  )
223
223
  self.assertTrue(
224
224
  Alias.objects.filter(address="user@test.com", internal=False).exists()
@@ -315,7 +315,7 @@ alias; alias1@test.com; True; user1@test.com
315
315
  ) # NOQA:E501
316
316
  resp = self.client.post(
317
317
  reverse("v2:identities-import-from-csv"),
318
- {"sourcefile": f, "crypt_password": True},
318
+ {"sourcefile": f, "crypt_passwords": True},
319
319
  )
320
320
  self.assertEqual(resp.status_code, 200)
321
321
 
@@ -76,8 +76,12 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
76
76
  default_password = serializers.CharField(default="ChangeMe1!")
77
77
  random_password_length = serializers.IntegerField(min_value=8, default=8)
78
78
  allow_special_characters = serializers.BooleanField(default=False)
79
- update_password_url = serializers.URLField(required=False, allow_blank=True)
80
- password_recovery_msg = serializers.CharField(required=False, allow_blank=True)
79
+ update_password_url = serializers.URLField(
80
+ required=False, allow_blank=True, allow_null=True
81
+ )
82
+ password_recovery_msg = serializers.CharField(
83
+ required=False, allow_blank=True, allow_null=True
84
+ )
81
85
  sms_password_recovery = serializers.BooleanField(default=False)
82
86
  sms_provider = serializers.ChoiceField(
83
87
  choices=constants.SMS_BACKENDS, required=False, allow_null=True
@@ -87,7 +91,9 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
87
91
  ldap_server_address = serializers.CharField(default="localhost")
88
92
  ldap_server_port = serializers.IntegerField(default=389)
89
93
  ldap_enable_secondary_server = serializers.BooleanField(default=False)
90
- ldap_secondary_server_address = serializers.CharField(required=False)
94
+ ldap_secondary_server_address = serializers.CharField(
95
+ required=False, allow_blank=True, allow_null=True
96
+ )
91
97
  ldap_secondary_server_port = serializers.IntegerField(default=389, required=False)
92
98
  ldap_secured = serializers.ChoiceField(
93
99
  choices=constants.LDAP_SECURE_MODES, default="none"
@@ -109,9 +115,11 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
109
115
  choices=constants.LDAP_AUTH_METHODS,
110
116
  default="searchbind",
111
117
  )
112
- ldap_bind_dn = serializers.CharField(default="", required=False, allow_blank=True)
118
+ ldap_bind_dn = serializers.CharField(
119
+ default="", required=False, allow_blank=True, allow_null=True
120
+ )
113
121
  ldap_bind_password = serializers.CharField(
114
- default="", required=False, allow_blank=True
122
+ default="", required=False, allow_blank=True, allow_null=True
115
123
  )
116
124
  ldap_search_base = serializers.CharField(
117
125
  default="", required=False, allow_blank=True
@@ -124,15 +132,21 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
124
132
  )
125
133
 
126
134
  # LDAP sync settings
127
- ldap_sync_bind_dn = serializers.CharField(required=False, allow_blank=True)
128
- ldap_sync_bind_password = serializers.CharField(required=False, allow_blank=True)
135
+ ldap_sync_bind_dn = serializers.CharField(
136
+ required=False, allow_blank=True, allow_null=True
137
+ )
138
+ ldap_sync_bind_password = serializers.CharField(
139
+ required=False, allow_blank=True, allow_null=True
140
+ )
129
141
  ldap_enable_sync = serializers.BooleanField(default=False)
130
142
  ldap_sync_delete_remote_account = serializers.BooleanField(default=False)
131
143
  ldap_sync_account_dn_template = serializers.CharField(
132
- required=False, allow_blank=True
144
+ required=False, allow_blank=True, allow_null=True
133
145
  )
134
146
  ldap_enable_import = serializers.BooleanField(default=False)
135
- ldap_import_search_base = serializers.CharField(required=False, allow_blank=True)
147
+ ldap_import_search_base = serializers.CharField(
148
+ required=False, allow_blank=True, allow_null=True
149
+ )
136
150
  ldap_import_search_filter = serializers.CharField(default="(cn=*)", required=False)
137
151
  ldap_import_username_attr = serializers.CharField(default="cn")
138
152
  ldap_dovecot_sync = serializers.BooleanField(default=False)
@@ -141,7 +155,9 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
141
155
  )
142
156
 
143
157
  # Dashboard settings
144
- custom_welcome_message = serializers.CharField(required=False, allow_blank=True)
158
+ custom_welcome_message = serializers.CharField(
159
+ required=False, allow_blank=True, allow_null=True
160
+ )
145
161
  rss_feed_url = serializers.URLField(
146
162
  allow_blank=True, required=False, allow_null=True
147
163
  )
@@ -162,7 +178,9 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
162
178
  enable_api_communication = serializers.BooleanField(default=True)
163
179
  check_new_versions = serializers.BooleanField(default=True)
164
180
  send_new_versions_email = serializers.BooleanField(default=False)
165
- new_versions_email_rcpt = lib_fields.DRFEmailFieldUTF8(required=False)
181
+ new_versions_email_rcpt = lib_fields.DRFEmailFieldUTF8(
182
+ required=False, allow_null=True
183
+ )
166
184
  send_statistics = serializers.BooleanField(default=True)
167
185
 
168
186
  # Misc settings
@@ -170,6 +188,7 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
170
188
  inactive_account_threshold = serializers.IntegerField(default=30)
171
189
  top_notifications_check_interval = serializers.IntegerField(default=30)
172
190
  log_maximum_age = serializers.IntegerField(default=365)
191
+ message_history_maximum_age = serializers.IntegerField(default=180)
173
192
  items_per_page = serializers.IntegerField(default=30)
174
193
  default_top_redirection = serializers.ChoiceField(
175
194
  default="user", choices=[("user", _("User profile"))], required=False
@@ -195,10 +214,11 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
195
214
  return value
196
215
 
197
216
  def validate_ldap_sync_account_dn_template(self, value):
198
- try:
199
- value % {"user": "toto"}
200
- except (KeyError, ValueError):
201
- raise serializers.ValidationError(_("Invalid syntax")) from None
217
+ if value:
218
+ try:
219
+ value % {"user": "toto"}
220
+ except (KeyError, ValueError):
221
+ raise serializers.ValidationError(_("Invalid syntax")) from None
202
222
  return value
203
223
 
204
224
  def validate_ldap_search_filter(self, value):
@@ -672,3 +692,11 @@ class ThemeSerializer(serializers.Serializer):
672
692
  theme_primary_color_light = serializers.CharField(default="#3688F9")
673
693
  theme_secondary_color = serializers.CharField(default="#F18429")
674
694
  theme_label_color = serializers.CharField(default="#616161")
695
+
696
+
697
+ class StatisticsSerializer(serializers.Serializer):
698
+
699
+ domain_count = serializers.IntegerField()
700
+ domain_alias_count = serializers.IntegerField()
701
+ account_count = serializers.IntegerField()
702
+ alias_count = serializers.IntegerField()
@@ -658,3 +658,21 @@ class NewsFeedAPIViewTestCase(ModoAPITestCase):
658
658
  response = self.client.get(url)
659
659
  self.assertEqual(response.status_code, 200)
660
660
  self.assertIn("modoboa", response.json()[0]["link"])
661
+
662
+
663
+ class StatisticsAPIViewTestCase(ModoAPITestCase):
664
+
665
+ @classmethod
666
+ def setUpTestData(cls):
667
+ """Create test data."""
668
+ super().setUpTestData()
669
+ factories.populate_database()
670
+
671
+ def test_get_statistics(self):
672
+ url = reverse("v2:statistics")
673
+ response = self.client.get(url)
674
+ self.assertEqual(response.status_code, 200)
675
+ stats = response.json()
676
+ self.assertEqual(stats["domain_count"], 2)
677
+ self.assertEqual(stats["account_count"], 5)
678
+ self.assertEqual(stats["alias_count"], 3)
@@ -42,6 +42,11 @@ urlpatterns += [
42
42
  views.NewsFeedAPIView.as_view(),
43
43
  name="news-feed",
44
44
  ),
45
+ path(
46
+ "admin/statistics/",
47
+ views.StatisticsAPIView.as_view(),
48
+ name="statistics",
49
+ ),
45
50
  path("capabilities/", views.CapabilitiesAPIView.as_view(), name="capabilities"),
46
51
  path("theme/", views.ThemeAPIView.as_view(), name="theme"),
47
52
  ]
@@ -14,7 +14,8 @@ from drf_spectacular.utils import extend_schema
14
14
  from rest_framework import permissions, response
15
15
  from rest_framework.views import APIView
16
16
 
17
- from modoboa.core import signals
17
+ from modoboa.admin import models as admin_models
18
+ from modoboa.core import models, signals
18
19
  from modoboa.core.utils import check_for_updates, get_capabilities
19
20
  from modoboa.lib.permissions import IsSuperUser, IsPrivilegedUser
20
21
  from modoboa.lib.throttle import (
@@ -226,3 +227,21 @@ class NewsFeedAPIView(APIView):
226
227
 
227
228
  serializer = serializers.NewsFeedEntrySerializer(entries, many=True)
228
229
  return response.Response(serializer.data)
230
+
231
+
232
+ class StatisticsAPIView(APIView):
233
+ """Return some statistics about this modoboa instance."""
234
+
235
+ permission_classes = [permissions.IsAuthenticated, IsSuperUser]
236
+ throttle_classes = [UserLesserDdosUser]
237
+
238
+ @extend_schema(responses=serializers.StatisticsSerializer())
239
+ def get(self, request, *args, **kwargs):
240
+ data = {
241
+ "domain_count": admin_models.Domain.objects.count(),
242
+ "domain_alias_count": admin_models.DomainAlias.objects.count(),
243
+ "account_count": models.User.objects.count(),
244
+ "alias_count": admin_models.Alias.objects.filter(internal=False).count(),
245
+ }
246
+ serializer = serializers.StatisticsSerializer(data)
247
+ return response.Response(serializer.data)
@@ -649,7 +649,7 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict(
649
649
  "new_versions_email_rcpt",
650
650
  {
651
651
  "label": gettext_lazy("Recipient"),
652
- "display": "check_new_versions=true",
652
+ "display": "send_new_versions_email=true",
653
653
  "help_text": gettext_lazy(
654
654
  "Recipient of new versions notification emails."
655
655
  ),
@@ -720,6 +720,17 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict(
720
720
  ),
721
721
  },
722
722
  ),
723
+ (
724
+ "message_history_maximum_age",
725
+ {
726
+ "label": gettext_lazy(
727
+ "Retention time in message history"
728
+ ),
729
+ "help_text": gettext_lazy(
730
+ "Retention time (in days) of a message in the message history section"
731
+ ),
732
+ },
733
+ ),
723
734
  (
724
735
  "items_per_page",
725
736
  {
@@ -34,10 +34,9 @@ def begin_registration(request):
34
34
  ),
35
35
  list(get_creds_from_user(request.user.pk).values()),
36
36
  user_verification=UserVerificationRequirement.DISCOURAGED,
37
- extensions={"credentialProtectionPolicy": "userVerificationOptional"},
38
37
  )
39
38
  request.session["fido2_state"] = state
40
- return options
39
+ return dict(options)
41
40
 
42
41
 
43
42
  def end_registration(request):
@@ -5,6 +5,7 @@ from django.core.management.base import BaseCommand
5
5
  from django.utils import timezone
6
6
 
7
7
  from modoboa.core.models import Log
8
+ from modoboa.maillog.models import Maillog
8
9
  from modoboa.parameters import tools as param_tools
9
10
 
10
11
 
@@ -41,4 +42,12 @@ class Command(BaseCommand):
41
42
  self.__vprint(f"Deleting logs older than {log_maximum_age} days...")
42
43
  limit = timezone.now() - datetime.timedelta(log_maximum_age)
43
44
  Log.objects.filter(date_created__lt=limit).delete()
45
+ message_history_maximum_age = param_tools.get_global_parameter(
46
+ "message_history_maximum_age"
47
+ )
48
+ self.__vprint(
49
+ f"Deleting messages in history older than {message_history_maximum_age} days..."
50
+ )
51
+ limit = timezone.now() - datetime.timedelta(message_history_maximum_age)
52
+ Maillog.objects.filter(date__lt=limit).delete()
44
53
  self.__vprint("Done.")
@@ -159,12 +159,6 @@ class Command(BaseCommand):
159
159
  os.path.dirname(__file__), "../../../frontend_dist/"
160
160
  )
161
161
  frontend_target_dir = f"{settings.BASE_DIR}/frontend"
162
- if hasattr(settings, "MODOBOA_CUSTOM_LOGO"):
163
- menu_logo_path = settings.MODOBOA_CUSTOM_LOGO
164
- form_logo_path = settings.MODOBOA_CUSTOM_LOGO
165
- else:
166
- menu_logo_path = f"{frontend_target_dir}/assets/Modoboa_RVB-BLANC-SANS.png"
167
- form_logo_path = f"{frontend_target_dir}/assets/Modoboa_RVB-BLEU-SANS.png"
168
162
  if os.path.isdir(base_frontend_dir):
169
163
  shutil.rmtree(frontend_target_dir, ignore_errors=True)
170
164
  os.makedirs(frontend_target_dir, exist_ok=True)
@@ -178,13 +172,13 @@ class Command(BaseCommand):
178
172
  api_base_url = "/api/v2"
179
173
  api_doc_url = "/api/schema-v2/swagger/"
180
174
  oauth_authority_url = "/api/o"
181
- if options["relative_urls_in_config"]:
182
- api_base_url += base_uri
183
- api_doc_url += base_uri
184
- oauth_authority_url += base_uri
185
- oauth_post_logout_redirect_uri = ""
186
- else:
175
+ if not options["relative_urls_in_config"]:
176
+ api_base_url = f"{base_uri}{api_base_url}"
177
+ api_doc_url = f"{base_uri}{base_uri}"
178
+ oauth_authority_url = f"{base_uri}{oauth_authority_url}"
187
179
  oauth_post_logout_redirect_uri = base_uri
180
+ else:
181
+ oauth_post_logout_redirect_uri = ""
188
182
 
189
183
  with open(f"{frontend_target_dir}/config.json", "w") as fp:
190
184
  fp.write(
@@ -194,9 +188,7 @@ class Command(BaseCommand):
194
188
  "OAUTH_AUTHORITY_URL": "{oauth_authority_url}",
195
189
  "OAUTH_CLIENT_ID": "{client_id}",
196
190
  "OAUTH_REDIRECT_URI": "{redirect_uri}",
197
- "OAUTH_POST_REDIRECT_URI": "{oauth_post_logout_redirect_uri}",
198
- "MENU_LOGO_PATH": "{menu_logo_path}",
199
- "CREATION_FORM_LOGO_PATH": "{form_logo_path}"
191
+ "OAUTH_POST_REDIRECT_URI": "{oauth_post_logout_redirect_uri}"
200
192
  }}
201
193
  """
202
194
  )
@@ -9,9 +9,9 @@ class Migration(migrations.Migration):
9
9
  ]
10
10
 
11
11
  operations = [
12
- migrations.RenameIndex(
13
- model_name="user",
14
- new_name="core_user_email_c0c03f_idx",
15
- old_fields=("email", "is_active"),
16
- ),
12
+ # migrations.RenameIndex(
13
+ # model_name="user",
14
+ # new_name="core_user_email_c0c03f_idx",
15
+ # old_fields=("email", "is_active"),
16
+ # ),
17
17
  ]
@@ -16,6 +16,7 @@ from django.urls import reverse
16
16
  from django.utils import timezone
17
17
 
18
18
  from modoboa.lib.tests import ModoTestCase, SimpleModoTestCase
19
+ from modoboa.maillog import factories as maillog_factories, models as maillog_models
19
20
  from .. import factories, mocks, models
20
21
 
21
22
 
@@ -60,12 +61,16 @@ class ManagementCommandsTestCase(SimpleModoTestCase):
60
61
 
61
62
  def test_clean_logs(self):
62
63
  """Run cleanlogs command."""
64
+ management.call_command("load_initial_data", "--no-frontend")
63
65
  log1 = factories.LogFactory()
64
66
  factories.LogFactory()
65
67
  log1.date_created -= relativedelta(years=2)
66
68
  log1.save(update_fields=["date_created"])
69
+ maillog_factories.MaillogFactory(date=timezone.now() - relativedelta(days=190))
70
+ maillog_factories.MaillogFactory()
67
71
  management.call_command("cleanlogs")
68
72
  self.assertEqual(models.Log.objects.count(), 1)
73
+ self.assertEqual(maillog_models.Maillog.objects.count(), 1)
69
74
 
70
75
  def test_init_data_non_duplicate_client_creation(self):
71
76
  """Test that the load_initial_data command does not create duplicates
modoboa/core/utils.py CHANGED
@@ -111,4 +111,7 @@ def get_capabilities():
111
111
  if is_rspamd_installed:
112
112
  rspamd_options = get_rspamd_options()
113
113
  capabilities.update({"rspamd": rspamd_options})
114
+ # IMAP migration
115
+ if "modoboa.imap_migration" in settings.MODOBOA_APPS:
116
+ capabilities.update({"imap_migration": {}})
114
117
  return capabilities
@@ -7,22 +7,22 @@ from modoboa.admin import models as admin_models
7
7
  from ... import models
8
8
 
9
9
 
10
- class MXRecordSerializer(serializers.ModelSerializer):
11
- """Serializer for MXRecord."""
10
+ class DNSBLResultSerializer(serializers.ModelSerializer):
11
+ """Serializer for DNSBLResult."""
12
12
 
13
13
  class Meta:
14
- model = admin_models.MXRecord
15
- fields = ("name", "address", "updated")
14
+ model = admin_models.DNSBLResult
15
+ fields = ("provider", "status")
16
16
 
17
17
 
18
- class DNSBLResultSerializer(serializers.ModelSerializer):
19
- """Serializer for DNSBLResult."""
18
+ class MXRecordSerializer(serializers.ModelSerializer):
19
+ """Serializer for MXRecord."""
20
20
 
21
- mx = MXRecordSerializer()
21
+ dnsbl_results = DNSBLResultSerializer(many=True, source="active_dnsbl_results")
22
22
 
23
23
  class Meta:
24
- model = admin_models.DNSBLResult
25
- fields = ("provider", "mx", "status")
24
+ model = admin_models.MXRecord
25
+ fields = ("name", "address", "dnsbl_results", "updated")
26
26
 
27
27
 
28
28
  class DNSRecordSerializer(serializers.ModelSerializer):
@@ -41,13 +41,11 @@ class DNSDetailSerializer(serializers.ModelSerializer):
41
41
  spf_record = DNSRecordSerializer()
42
42
  dkim_record = DNSRecordSerializer()
43
43
  dmarc_record = DNSRecordSerializer()
44
- dnsbl_results = DNSBLResultSerializer(many=True, source="dnsblresult_set")
45
44
 
46
45
  class Meta:
47
46
  model = admin_models.Domain
48
47
  fields = (
49
48
  "mx_records",
50
- "dnsbl_results",
51
49
  "autoconfig_record",
52
50
  "autodiscover_record",
53
51
  "spf_record",