Repository: modoboa/modoboa (language: python)

Issue:
[Bug] Settings don't load on new-admin after reloading the page
On modoboa 2.2.0, if the user reloads the page, it seems that the parameters.getApplications() never gets called and users have no option for the settings drawer.


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index fbe718e92..d78a32620 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,4 @@ test_project/modoboa.db
 
 # Misc
 .ash_history
+modoboa.kdev4
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index cdd49f63a..fc1870a5a 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -7,7 +7,7 @@
   </template>
 
   <template v-else>
-    <navbar />
+    <navbar v-if="isAuthenticated" />
     <v-main>
       <v-container fluid>
         <transition name="fade">
diff --git a/frontend/src/components/domains/DNSDetail.vue b/frontend/src/components/domains/DNSDetail.vue
index 02a8207a7..d9340d561 100644
--- a/frontend/src/components/domains/DNSDetail.vue
+++ b/frontend/src/components/domains/DNSDetail.vue
@@ -122,7 +122,9 @@
           </v-chip>
         </v-col>
       </v-row>
-      <v-row v-if="value.enable_dkim">
+    </template>
+    <template v-if="value.enable_dkim">
+      <v-row>
         <v-col cols="3"><translate>DKIM key</translate></v-col>
         <v-col cols="9">
           <div v-if="value.dkim_private_key_path && value.dkim_public_key">
diff --git a/frontend/src/components/domains/DomainCreationForm.vue b/frontend/src/components/domains/DomainCreationForm.vue
index f2b4679f2..ebcf375ef 100644
--- a/frontend/src/components/domains/DomainCreationForm.vue
+++ b/frontend/src/components/domains/DomainCreationForm.vue
@@ -92,7 +92,7 @@ export default {
           items: [
             { key: this.$gettext('Quota'), value: this.domain.quota },
             { key: this.$gettext('Default mailbox quota'), value: this.domain.default_mailbox_quota },
-            { key: this.$gettext('Message sending limit'), value: this.domain.message_sending_limit }
+            { key: this.$gettext('Message sending limit'), value: this.domain.message_limit }
           ]
         }
       ]
diff --git a/frontend/src/components/domains/DomainDNSForm.vue b/frontend/src/components/domains/DomainDNSForm.vue
index ec08d7596..de54e36f7 100644
--- a/frontend/src/components/domains/DomainDNSForm.vue
+++ b/frontend/src/components/domains/DomainDNSForm.vue
@@ -3,12 +3,12 @@
   <v-switch
     :label="'Enable DNS checks' | translate"
     v-model="form.enable_dns_checks"
-    @input="update"
+    @change="update"
     />
   <v-switch
     :label="'Enable DKIM signing' | translate"
     v-model="form.enable_dkim"
-    @input="update"
+    @change="update"
     />
   <v-text-field
     :label="'DKIM key selector' | translate"
diff --git a/frontend/src/components/domains/DomainForm.vue b/frontend/src/components/domains/DomainForm.vue
index 35f4156ac..8f53b75b3 100644
--- a/frontend/src/components/domains/DomainForm.vue
+++ b/frontend/src/components/domains/DomainForm.vue
@@ -89,8 +89,8 @@
                 <v-col cols="6">
                   <translate class="mr-2">Quota: </translate> {{ domain.quota }}
                 </v-col>
-                <v-col cols="6" v-if="domain.message_sending_limit">
-                  <translate class="mr-2">Sending limit: </translate> {{ domain.message_sending_limit }}
+                <v-col cols="6" v-if="domain.message_limit">
+                  <translate class="mr-2">Sending limit: </translate> {{ domain.message_limit }}
                 </v-col>
                 <v-col cols="6" v-else>
                   <translate class="mr-2">No sending limit</translate>
@@ -207,7 +207,7 @@ export default {
       }
       this.working = true
       try {
-        const data = { ...this.editedDomain }
+        const data = JSON.parse(JSON.stringify(this.editedDomain))
         if (data.transport === null) {
           delete data.transport
         }
@@ -231,7 +231,7 @@ export default {
     domain: {
       handler: function (val) {
         if (val) {
-          this.editedDomain = JSON.parse(JSON.stringify(val))
+          this.editedDomain = { ...val }
         }
       },
       immediate: true
diff --git a/frontend/src/components/domains/DomainLimitationsForm.vue b/frontend/src/components/domains/DomainLimitationsForm.vue
index ba4b23e16..82ce575cd 100644
--- a/frontend/src/components/domains/DomainLimitationsForm.vue
+++ b/frontend/src/components/domains/DomainLimitationsForm.vue
@@ -38,7 +38,7 @@
       :label="'Message sending limit' | translate"
       :hint="'Number of messages this domain can send per day. Leave empty for no limit.' | translate"
       persistent-hint
-      v-model="form.message_sending_limit"
+      v-model="form.message_limit"
       :error-messages="errors"
       outlined
       @input="update"
@@ -49,9 +49,7 @@
 
 <script>
 export default {
-  props: {
-    value: Object
-  },
+  props: ['value'],
   data () {
     return {
       form: {}
@@ -65,12 +63,18 @@ export default {
       this.$emit('input', this.form)
     }
   },
+  mounted () {
+    this.form = { ...this.value }
+  },
   watch: {
     value: {
-      handler: function (newValue) {
-        this.form = { ...this.value }
+      handler (newValue) {
+        if (newValue.message_limit === '') {
+          newValue.message_limit = null
+        }
+        this.form = { ...newValue }
       },
-      immediate: true
+      deep: true
     }
   }
 }
diff --git a/frontend/src/components/domains/DomainSummary.vue b/frontend/src/components/domains/DomainSummary.vue
index 6567459f5..f94d24670 100644
--- a/frontend/src/components/domains/DomainSummary.vue
+++ b/frontend/src/components/domains/DomainSummary.vue
@@ -14,15 +14,17 @@
     </v-row>
     <v-row>
       <v-col cols="6"><translate>Quota</translate></v-col>
-      <v-col cols="6">{{ domain.quota }} <translate>MB</translate></v-col>
+      <v-col cols="6" v-if="domain.quota === '0'"><translate>Unlimited</translate></v-col>
+      <v-col cols="6" v-else>{{ domain.quota }} <translate>MB</translate></v-col>
     </v-row>
     <v-row>
       <v-col cols="6"><translate>Default mailbox quota</translate></v-col>
-      <v-col cols="6">{{ domain.default_mailbox_quota }} MB</v-col>
+      <v-col cols="6" v-if="domain.default_mailbox_quota === '0'"><translate>Unlimited</translate></v-col>
+      <v-col cols="6" v-else>{{ domain.default_mailbox_quota }} MB</v-col>
     </v-row>
-    <v-row v-if="domain.sending_limit !== undefined">
+    <v-row v-if="domain.message_limit !== undefined">
       <v-col cols="6"><translate>Daily sending limit</translate></v-col>
-      <v-col cols="6">{{ domain.sending_limit }} <translate>messages</translate></v-col>
+      <v-col cols="6">{{ domain.message_limit }} <translate>messages</translate></v-col>
     </v-row>
     <v-row>
       <v-col cols="6"><translate>Mailboxes</translate></v-col>
diff --git a/frontend/src/components/identities/AccountCreationForm.vue b/frontend/src/components/identities/AccountCreationForm.vue
index e52a84476..8b05020f8 100644
--- a/frontend/src/components/identities/AccountCreationForm.vue
+++ b/frontend/src/components/identities/AccountCreationForm.vue
@@ -50,6 +50,7 @@
 </template>
 
 <script>
+import { mapGetters } from 'vuex'
 import { bus } from '@/main'
 import accounts from '@/api/accounts'
 import AccountAliasForm from './AccountAliasForm'
@@ -70,6 +71,9 @@ export default {
     needsMailbox () {
       return this.account.role !== 'SuperAdmins' || (this.account.username && this.account.username.indexOf('@') !== -1)
     },
+    ...mapGetters({
+      authUser: 'auth/authUser'
+    }),
     summarySections () {
       const result = [
         {
diff --git a/frontend/src/components/identities/AccountForm.vue b/frontend/src/components/identities/AccountForm.vue
index e20ed7ba8..3564c4bc7 100644
--- a/frontend/src/components/identities/AccountForm.vue
+++ b/frontend/src/components/identities/AccountForm.vue
@@ -137,15 +137,37 @@
     <v-expansion-panel
       v-if="limitsConfig.params && limitsConfig.params.enable_admin_limits && account.role !== 'SimpleUsers' && account.role !== 'SuperAdmins'"
       >
-      <v-expansion-panel-header>
+      <v-expansion-panel-header v-slot="{ open }">
         <v-row no-gutters>
           <v-col cols="4">
             <translate>Resources</translate>
           </v-col>
+          <v-col
+            cols="8"
+            class="text--secondary"
+            >
+            <v-fade-transition leave-absolute>
+              <span v-if="open"></span>
+              <v-row
+                v-else
+                no-gutters
+                style="width: 100%"
+                >
+                <template v-if="account.resources != undefined && account.resources.length == 2">
+                  <v-col cols="6">
+                    <translate class="mr-2">Mailbox: </translate> <translate>Number of allowed mailboxes: </translate> {{ account.resources[0].max_value}}
+                  </v-col>
+                  <v-col cols="6">
+                  <translate class="mr-2">Mailbox aliases: </translate> <translate>Number of allowed mailbox aliases: </translate> {{ account.resources[1].max_value}}
+                  </v-col>
+                </template>
+              </v-row>
+            </v-fade-transition>
+          </v-col>
         </v-row>
       </v-expansion-panel-header>
       <v-expansion-panel-content>
-        <resources-form ref="resourcesForm" :resources="editedAccount.resources" />
+        <resources-form ref="resourcesForm" v-model="editedAccount.resources" />
       </v-expansion-panel-content>
     </v-expansion-panel>
   </v-expansion-panels>
@@ -161,6 +183,7 @@
 </template>
 
 <script>
+import { mapGetters } from 'vuex'
 import _isEmpty from 'lodash/isEmpty'
 import { bus } from '@/main'
 import accounts from '@/api/accounts'
@@ -183,7 +206,10 @@ export default {
   computed: {
     usernameIsEmail () {
       return this.editedAccount.username && this.editedAccount.username.indexOf('@') !== -1
-    }
+    },
+    ...mapGetters({
+      authUser: 'auth/authUser'
+    })
   },
   data () {
     return {
diff --git a/frontend/src/components/identities/AccountRoleForm.vue b/frontend/src/components/identities/AccountRoleForm.vue
index a56812bf3..955f8227b 100644
--- a/frontend/src/components/identities/AccountRoleForm.vue
+++ b/frontend/src/components/identities/AccountRoleForm.vue
@@ -29,12 +29,13 @@
 
 <script>
 import ChoiceField from '@/components/tools/ChoiceField'
+import { mapGetters } from 'vuex'
 
 export default {
   components: {
     ChoiceField
   },
-  props: ['value', 'account'],
+  props: ['value', 'account', 'role'],
   computed: {
     roleLabel () {
       const role = this.accountRoles.find(role => role.value === this.account.role)
@@ -43,29 +44,49 @@ export default {
     roleHelp () {
       const role = this.accountRoles.find(role => role.value === this.account.role)
       return role !== undefined ? role.help : ''
-    }
+    },
+    accountRoles () {
+      if (this.authUser.role === 'SuperAdmins') {
+        return [...this.domainAdminsRole, ...this.resellerRole, ...this.simpleUserRole, ...this.superAdminsRole]
+      } else if (this.authUser.role === 'DomainAdmins') {
+        return this.simpleUserRole
+      } else if (this.authUser.role === 'Resellers') {
+        return [...this.domainAdminsRole, ...this.simpleUserRole]
+      } else {
+        return []
+      }
+    },
+    ...mapGetters({
+      authUser: 'auth/authUser'
+    })
   },
   methods: {
     reset () { }
   },
   data () {
     return {
-      accountRoles: [
+      simpleUserRole: [
+        {
+          label: this.$gettext('Simple user'),
+          value: 'SimpleUsers',
+          help: this.$gettext('A user with no privileges but with a mailbox. He will be allowed to use all end-user features: webmail, calendar, contacts, filters.')
+        }
+      ],
+      domainAdminsRole: [
         {
           label: this.$gettext('Domain administrator'),
           value: 'DomainAdmins',
           help: this.$gettext('A user with privileges on one or more domain. He will be allowed to administer mailboxes and he can also have a mailbox.')
-        },
+        }
+      ],
+      resellerRole: [
         {
           label: this.$gettext('Reseller'),
           value: 'Resellers',
           help: this.$gettext('help')
-        },
-        {
-          label: this.$gettext('Simple user'),
-          value: 'SimpleUsers',
-          help: this.$gettext('A user with no privileges but with a mailbox. He will be allowed to use all end-user features: webmail, calendar, contacts, filters.')
-        },
+        }
+      ],
+      superAdminsRole: [
         {
           label: this.$gettext('Super administrator'),
           value: 'SuperAdmins',
diff --git a/frontend/src/components/identities/AccountSummary.vue b/frontend/src/components/identities/AccountSummary.vue
index 16f78b8d6..ac3571872 100644
--- a/frontend/src/components/identities/AccountSummary.vue
+++ b/frontend/src/components/identities/AccountSummary.vue
@@ -32,7 +32,10 @@
     <template v-if="account.mailbox">
       <v-row>
         <v-col cols="6"><translate>Quota</translate></v-col>
-        <v-col cols="6">
+        <v-col cols="6" v-if="account.mailbox.quota === '0'">
+          <translate>Unlimited</translate>
+        </v-col>
+        <v-col cols="6" v-else>
           <v-progress-linear
             :color="account.mailbox.quota_usage < 80 ? 'primary' : 'warning'"
             :value="account.mailbox.quota_usage"
diff --git a/frontend/src/components/layout/Navbar.vue b/frontend/src/components/layout/Navbar.vue
index b8b2da4f9..078d728e1 100644
--- a/frontend/src/components/layout/Navbar.vue
+++ b/frontend/src/components/layout/Navbar.vue
@@ -284,20 +284,18 @@ export default {
     }
   },
   created () {
-    if (this.isAuthenticated) {
-      parameters.getApplications().then(response => {
-        response.data.forEach(item => {
-          this.mainMenuItems[6].children.push({
-            text: item.label,
-            to: { name: 'ParametersEdit', params: { app: item.name } }
-          })
+    parameters.getApplications().then(response => {
+      response.data.forEach(item => {
+        this.mainMenuItems[6].children.push({
+          text: item.label,
+          to: { name: 'ParametersEdit', params: { app: item.name } }
         })
       })
-      parameters.getApplication('imap_migration').then(response => {
-        this.imapMigration = response.data.params.enabled_imapmigration
-      })
-      bus.$on('imapSettingsChanged', (status) => { this.imapMigration = status })
-    }
+    })
+    parameters.getApplication('imap_migration').then(response => {
+      this.imapMigration = response.data.params.enabled_imapmigration
+    })
+    bus.$on('imapSettingsChanged', (status) => { this.imapMigration = status })
   },
   methods: {
     displayMenuItem (item) {
diff --git a/modoboa/admin/api/v1/serializers.py b/modoboa/admin/api/v1/serializers.py
index c9d2ea9f3..c40d89c06 100644
--- a/modoboa/admin/api/v1/serializers.py
+++ b/modoboa/admin/api/v1/serializers.py
@@ -109,6 +109,15 @@ def create(self, validated_data):
         domain.save(creator=creator)
         return domain
 
+    def update(self, instance, validated_data):
+        """Update domain and create/update transport."""
+        super().update(instance, validated_data)
+        _enable_dkim = validated_data.get("enable_dkim")
+        if _enable_dkim is False and instance.dkim_private_key_path != "":
+            instance.dkim_private_key_path = ""
+            instance.save()
+        return instance
+
 
 class DomainAliasSerializer(serializers.ModelSerializer):
     """Base DomainAlias serializer."""
diff --git a/modoboa/admin/api/v1/viewsets.py b/modoboa/admin/api/v1/viewsets.py
index d417253e7..892be34d0 100644
--- a/modoboa/admin/api/v1/viewsets.py
+++ b/modoboa/admin/api/v1/viewsets.py
@@ -36,7 +36,9 @@
         summary="Create a new domain"
     )
 )
-class DomainViewSet(GetThrottleViewsetMixin, lib_viewsets.RevisionModelMixin, viewsets.ModelViewSet):
+class DomainViewSet(GetThrottleViewsetMixin,
+                    lib_viewsets.RevisionModelMixin,
+                    viewsets.ModelViewSet):
     """Domain viewset."""
 
     permission_classes = [IsAuthenticated, DjangoModelPermissions, ]
diff --git a/modoboa/admin/forms/domain.py b/modoboa/admin/forms/domain.py
index 01375d217..c14bf067f 100644
--- a/modoboa/admin/forms/domain.py
+++ b/modoboa/admin/forms/domain.py
@@ -220,6 +220,9 @@ def save(self, user, commit=True, domalias_post_create=False):
                 options = {"creator": user} if domalias_post_create else {}
                 DomainAlias(name=alias, target=d, enabled=d.enabled).save(
                     **options)
+        if self.cleaned_data.get("enable_dkim") is False and d.dkim_private_key_path != "":
+            d.dkim_private_key_path = ""
+            d.save()
         return d
 
 
diff --git a/modoboa/admin/management/commands/subcommands/_manage_dkim_keys.py b/modoboa/admin/management/commands/subcommands/_manage_dkim_keys.py
index 7c021b83b..e528809ed 100644
--- a/modoboa/admin/management/commands/subcommands/_manage_dkim_keys.py
+++ b/modoboa/admin/management/commands/subcommands/_manage_dkim_keys.py
@@ -74,13 +74,9 @@ def handle(self, *args, **options):
                                                   dkim_private_key_path="")
             if domain.exists():
                 self.create_new_dkim_key(domain[0])
-                signals.new_dkim_keys.send(sender=self.__class__,
-                                           domains=domain)
             return
 
         qset = models.Domain.objects.filter(
             enable_dkim=True, dkim_private_key_path="")
         for domain in qset:
             self.create_new_dkim_key(domain)
-        if qset.exists():
-            signals.new_dkim_keys.send(sender=self.__class__, domains=qset)
diff --git a/modoboa/admin/signals.py b/modoboa/admin/signals.py
index 756ddb9c8..f5fdd32d4 100644
--- a/modoboa/admin/signals.py
+++ b/modoboa/admin/signals.py
@@ -19,5 +19,4 @@
 # get_domain_tags = django.dispatch.Signal(providing_args=["domain"])
 import_object = django.dispatch.Signal() # Provides objtype
 use_external_recipients = django.dispatch.Signal() # Provides recipients
-new_dkim_keys = django.dispatch.Signal() # Provides domain
 extra_account_identities_actions = django.dispatch.Signal() # Provides account
diff --git a/modoboa/pdfcredentials/handlers.py b/modoboa/pdfcredentials/handlers.py
index ba8e1ae53..cf8dfb360 100644
--- a/modoboa/pdfcredentials/handlers.py
+++ b/modoboa/pdfcredentials/handlers.py
@@ -1,5 +1,6 @@
 """PDF credentials handlers."""
 
+import logging
 import os
 
 from django.urls import reverse
@@ -10,6 +11,7 @@
 from modoboa.admin import signals as admin_signals
 from modoboa.core import models as core_models
 from modoboa.core import signals as core_signals
+from modoboa.lib.exceptions import InternalError
 from modoboa.parameters import tools as param_tools
 
 from .documents import credentials
@@ -25,7 +27,13 @@ def password_updated(sender, account, password, created, **kwargs):
         "generate_at_creation")
     if (generate_at_creation and not created) or account.is_superuser:
         return
-    init_storage_dir()
+    try:
+        init_storage_dir()
+    except InternalError as e:
+        logger = logging.getLogger("modoboa.admin")
+        logger.error(
+            _("Failed to create PDF_credentials directory. "
+            "Please check the permissions or the path."))
     credentials(account, password)
 
 
diff --git a/modoboa/pdfcredentials/lib.py b/modoboa/pdfcredentials/lib.py
index 363703855..895e202ca 100644
--- a/modoboa/pdfcredentials/lib.py
+++ b/modoboa/pdfcredentials/lib.py
@@ -1,6 +1,7 @@
 """Internal library."""
 
 from io import BytesIO
+import logging
 import os
 import struct
 
@@ -18,7 +19,7 @@
 
 
 def init_storage_dir():
-    """Create the directory whare documents will be stored."""
+    """Create the directory where documents will be stored."""
     storage_dir = param_tools.get_global_parameter("storage_dir")
     if os.path.exists(storage_dir) and os.access(storage_dir, os.W_OK):
         return
@@ -66,17 +67,23 @@ def crypt_and_save_to_file(content, filename, length, chunksize=64*512):
     iv = os.urandom(16)
     cipher = _get_cipher(iv)
     encryptor = cipher.encryptor()
-    with open(filename, "wb") as fp:
-        fp.write(struct.pack(b"<Q", length))
-        fp.write(iv)
-        while True:
-            chunk = content.read(chunksize)
-            if not len(chunk):
-                break
-            elif len(chunk) % 16:
-                chunk += b" " * (16 - len(chunk) % 16)
-            fp.write(encryptor.update(force_bytes(chunk)))
-        fp.write(encryptor.finalize())
+    try:
+        with open(filename, "wb") as fp:
+            fp.write(struct.pack(b"<Q", length))
+            fp.write(iv)
+            while True:
+                chunk = content.read(chunksize)
+                if not len(chunk):
+                    break
+                elif len(chunk) % 16:
+                    chunk += b" " * (16 - len(chunk) % 16)
+                fp.write(encryptor.update(force_bytes(chunk)))
+            fp.write(encryptor.finalize())
+    except FileNotFoundError as e:
+        logger = logging.getLogger("modoboa.admin")
+        logger.error(
+            _("Failed to create PDF_credentials file. "
+            "Please check the permissions or the path."))
 
 
 def decrypt_file(filename, chunksize=24*1024):
diff --git a/pyproject.toml b/pyproject.toml
index 24352bf6d..519a26022 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -14,18 +14,19 @@ authors = [
 ]
 description = "Mail hosting made simple"
 readme = "README.rst"
-requires-python = ">=3.7"
+requires-python = ">=3.8"
 classifiers = [
     "Development Status :: 5 - Production/Stable",
     "Environment :: Web Environment",
-    "Framework :: Django :: 2.2",
+    "Framework :: Django :: 4.2",
     "Intended Audience :: System Administrators",
     "License :: OSI Approved :: ISC License (ISCL)",
     "Operating System :: OS Independent",
     "Programming Language :: Python :: 3",
-    "Programming Language :: Python :: 3.7",
     "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
     "Topic :: Communications :: Email",
     "Topic :: Internet :: WWW/HTTP",
 ]


Test patch:
diff --git a/modoboa/admin/api/v1/tests.py b/modoboa/admin/api/v1/tests.py
index 9545014ca..0c20b16a6 100644
--- a/modoboa/admin/api/v1/tests.py
+++ b/modoboa/admin/api/v1/tests.py
@@ -133,6 +133,16 @@ def test_patch_domain(self):
         domain.refresh_from_db()
         self.assertEqual(domain.name, "toto.test")
 
+    def test_remove_dkim_domain(self):
+        """Try to remove DKIM from a domain."""
+        domain = models.Domain.objects.get(name="test.com")
+        url = reverse("v1:domain-detail", args=[domain.pk])
+        response = self.client.put(
+            url, {"name": "test.com", "enable_dkim": False})
+        self.assertEqual(response.status_code, 200)
+        domain.refresh_from_db()
+        self.assertEqual(domain.dkim_private_key_path, "")
+
     def test_delete_domain(self):
         """Try to delete a domain."""
         domain = models.Domain.objects.get(name="test.com")
diff --git a/modoboa/admin/tests/test_domain.py b/modoboa/admin/tests/test_domain.py
index abf9593f4..e472ecef7 100644
--- a/modoboa/admin/tests/test_domain.py
+++ b/modoboa/admin/tests/test_domain.py
@@ -485,6 +485,19 @@ def test_dkim_key_creation(self):
         domain.refresh_from_db()
         self.assertNotEqual(domain.dkim_private_key_path, "")
 
+        # Try disabling DKIM and checking that dkim_private_key_path is emptied
+        values = {
+            "name": "pouet.com", "enable_dkim": False,
+            "quota": 1000, "default_mailbox_quota": 100,
+            "type": "domain"
+        }
+        self.ajax_post(
+            reverse("admin:domain_change", args=[domain.id]),
+            values
+        )
+        domain.refresh_from_db()
+        self.assertEqual(domain.dkim_private_key_path, "")
+
     def test_dkim_key_length_modification(self):
         """ """
         self.set_global_parameter("dkim_keys_storage_dir", self.workdir)
diff --git a/modoboa/pdfcredentials/tests/test_views.py b/modoboa/pdfcredentials/tests/test_views.py
index 885528ffa..48f2d9508 100644
--- a/modoboa/pdfcredentials/tests/test_views.py
+++ b/modoboa/pdfcredentials/tests/test_views.py
@@ -144,4 +144,11 @@ def test_download_and_delete_account(self):
     def test_storage_dir_creation(self):
         """Test storage directory creation."""
         self.set_global_parameter("storage_dir", "/nonexistentdir")
-        self._create_account("leon@test.com", expected_status=500)
+        with self.assertLogs(logger='modoboa.admin', level='ERROR') as log:
+            resp = self._create_account("leon@test.com")
+            self.assertIn(
+                "ERROR:modoboa.admin:Failed to create PDF_credentials directory. "
+                "Please check the permissions or the path.",
+                log.output
+            )
+