From db0b7eb79d4a91c4a218ce0031123da89190e776 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 18 Feb 2025 15:54:59 +0100 Subject: [PATCH] New fixes --- modoboa/admin/api/v1/tests.py | 18 +- modoboa/admin/api/v2/serializers.py | 6 +- modoboa/admin/factories.py | 2 +- modoboa/admin/forms/account.py | 8 +- modoboa/admin/forms/alias.py | 4 +- modoboa/admin/forms/domain.py | 22 +- modoboa/admin/handlers.py | 16 +- .../commands/handle_mailbox_operations.py | 14 +- .../commands/subcommands/_import.py | 9 +- .../commands/subcommands/_manage_dkim_keys.py | 16 +- .../management/commands/subcommands/_mx.py | 8 +- .../commands/subcommands/_repair.py | 10 +- modoboa/admin/models/alarm.py | 4 +- modoboa/admin/models/alias.py | 2 +- modoboa/admin/models/domain.py | 4 +- modoboa/admin/models/mailbox.py | 2 +- modoboa/admin/models/mxrecord.py | 4 +- modoboa/admin/postfix_maps.py | 10 +- modoboa/admin/tests/test_account.py | 8 +- modoboa/admin/tests/test_domain.py | 10 +- modoboa/admin/tests/test_domain_alias.py | 6 +- modoboa/admin/tests/test_import_.py | 6 +- .../admin/tests/test_mailbox_operations.py | 12 +- modoboa/admin/tests/test_mx.py | 2 +- modoboa/admin/tests/utils.py | 4 +- modoboa/admin/views/domain.py | 8 +- modoboa/admin/views/export.py | 2 +- modoboa/calendars/backends/__init__.py | 6 +- modoboa/calendars/backends/caldav_.py | 8 +- modoboa/calendars/handlers.py | 4 +- .../management/commands/generate_rights.py | 28 +- modoboa/calendars/mocks.py | 4 +- modoboa/calendars/models.py | 2 +- modoboa/calendars/tests.py | 34 +- modoboa/contacts/factories.py | 4 +- modoboa/contacts/lib/carddav.py | 6 +- modoboa/contacts/mocks.py | 8 +- modoboa/contacts/models.py | 10 +- modoboa/contacts/serializers.py | 2 +- modoboa/contacts/tests.py | 11 +- modoboa/core/api/v1/viewsets.py | 4 +- modoboa/core/api/v2/viewsets.py | 4 +- modoboa/core/commands/__init__.py | 2 +- modoboa/core/commands/deploy.py | 24 +- modoboa/core/extensions.py | 2 +- modoboa/core/factories.py | 8 +- .../commands/clean_inactive_accounts.py | 2 +- .../commands/generate_postfix_maps.py | 4 +- .../management/commands/load_initial_data.py | 4 +- modoboa/core/models.py | 2 +- modoboa/core/password_hashers/utils.py | 2 +- modoboa/core/password_validation.py | 2 +- modoboa/core/sms_backends/__init__.py | 4 +- modoboa/core/sms_backends/ovh.py | 2 +- modoboa/core/templatetags/core_tags.py | 4 +- modoboa/core/tests/test_authentication.py | 6 +- modoboa/core/tests/test_ldap.py | 2 +- modoboa/core/tests/test_views.py | 2 +- modoboa/core/tests/utils.py | 2 +- modoboa/core/views/dashboard.py | 4 +- modoboa/dmarc/lib.py | 10 +- .../commands/import_aggregated_report.py | 2 - modoboa/dmarc/models.py | 4 +- modoboa/dmarc/templatetags/dmarc_tags.py | 24 +- modoboa/dmarc/tests/mixins.py | 6 +- modoboa/dmarc/tests/test_views.py | 12 +- modoboa/dmarc/views.py | 8 +- modoboa/dnstools/lib.py | 12 +- modoboa/dnstools/models.py | 6 +- modoboa/imap_migration/__init__.py | 4 - modoboa/imap_migration/api/v2/viewsets.py | 3 +- modoboa/imap_migration/auth_backends.py | 11 +- modoboa/imap_migration/factories.py | 2 +- .../commands/generate_offlineimap_config.py | 2 +- modoboa/imap_migration/tests.py | 2 +- modoboa/ldapsync/lib.py | 23 +- modoboa/lib/api_client.py | 10 +- modoboa/lib/authbackends.py | 4 +- modoboa/lib/email_utils.py | 4 +- modoboa/lib/form_utils.py | 6 +- modoboa/lib/paginator.py | 4 +- modoboa/lib/sysutils.py | 2 +- modoboa/lib/templatetags/form_tags.py | 2 +- modoboa/lib/test_utils.py | 6 +- modoboa/lib/tests/__init__.py | 10 +- modoboa/lib/validators.py | 2 +- modoboa/lib/web_utils.py | 4 +- modoboa/limits/api/v1/tests.py | 16 +- modoboa/limits/forms.py | 8 +- modoboa/limits/handlers.py | 4 +- modoboa/limits/models.py | 12 +- modoboa/limits/tests/test_domain_limits.py | 6 +- modoboa/limits/tests/test_import.py | 58 +- modoboa/limits/tests/test_user_limits.py | 2 +- modoboa/maillog/api/v2/viewsets.py | 2 +- modoboa/maillog/factories.py | 6 +- modoboa/maillog/graphics.py | 6 +- modoboa/maillog/lib.py | 1 - .../maillog/management/commands/logparser.py | 10 +- .../management/commands/update_statistics.py | 2 +- modoboa/maillog/parser.py | 7 +- modoboa/maillog/tests/test_views.py | 28 +- modoboa/maillog/views.py | 2 - modoboa/parameters/tools.py | 4 +- modoboa/pdfcredentials/app_settings.py | 2 +- modoboa/pdfcredentials/lib.py | 4 +- modoboa/pdfcredentials/tests/test_lib.py | 4 +- modoboa/policyd/core.py | 6 +- .../management/commands/policy_daemon.py | 2 +- modoboa/postfix_autoreply/__init__.py | 4 - modoboa/postfix_autoreply/api/v2/tests.py | 10 +- modoboa/postfix_autoreply/factories.py | 4 - modoboa/postfix_autoreply/forms.py | 2 - modoboa/postfix_autoreply/handlers.py | 18 +- modoboa/postfix_autoreply/known_problems.py | 10 +- .../management/commands/autoreply.py | 7 +- modoboa/postfix_autoreply/models.py | 2 +- modoboa/postfix_autoreply/tests.py | 2 +- modoboa/postfix_autoreply/views.py | 2 - modoboa/relaydomains/api/v1/serializers.py | 6 +- modoboa/relaydomains/api/v1/tests.py | 2 +- modoboa/relaydomains/forms.py | 2 +- modoboa/relaydomains/postfix_maps.py | 6 +- modoboa/relaydomains/tests.py | 4 +- modoboa/sievefilters/__init__.py | 4 - modoboa/sievefilters/imaputils.py | 9 +- modoboa/sievefilters/mocks.py | 2 - modoboa/transport/backends.py | 6 +- modoboa/transport/factories.py | 2 +- modoboa/transport/forms.py | 16 +- modoboa/transport/postfix_maps.py | 2 +- pylintrc | 639 ------------------ pyproject.toml | 2 +- 133 files changed, 405 insertions(+), 1165 deletions(-) delete mode 100644 pylintrc diff --git a/modoboa/admin/api/v1/tests.py b/modoboa/admin/api/v1/tests.py index 69d3b0715..d443116de 100644 --- a/modoboa/admin/api/v1/tests.py +++ b/modoboa/admin/api/v1/tests.py @@ -161,7 +161,7 @@ class DomainAliasAPITestCase(ModoAPITestCase): @classmethod def setUpTestData(cls): # NOQA:N802 """Create test data.""" - super(DomainAliasAPITestCase, cls).setUpTestData() + super().setUpTestData() factories.populate_database() cls.dom_alias1 = factories.DomainAliasFactory( name="dalias1.com", target__name="test.com" @@ -185,7 +185,7 @@ class DomainAliasAPITestCase(ModoAPITestCase): self.assertEqual(response.data["name"], "dalias1.com") url = reverse("v1:domain_alias-list") - response = self.client.get("{}?domain=test.com".format(url)) + response = self.client.get(f"{url}?domain=test.com") self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 1) @@ -246,7 +246,7 @@ class AccountAPITestCase(ModoAPITestCase): @classmethod def setUpTestData(cls): # NOQA:N802 """Create test data.""" - super(AccountAPITestCase, cls).setUpTestData() + super().setUpTestData() factories.populate_database() cls.da_token = Token.objects.create( user=core_models.User.objects.get(username="admin@test.com") @@ -271,17 +271,17 @@ class AccountAPITestCase(ModoAPITestCase): response = response.json() self.assertEqual(len(response), 5) - response = self.client.get("{}?domain=test.com".format(url)) + response = self.client.get(f"{url}?domain=test.com") self.assertEqual(response.status_code, 200) response = response.json() self.assertEqual(len(response), 2) - response = self.client.get("{}?domain=pouet.com".format(url)) + response = self.client.get(f"{url}?domain=pouet.com") self.assertEqual(response.status_code, 200) response = response.json() self.assertEqual(len(response), 0) - response = self.client.get("{}?search=user@test.com".format(url)) + response = self.client.get(f"{url}?search=user@test.com") self.assertEqual(response.status_code, 200) response = response.json() self.assertEqual(len(response), 1) @@ -608,7 +608,7 @@ class AliasAPITestCase(ModoAPITestCase): @classmethod def setUpTestData(cls): # NOQA:N802 """Create test data.""" - super(AliasAPITestCase, cls).setUpTestData() + super().setUpTestData() cls.localconfig.parameters.set_value("enable_admin_limits", False, app="limits") cls.localconfig.save() factories.populate_database() @@ -624,7 +624,7 @@ class AliasAPITestCase(ModoAPITestCase): response = response.json() self.assertEqual(len(response), 3) - response = self.client.get("{}?domain=test.com".format(url)) + response = self.client.get(f"{url}?domain=test.com") self.assertEqual(response.status_code, 200) response = response.json() self.assertEqual(len(response), 3) @@ -744,7 +744,7 @@ class SenderAddressAPITestCase(ModoAPITestCase): @classmethod def setUpTestData(cls): # NOQA:N802 """Create test data.""" - super(SenderAddressAPITestCase, cls).setUpTestData() + super().setUpTestData() cls.localconfig.parameters.set_value("enable_admin_limits", False, app="limits") cls.localconfig.save() factories.populate_database() diff --git a/modoboa/admin/api/v2/serializers.py b/modoboa/admin/api/v2/serializers.py index d3de5bdad..547c3995e 100644 --- a/modoboa/admin/api/v2/serializers.py +++ b/modoboa/admin/api/v2/serializers.py @@ -136,7 +136,7 @@ class DomainSerializer(v1_serializers.DomainSerializer): core_signals.can_create_object.send( self.__class__, context=user, klass=models.Alias ) - address = "postmaster@{}".format(domain.name) + address = f"postmaster@{domain.name}" alias = models.Alias.objects.create( address=address, domain=domain, enabled=True ) @@ -364,9 +364,7 @@ class AccountMeSerializer(v1_serializers.AccountSerializer): permissions = ( user.user_permissions.all() | Permission.objects.filter(group__user=user) ).select_related("content_type") - return [ - "{}.{}".format(p.content_type.app_label, p.codename) for p in permissions - ] + return [f"{p.content_type.app_label}.{p.codename}" for p in permissions] class AccountSerializer(v1_serializers.AccountSerializer): diff --git a/modoboa/admin/factories.py b/modoboa/admin/factories.py index 2d59ff863..359c82f9a 100644 --- a/modoboa/admin/factories.py +++ b/modoboa/admin/factories.py @@ -15,7 +15,7 @@ class DomainFactory(PermissionFactory): model = models.Domain django_get_or_create = ("name",) - name = factory.Sequence(lambda n: "domain{}.test".format(n)) + name = factory.Sequence(lambda n: f"domain{n}.test") type = "domain" # NOQA:A003 quota = 0 default_mailbox_quota = 10 diff --git a/modoboa/admin/forms/account.py b/modoboa/admin/forms/account.py index cbfcffc52..39dcb78c7 100644 --- a/modoboa/admin/forms/account.py +++ b/modoboa/admin/forms/account.py @@ -292,12 +292,12 @@ class AccountFormMail(forms.Form, DynamicForm): self.fields["email"].required = True qset = self.mb.aliasrecipient_set.filter(alias__internal=False) for cpt, ralias in enumerate(qset): - name = "aliases_{}".format(cpt + 1) + name = f"aliases_{cpt + 1}" self._create_field( lib_fields.UTF8AndEmptyUserEmailField, name, ralias.alias.address ) for cpt, saddress in enumerate(self.mb.senderaddress_set.all()): - name = "senderaddress_{}".format(cpt + 1) + name = f"senderaddress_{cpt + 1}" self._create_field( lib_fields.UTF8AndEmptyUserEmailField, name, saddress.address ) @@ -336,14 +336,14 @@ class AccountFormMail(forms.Form, DynamicForm): try: self.domain = models.Domain.objects.get(name=domname) except models.Domain.DoesNotExist: - raise forms.ValidationError(_("Domain does not exist")) + raise forms.ValidationError(_("Domain does not exist")) from None if not self.mb: try: core_signals.can_create_object.send( sender=self.__class__, context=self.domain, object_type="mailboxes" ) except lib_exceptions.ModoboaException as inst: - raise forms.ValidationError(inst) + raise forms.ValidationError(inst) from None return email def clean_quota(self): diff --git a/modoboa/admin/forms/alias.py b/modoboa/admin/forms/alias.py index 33794bfdb..70636a72e 100644 --- a/modoboa/admin/forms/alias.py +++ b/modoboa/admin/forms/alias.py @@ -123,9 +123,7 @@ class AliasForm(forms.ModelForm, DynamicForm): """Custom save method.""" alias = super(AliasForm, self).save(commit=False) if self.cleaned_data.get("random_address"): - alias.address = "{}@{}".format( - Alias.generate_random_address(), alias.domain - ) + alias.address = f"{Alias.generate_random_address()}@{alias.domain}" else: local_part, domname = split_mailbox(self.cleaned_data["address"]) alias.domain = Domain.objects.get(name=domname) diff --git a/modoboa/admin/forms/domain.py b/modoboa/admin/forms/domain.py index e5e028527..85eb1ce2b 100644 --- a/modoboa/admin/forms/domain.py +++ b/modoboa/admin/forms/domain.py @@ -80,7 +80,7 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm): if "instance" in kwargs: self.old_dkim_key_length = kwargs["instance"].dkim_key_length self.oldname = kwargs["instance"].name - super(DomainFormGeneral, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) params = dict(param_tools.get_global_parameters("admin")) self.fields["quota"].initial = params["default_domain_quota"] if params["default_domain_message_limit"] is not None: @@ -97,7 +97,7 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm): elif "instance" in kwargs: d = kwargs["instance"] for pos, dalias in enumerate(d.domainalias_set.all()): - name = "aliases_%d" % (pos + 1) + name = f"aliases_{pos + 1}" self._create_field(forms.CharField, name, dalias.name, 3) def clean_name(self): @@ -148,7 +148,7 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm): The validation way is not very smart... """ - cleaned_data = super(DomainFormGeneral, self).clean() + cleaned_data = super().clean() if self._errors: return cleaned_data condition = ( @@ -203,7 +203,7 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm): quotas update. """ - d = super(DomainFormGeneral, self).save(commit=False) + d = super().save(commit=False) core_signals.can_create_object.send( sender=self.__class__, context=user, klass=Domain, instance=d ) @@ -280,7 +280,7 @@ class DomainFormOptions(forms.Form): ) def __init__(self, user, *args, **kwargs): - super(DomainFormOptions, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) results = core_signals.user_can_set_role.send( sender=self.__class__, user=user, role="DomainAdmins" ) @@ -296,7 +296,7 @@ class DomainFormOptions(forms.Form): def clean(self): """Check required values.""" - cleaned_data = super(DomainFormOptions, self).clean() + cleaned_data = super().clean() if cleaned_data.get("create_dom_admin"): if not cleaned_data.get("dom_admin_username"): self.add_error("dom_admin_username", _("This field is required.")) @@ -311,7 +311,7 @@ class DomainFormOptions(forms.Form): return user = kwargs.pop("user") domain = kwargs.pop("domain") - username = "%s@%s" % (self.cleaned_data["dom_admin_username"], domain.name) + username = f"{self.cleaned_data['dom_admin_username']}@{domain.name}" try: da = User.objects.get(username=username) except User.DoesNotExist: @@ -349,7 +349,7 @@ class DomainFormOptions(forms.Form): core_signals.can_create_object.send( self.__class__, context=user, klass=Alias ) - address = "postmaster@{}".format(domain.name) + address = f"postmaster@{domain.name}" alias = Alias.objects.create( address=address, domain=domain, enabled=True ) @@ -386,7 +386,7 @@ class DomainForm(TabForms): self.forms += reduce(lambda a, b: a + b, [result[1] for result in results]) if not self.forms: self.active_id = "admins" - super(DomainForm, self).__init__(request, *args, **kwargs) + super().__init__(request, *args, **kwargs) def extra_context(self, context): """Add information to template context.""" @@ -409,7 +409,7 @@ class DomainForm(TabForms): if "general" in self.instances: instance = self.instances["general"] instance.oldname = instance.name - return super(DomainForm, self).is_valid() + return super().is_valid() def save(self): """Custom save method. @@ -437,7 +437,7 @@ class DomainWizard(WizardForm): """Domain creation wizard.""" def __init__(self, request): - super(DomainWizard, self).__init__(request) + super().__init__(request) self.add_step( WizardStep( "general", diff --git a/modoboa/admin/handlers.py b/modoboa/admin/handlers.py index f2c7a686e..ba87d4507 100644 --- a/modoboa/admin/handlers.py +++ b/modoboa/admin/handlers.py @@ -27,13 +27,9 @@ def update_domain_mxs_and_mailboxes(sender, instance, **kwargs): ) if instance.old_mail_homes is None: return - qset = models.Quota.objects.filter( - username__contains="@{}".format(instance.oldname) - ) + qset = models.Quota.objects.filter(username__contains=f"@{instance.oldname}") for q in qset: - username = q.username.replace( - "@{}".format(instance.oldname), "@{}".format(instance.name) - ) + username = q.username.replace(f"@{instance.oldname}", f"@{instance.name}") models.Quota.objects.create( username=username, bytes=q.bytes, messages=q.messages ) @@ -62,17 +58,17 @@ def create_alias_for_domainalias(sender, instance, **kwargs): if not kwargs.get("created"): return alias = models.Alias.objects.create( - address="@{}".format(instance.name), enabled=True, internal=True + address=f"@{instance.name}", enabled=True, internal=True ) models.AliasRecipient.objects.create( - address="@{}".format(instance.target.name), alias=alias + address=f"@{instance.target.name}", alias=alias ) @receiver(signals.post_delete, sender=models.DomainAlias) def remove_alias_for_domainalias(sender, instance, **kwargs): """Remove the alias associated to domain alias.""" - models.Alias.objects.filter(address="@{}".format(instance.name)).delete() + models.Alias.objects.filter(address=f"@{instance.name}").delete() @receiver(signals.post_save, sender=models.Mailbox) @@ -256,7 +252,7 @@ def import_account_mailbox(sender, user, account, row, **kwargs): raise exceptions.BadRequest( _("Account import failed (%s): wrong quota value") % account.username - ) + ) from None use_domain_quota = True if not quota else False mb = models.Mailbox( address=mailbox, diff --git a/modoboa/admin/management/commands/handle_mailbox_operations.py b/modoboa/admin/management/commands/handle_mailbox_operations.py index bfa527c76..f6274f484 100644 --- a/modoboa/admin/management/commands/handle_mailbox_operations.py +++ b/modoboa/admin/management/commands/handle_mailbox_operations.py @@ -25,7 +25,7 @@ class Command(BaseCommand): help = "Handles rename and delete operations on mailboxes" # NOQA:A003 def __init__(self, *args, **kwargs): - super(Command, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.logger = logging.getLogger("modoboa.admin") def add_arguments(self, parser): @@ -44,9 +44,9 @@ class Command(BaseCommand): if not os.path.exists(dirname): try: os.makedirs(dirname) - except os.error as e: - raise OperationError(str(e)) - code, output = exec_cmd("mv %s %s" % (operation.argument, new_mail_home)) + except OSError as e: + raise OperationError(str(e)) from None + code, output = exec_cmd(f"mv {operation.argument} {new_mail_home}") if code: raise OperationError(output) @@ -57,7 +57,7 @@ class Command(BaseCommand): def onerror(function, path, excinfo): """Handle errors.""" - self.logger.critical("delete failed (reason: {})".format(excinfo)) + self.logger.critical(f"delete failed (reason: {excinfo})") shutil.rmtree(operation.argument, False, onerror) @@ -71,7 +71,7 @@ class Command(BaseCommand): with open(path) as fp: pid = fp.read().strip() code, output = exec_cmd( - "grep handle_mailbox_operations /proc/%s/cmdline" % pid + f"grep handle_mailbox_operations /proc/{pid}/cmdline" ) if not code: return False @@ -88,7 +88,7 @@ class Command(BaseCommand): return for ope in MailboxOperation.objects.all(): try: - f = getattr(self, "%s_mailbox" % ope.type) + f = getattr(self, f"{ope.type}_mailbox") except AttributeError: continue try: diff --git a/modoboa/admin/management/commands/subcommands/_import.py b/modoboa/admin/management/commands/subcommands/_import.py index b55788811..0f2c39b62 100644 --- a/modoboa/admin/management/commands/subcommands/_import.py +++ b/modoboa/admin/management/commands/subcommands/_import.py @@ -1,7 +1,6 @@ """Django management command to import admin objects.""" import csv -import io import os import progressbar @@ -49,12 +48,12 @@ class ImportCommand(BaseCommand): if not os.path.isfile(filename): raise CommandError("File not found") - num_lines = sum(1 for line in io.open(filename, encoding=encoding) if line) + num_lines = sum(1 for line in open(filename, encoding=encoding) if line) pbar = progressbar.ProgressBar( widgets=[progressbar.Percentage(), progressbar.Bar(), progressbar.ETA()], maxval=num_lines, ).start() - with io.open(filename, encoding=encoding, newline="") as f: + with open(filename, encoding=encoding, newline="") as f: reader = csv.reader(f, delimiter=options["sepchar"]) i = 0 for row in reader: @@ -76,7 +75,7 @@ class ImportCommand(BaseCommand): "Object already exists at line {}: {}".format( i + 1, options["sepchar"].join(row[:2]) ) - ) + ) from None i += 1 pbar.update(i) @@ -101,7 +100,7 @@ class ImportCommand(BaseCommand): ) ) detector = UniversalDetector() - with io.open(filename, "rb") as fp: + with open(filename, "rb") as fp: for line in fp: detector.feed(line) if detector.done: diff --git a/modoboa/admin/management/commands/subcommands/_manage_dkim_keys.py b/modoboa/admin/management/commands/subcommands/_manage_dkim_keys.py index 9cac1a2b8..e8790a120 100644 --- a/modoboa/admin/management/commands/subcommands/_manage_dkim_keys.py +++ b/modoboa/admin/management/commands/subcommands/_manage_dkim_keys.py @@ -19,7 +19,7 @@ class ManageDKIMKeys(BaseCommand): def create_new_dkim_key(self, domain): """Create a new DKIM key.""" storage_dir = param_tools.get_global_parameter("dkim_keys_storage_dir") - pkey_path = os.path.join(storage_dir, "{}.pem".format(domain.name)) + pkey_path = os.path.join(storage_dir, f"{domain.name}.pem") alarm_qset = domain.alarms.filter(internal_name=DKIM_WRITE_ERROR) if not os.access(storage_dir, os.W_OK): @@ -43,26 +43,20 @@ class ManageDKIMKeys(BaseCommand): if domain.dkim_key_length else self.default_key_length ) - code, output = sysutils.exec_cmd( - "openssl genrsa -out {} {}".format(pkey_path, key_size) - ) + code, output = sysutils.exec_cmd(f"openssl genrsa -out {pkey_path} {key_size}") if code: print( - "Failed to generate DKIM private key for domain {}: {}".format( - domain.name, smart_str(output) - ) + f"Failed to generate DKIM private key for domain {domain.name}: {smart_str(output)}" ) domain.alarms.create( title=_("Failed to generate DKIM private key"), internal_name=DKIM_ERROR ) return domain.dkim_private_key_path = pkey_path - code, output = sysutils.exec_cmd("openssl rsa -in {} -pubout".format(pkey_path)) + code, output = sysutils.exec_cmd(f"openssl rsa -in {pkey_path} -pubout") if code: print( - "Failed to generate DKIM public key for domain {}: {}".format( - domain.name, smart_str(output) - ) + f"Failed to generate DKIM public key for domain {domain.name}: {smart_str(output)}" ) domain.alarms.create( title=_("Failed to generate DKIM public key"), internal_name=DKIM_ERROR diff --git a/modoboa/admin/management/commands/subcommands/_mx.py b/modoboa/admin/management/commands/subcommands/_mx.py index a2d365da1..ff1d9e890 100644 --- a/modoboa/admin/management/commands/subcommands/_mx.py +++ b/modoboa/admin/management/commands/subcommands/_mx.py @@ -87,7 +87,7 @@ class CheckMXRecords(BaseCommand): else: delim = "." if ip.version == 4 else ":" reverse = delim.join(ip.exploded.split(delim)[::-1]) - pattern = "{}.{}.".format(reverse, provider) + pattern = f"{reverse}.{provider}." try: result = socket.gethostbyname(pattern) # result from dnsbl is in ipv4 format @@ -128,7 +128,7 @@ class CheckMXRecords(BaseCommand): dnsbl_result.save() if not dnsbl_result.status and result: trigger = True - alarm_name = "domain_mx_in_dnsbl_{}".format(provider) + alarm_name = f"domain_mx_in_dnsbl_{provider}" if trigger: title = _("MX {} listed by DNSBL provider {}").format(mx.name, provider) domain.alarms.create( @@ -165,7 +165,7 @@ class CheckMXRecords(BaseCommand): """ alerts = [] check = False - mxs = [(mx, ipaddress.ip_address("%s" % mx.address)) for mx in mx_list] + mxs = [(mx, ipaddress.ip_address(str(mx.address))) for mx in mx_list] valid_mxs = self.valid_mxs if not mxs: alarm, created = domain.alarms.get_or_create( @@ -187,7 +187,7 @@ class CheckMXRecords(BaseCommand): mx.managed = check = True mx.save() if check is False: - mx_names = ["{0.name} ({0.address})".format(mx) for mx in mx_list] + mx_names = [f"{mx.name} ({mx.address})" for mx in mx_list] alarm, created = domain.alarms.get_or_create( internal_name="domain_invalid_mx", status=constants.ALARM_OPENED, diff --git a/modoboa/admin/management/commands/subcommands/_repair.py b/modoboa/admin/management/commands/subcommands/_repair.py index 27a5f2cf6..90c42b91d 100644 --- a/modoboa/admin/management/commands/subcommands/_repair.py +++ b/modoboa/admin/management/commands/subcommands/_repair.py @@ -76,18 +76,16 @@ def sometimes_mailbox_have_no_alias(**options): ) if created: alias_created += 1 - log("Alias {0} created".format(alias), **options) + log(f"Alias {alias} created", **options) recipient, created = models.AliasRecipient.objects.get_or_create( alias=alias, address=instance.full_address, r_mailbox=instance ) if created: recipient_created += 1 - log("AliasRecipient {0} created".format(recipient), **options) + log(f"AliasRecipient {recipient} created", **options) if alias_created or recipient_created: log( - "{0} alias created. {1} alias recipient created".format( - alias_created, recipient_created - ), + f"{alias_created} alias created. {recipient_created} alias recipient created", **options, ) @@ -129,5 +127,5 @@ class Repair(BaseCommand): for func in known_problems: title = func.__doc__.strip() log("", **options) - log("Checking for... {}...".format(title), **options) + log(f"Checking for... {title}...", **options) func(**options) diff --git a/modoboa/admin/models/alarm.py b/modoboa/admin/models/alarm.py index 15267a609..e7580161c 100644 --- a/modoboa/admin/models/alarm.py +++ b/modoboa/admin/models/alarm.py @@ -48,9 +48,7 @@ class Alarm(models.Model): ordering = ["created"] def __str__(self): - return "[{}] {} - {}".format( - self.created, self.domain, self.get_status_display() - ) + return f"[{self.created}] {self.domain} - {self.get_status_display()}" def close(self): """Close this alarm.""" diff --git a/modoboa/admin/models/alias.py b/modoboa/admin/models/alias.py index 9d870a9fb..afdad2870 100644 --- a/modoboa/admin/models/alias.py +++ b/modoboa/admin/models/alias.py @@ -306,4 +306,4 @@ class AliasRecipient(models.Model): def __str__(self): """Return alias and recipient.""" - return smart_str("{} -> {}".format(self.alias.address, self.address)) + return smart_str(f"{self.alias.address} -> {self.address}") diff --git a/modoboa/admin/models/domain.py b/modoboa/admin/models/domain.py index 6a84c2f1a..ab1f515b0 100644 --- a/modoboa/admin/models/domain.py +++ b/modoboa/admin/models/domain.py @@ -284,9 +284,7 @@ class Domain(mixins.MessageLimitMixin, AdminObject): split_record.append(' "%s"' % record) break record = "\n".join(split_record) - return "{}._domainkey.{}. IN TXT (\n{})".format( - self.dkim_key_selector, self.name, record - ) + return f"{self.dkim_key_selector}._domainkey.{self.name}. IN TXT (\n{record})" def add_admin(self, account): """Add a new administrator to this domain. diff --git a/modoboa/admin/models/mailbox.py b/modoboa/admin/models/mailbox.py index e87d76502..17403ef97 100644 --- a/modoboa/admin/models/mailbox.py +++ b/modoboa/admin/models/mailbox.py @@ -25,7 +25,7 @@ class QuotaManager(models.Manager): def get_domain_usage(self, domain): """Return current usage for domain.""" - qset = self.get_queryset().filter(username__endswith="@{}".format(domain.name)) + qset = self.get_queryset().filter(username__endswith=f"@{domain.name}") result = qset.aggregate(usage=models.Sum("bytes")).get("usage", 0) if result is None: result = 0 diff --git a/modoboa/admin/models/mxrecord.py b/modoboa/admin/models/mxrecord.py index e0d630c5c..1eae36025 100644 --- a/modoboa/admin/models/mxrecord.py +++ b/modoboa/admin/models/mxrecord.py @@ -45,7 +45,7 @@ class MXRecordManager(models.Manager): record = self.get_queryset().create( domain=domain, name="{}".format(mx_addr.strip(".")), - address="{}".format(mx_ip_addr), + address=f"{mx_ip_addr}", updated=now + delta, ) yield record @@ -68,7 +68,7 @@ class MXRecord(models.Model): return bool(param_tools.get_global_parameter("valid_mxs").strip()) def __str__(self): - return "{0.name} ({0.address}) for {0.domain} ".format(self) + return f"{self.name} ({self.address}) for {self.domain} " class DNSBLQuerySet(models.QuerySet): diff --git a/modoboa/admin/postfix_maps.py b/modoboa/admin/postfix_maps.py index 1378997f9..1e865d683 100644 --- a/modoboa/admin/postfix_maps.py +++ b/modoboa/admin/postfix_maps.py @@ -1,7 +1,7 @@ """Map file definitions for postfix.""" -class DomainsMap(object): +class DomainsMap: """Map to list all domains.""" filename = "sql-domains.cf" @@ -18,7 +18,7 @@ class DomainsMap(object): ) -class DomainsAliasesMap(object): +class DomainsAliasesMap: """Map to list all domain aliases.""" filename = "sql-domain-aliases.cf" @@ -39,7 +39,7 @@ class DomainsAliasesMap(object): ) -class AliasesMap(object): +class AliasesMap: """A map to list all mailbox aliases.""" filename = "sql-aliases.cf" @@ -63,7 +63,7 @@ class AliasesMap(object): ) -class MaintainMap(object): +class MaintainMap: """Map files to list non available mailboxes.""" filename = "sql-maintain.cf" @@ -90,7 +90,7 @@ class MaintainMap(object): ) -class SenderLoginMap(object): +class SenderLoginMap: """Map file to list authorized sender addresses for a given account: * Its mailbox * Its aliases diff --git a/modoboa/admin/tests/test_account.py b/modoboa/admin/tests/test_account.py index 561079b02..7f0f38ff1 100644 --- a/modoboa/admin/tests/test_account.py +++ b/modoboa/admin/tests/test_account.py @@ -576,14 +576,14 @@ class AccountTestCase(ModoTestCase): self.assertIn("user@test.com", response["rows"]) old_rows = response["rows"] - response = self.ajax_get("{}?sort_order=-quota_value__bytes".format(url)) + response = self.ajax_get(f"{url}?sort_order=-quota_value__bytes") self.assertNotEqual(old_rows, response["rows"]) old_rows = response["rows"] - response = self.ajax_get("{}?sort_order=-quota_usage".format(url)) + response = self.ajax_get(f"{url}?sort_order=-quota_usage") self.assertEqual(old_rows, response["rows"]) - response = self.ajax_get("{}?sort_order=-unknown".format(url), status=400) + response = self.ajax_get(f"{url}?sort_order=-unknown", status=400) @skipIf(NO_LDAP, "No ldap module installed") @@ -615,7 +615,7 @@ class PermissionsTestCase(ModoTestCase): super(PermissionsTestCase, cls).setUpTestData() parameters = {} for name, _definition in limits_utils.get_user_limit_templates(): - parameters["deflt_user_{0}_limit".format(name)] = 2 + parameters[f"deflt_user_{name}_limit"] = 2 cls.localconfig.parameters.set_values(parameters, app="limits") cls.localconfig.save() factories.populate_database() diff --git a/modoboa/admin/tests/test_domain.py b/modoboa/admin/tests/test_domain.py index f0b7fcd40..b3df2ebc3 100644 --- a/modoboa/admin/tests/test_domain.py +++ b/modoboa/admin/tests/test_domain.py @@ -364,10 +364,10 @@ class DomainTestCase(ModoTestCase): self.assertIn("test.com", response["rows"]) old_rows = response["rows"] - response = self.ajax_get("{}?sort_order=-name".format(url)) + response = self.ajax_get(f"{url}?sort_order=-name") self.assertNotEqual(old_rows, response["rows"]) - response = self.ajax_get("{}?sort_order=allocated_quota".format(url)) + response = self.ajax_get(f"{url}?sort_order=allocated_quota") self.assertNotEqual(old_rows, response["rows"]) def test_domain_logs_list_view(self): @@ -386,10 +386,10 @@ class DomainTestCase(ModoTestCase): self.assertIn("ID1", response["rows"]) old_rows = response["rows"] - response = self.ajax_get("{}?sort_order=-sender".format(url)) + response = self.ajax_get(f"{url}?sort_order=-sender") self.assertNotEqual(old_rows, response["rows"]) - response = self.ajax_get("{}?searchquery=ID1".format(url)) + response = self.ajax_get(f"{url}?searchquery=ID1") self.assertNotEqual(old_rows, response["rows"]) admin = User.objects.get(username="admin@test.com") @@ -421,7 +421,7 @@ class DomainTestCase(ModoTestCase): response = self.ajax_get(url) self.assertIn("handle_mailboxes", response) self.assertIn("listalarms", response["rows"]) - response = self.ajax_get("{}?objtype=quota".format(url)) + response = self.ajax_get(f"{url}?objtype=quota") self.assertIn("progress-bar", response["rows"]) diff --git a/modoboa/admin/tests/test_domain_alias.py b/modoboa/admin/tests/test_domain_alias.py index 6cf4f07cf..9952cba6a 100644 --- a/modoboa/admin/tests/test_domain_alias.py +++ b/modoboa/admin/tests/test_domain_alias.py @@ -21,15 +21,13 @@ class DomainAliasTestCase(ModoTestCase): domal.target = dom domal.save() self.assertEqual(dom.domainalias_count, 1) - self.assertTrue(Alias.objects.filter(address="@{}".format(domal.name)).exists()) + self.assertTrue(Alias.objects.filter(address=f"@{domal.name}").exists()) domal.name = "domalias.org" domal.save() domal.delete() - self.assertFalse( - Alias.objects.filter(address="@{}".format(domal.name)).exists() - ) + self.assertFalse(Alias.objects.filter(address=f"@{domal.name}").exists()) def test_form(self): dom = Domain.objects.get(name="test.com") diff --git a/modoboa/admin/tests/test_import_.py b/modoboa/admin/tests/test_import_.py index 5a72ba6d5..ad09b2807 100644 --- a/modoboa/admin/tests/test_import_.py +++ b/modoboa/admin/tests/test_import_.py @@ -21,7 +21,7 @@ class ImportTestCase(ModoTestCase): @classmethod def setUpTestData(cls): # NOQA:N802 """Create test data.""" - super(ImportTestCase, cls).setUpTestData() + super().setUpTestData() cls.localconfig.parameters.set_value("enable_admin_limits", False, app="limits") cls.localconfig.save() factories.populate_database() @@ -326,9 +326,7 @@ alias;user@test.com;True;admin@test.com f = ContentFile( """domain; dómªin1.com; 1000; 100; True dómain; dómªin2.com; 1000; 100; True -""".encode( - "utf8" - ), +""".encode(), name="dómains.csv", ) self.client.post(reverse("admin:domain_import"), {"sourcefile": f}) diff --git a/modoboa/admin/tests/test_mailbox_operations.py b/modoboa/admin/tests/test_mailbox_operations.py index 99b32a1cf..2cf2b7d5d 100644 --- a/modoboa/admin/tests/test_mailbox_operations.py +++ b/modoboa/admin/tests/test_mailbox_operations.py @@ -13,7 +13,7 @@ from modoboa.lib.tests import ModoTestCase from .. import factories, models -@override_settings(DOVECOT_LOOKUP_PATH=["{}/dovecot".format(os.path.dirname(__file__))]) +@override_settings(DOVECOT_LOOKUP_PATH=[f"{os.path.dirname(__file__)}/dovecot"]) class MailboxOperationTestCase(ModoTestCase): """Test management command.""" @@ -27,7 +27,7 @@ class MailboxOperationTestCase(ModoTestCase): """Initiate test env.""" super(MailboxOperationTestCase, self).setUp() self.workdir = tempfile.mkdtemp() - path = "{}/test.com/admin".format(self.workdir) + path = f"{self.workdir}/test.com/admin" os.makedirs(path) self.set_global_parameter("handle_mailboxes", True) self.set_global_parameter("enable_admin_limits", False, app="limits") @@ -39,7 +39,7 @@ class MailboxOperationTestCase(ModoTestCase): @mock.patch("modoboa.admin.models.Mailbox.mail_home") def test_delete_account(self, mail_home_mock): """Check delete operation.""" - path = "{}/test.com/admin".format(self.workdir) + path = f"{self.workdir}/test.com/admin" mail_home_mock.__get__ = mock.Mock(return_value=path) mb = models.Mailbox.objects.select_related("user").get( address="admin", domain__name="test.com" @@ -52,7 +52,7 @@ class MailboxOperationTestCase(ModoTestCase): @mock.patch("modoboa.admin.models.Mailbox.mail_home") def test_rename_account(self, mail_home_mock): """Check rename operation.""" - path = "{}/test.com/admin".format(self.workdir) + path = f"{self.workdir}/test.com/admin" mail_home_mock.__get__ = mock.Mock(return_value=path) mb = models.Mailbox.objects.select_related("user").get( address="admin", domain__name="test.com" @@ -67,7 +67,7 @@ class MailboxOperationTestCase(ModoTestCase): "content": "content", } self.ajax_post(reverse("admin:account_change", args=[mb.user.pk]), values) - path = "{}/test.com/admin2".format(self.workdir) + path = f"{self.workdir}/test.com/admin2" mail_home_mock.__get__ = mock.Mock(return_value=path) call_command("handle_mailbox_operations") self.assertFalse(models.MailboxOperation.objects.exists()) @@ -76,7 +76,7 @@ class MailboxOperationTestCase(ModoTestCase): @mock.patch("modoboa.admin.models.Mailbox.mail_home") def test_delete_domain(self, mail_home_mock): """Check delete operation.""" - path = "{}/test.com/admin".format(self.workdir) + path = f"{self.workdir}/test.com/admin" mail_home_mock.__get__ = mock.Mock(return_value=path) domain = models.Domain.objects.get(name="test.com") self.ajax_post(reverse("admin:domain_delete", args=[domain.pk])) diff --git a/modoboa/admin/tests/test_mx.py b/modoboa/admin/tests/test_mx.py index 12bbfd3ce..902490cd2 100644 --- a/modoboa/admin/tests/test_mx.py +++ b/modoboa/admin/tests/test_mx.py @@ -110,7 +110,7 @@ class MXTestCase(ModoTestCase): mb = factories.MailboxFactory( address="admin", domain=domain, - user__username="admin@{}".format(domain.name), + user__username=f"admin@{domain.name}", user__groups=("DomainAdmins",), ) domain.add_admin(mb.user) diff --git a/modoboa/admin/tests/utils.py b/modoboa/admin/tests/utils.py index f1b9eeaf5..f9056655e 100644 --- a/modoboa/admin/tests/utils.py +++ b/modoboa/admin/tests/utils.py @@ -11,13 +11,13 @@ from dns.rdtypes.ANY.TXT import TXT from dns.resolver import NXDOMAIN, NoAnswer, NoNameservers, Timeout -class RRset(object): +class RRset: def __init__(self): self.address = "192.0.2.1" -class RRsetInvalid(object): +class RRsetInvalid: def __init__(self): self.address = "000.0.0.0" diff --git a/modoboa/admin/views/domain.py b/modoboa/admin/views/domain.py index b16980e7a..06b050cc2 100644 --- a/modoboa/admin/views/domain.py +++ b/modoboa/admin/views/domain.py @@ -150,10 +150,10 @@ def list_quotas(request): domains = Domain.objects.get_for_admin(request.user) domains = domains.exclude(quota=0) if sort_order in ["name", "quota"]: - domains = domains.order_by("{}{}".format(sort_dir, sort_order)) + domains = domains.order_by(f"{sort_dir}{sort_order}") elif sort_order == "allocated_quota": domains = domains.annotate(allocated_quota=Sum("mailbox__quota")).order_by( - "{}{}".format(sort_dir, sort_order) + f"{sort_dir}{sort_order}" ) page = get_listing_page(domains, request.GET.get("page", 1)) context = { @@ -182,7 +182,7 @@ def list_logs(request): ) else: logs = ml_models.Maillog.objects.all() - logs = logs.order_by("{}{}".format(sort_dir, sort_order)) + logs = logs.order_by(f"{sort_dir}{sort_order}") if search: logs = logs.filter( Q(sender__icontains=search) @@ -266,7 +266,7 @@ class DomainDetailView(auth_mixins.PermissionRequiredMixin, generic.DetailView): def get_context_data(self, **kwargs): """Include extra widgets.""" - context = super(DomainDetailView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) result = signals.extra_domain_dashboard_widgets.send( self.__class__, user=self.request.user, domain=self.object ) diff --git a/modoboa/admin/views/export.py b/modoboa/admin/views/export.py index ccb9ec6cc..d390187bd 100644 --- a/modoboa/admin/views/export.py +++ b/modoboa/admin/views/export.py @@ -27,7 +27,7 @@ def _export(content, filename): resp = HttpResponse(content) resp["Content-Type"] = "text/csv" resp["Content-Length"] = len(content) - resp["Content-Disposition"] = 'attachment; filename="{}"'.format(filename) + resp["Content-Disposition"] = f'attachment; filename="{filename}"' return resp diff --git a/modoboa/calendars/backends/__init__.py b/modoboa/calendars/backends/__init__.py index 3187ca110..b18191dfc 100644 --- a/modoboa/calendars/backends/__init__.py +++ b/modoboa/calendars/backends/__init__.py @@ -3,7 +3,7 @@ from importlib import import_module -class CalendarBackend(object): +class CalendarBackend: """Base backend class.""" def __init__(self, calendar=None): @@ -29,8 +29,8 @@ class CalendarBackend(object): def get_backend(name, *args, **kwargs): """Return a backend instance.""" - module = import_module("modoboa.calendars.backends.{}".format(name)) - return getattr(module, "{}Backend".format(name.capitalize()))(*args, **kwargs) + module = import_module(f"modoboa.calendars.backends.{name}") + return getattr(module, f"{name.capitalize()}Backend")(*args, **kwargs) def get_backend_from_request(name, request, calendar=None): diff --git a/modoboa/calendars/backends/caldav_.py b/modoboa/calendars/backends/caldav_.py index 73df9b374..7bc513ebe 100644 --- a/modoboa/calendars/backends/caldav_.py +++ b/modoboa/calendars/backends/caldav_.py @@ -94,7 +94,7 @@ class Caldav_Backend(CalendarBackend): def update_event(self, uid, original_data): """Update an existing event.""" data = dict(original_data) - url = "{}/{}.ics".format(self.remote_cal.url.geturl(), uid) + url = f"{self.remote_cal.url.geturl()}/{uid}.ics" cal = self.remote_cal.event_by_url(url) orig_evt = cal.vobject_instance.vevent if "title" in data: @@ -125,7 +125,7 @@ class Caldav_Backend(CalendarBackend): # Calendar has been changed, remove old event first. self.remote_cal.client.delete(url) remote_cal = Calendar(self.client, data["calendar"].encoded_path) - url = "{}/{}.ics".format(remote_cal.url.geturl(), uid) + url = f"{remote_cal.url.geturl()}/{uid}.ics" else: remote_cal = self.remote_cal remote_cal.add_event(cal.instance) @@ -133,7 +133,7 @@ class Caldav_Backend(CalendarBackend): def get_event(self, uid): """Retrieve and event using its uid.""" - url = "{}/{}.ics".format(self.remote_cal.url.geturl(), uid) + url = f"{self.remote_cal.url.geturl()}/{uid}.ics" event = self.remote_cal.event_by_url(url) return self._serialize_event(event) @@ -147,7 +147,7 @@ class Caldav_Backend(CalendarBackend): def delete_event(self, uid): """Delete an event using its uid.""" - url = "{}/{}.ics".format(self.remote_cal.url.geturl(), uid) + url = f"{self.remote_cal.url.geturl()}/{uid}.ics" self.remote_cal.client.delete(url) def import_events(self, fp): diff --git a/modoboa/calendars/handlers.py b/modoboa/calendars/handlers.py index 7fb8bcf54..0dbd20a56 100644 --- a/modoboa/calendars/handlers.py +++ b/modoboa/calendars/handlers.py @@ -48,7 +48,7 @@ def set_user_calendar_path(sender, instance, **kwargs): """Set path at creation.""" if instance.pk: return - instance._path = "{}/{}".format(instance.mailbox.full_address, instance.name) + instance._path = f"{instance.mailbox.full_address}/{instance.name}" if not instance.access_token: instance.access_token = token_hex(16) @@ -58,6 +58,6 @@ def set_shared_calendar_path(sender, instance, **kwargs): """Set path at creation.""" if instance.pk: return - instance._path = "{}/{}".format(instance.domain.name, instance.name) + instance._path = f"{instance.domain.name}/{instance.name}" if not instance.access_token: instance.access_token = token_hex(16) diff --git a/modoboa/calendars/management/commands/generate_rights.py b/modoboa/calendars/management/commands/generate_rights.py index b2413a1d3..b1b3a6dbf 100644 --- a/modoboa/calendars/management/commands/generate_rights.py +++ b/modoboa/calendars/management/commands/generate_rights.py @@ -30,21 +30,20 @@ class Command(BaseCommand): def _generate_acr(self, name, user, collection, perm="rw", comment=None): """Write a new access control rule to the config file.""" if comment is not None: - self._cfgfile.write("\n# %s" % comment) + self._cfgfile.write(f"\n# {comment}") self._cfgfile.write( - """ -[%s] -user = %s -collection = %s -permissions = %s + f""" +[{name}] +user = {user} +collection = {collection} +permissions = {perm} """ - % (name, user, collection, perm) ) def _user_access_rules(self): """Create user access rules.""" for acr in models.AccessRule.objects.select_related().all(): - section = "%s-to-%s-acr" % (acr.mailbox, acr.calendar) + section = f"{acr.mailbox}-to-{acr.calendar}-acr" permission = "" if acr.read: permission += "r" @@ -61,7 +60,7 @@ permissions = %s """Create token access rules.""" for model in [models.UserCalendar, models.SharedCalendar]: for calendar in model.objects.all(): - section = "token-{}-access".format(calendar._path) + section = f"token-{calendar._path}-access" self._generate_acr( section, calendar.access_token, @@ -73,15 +72,15 @@ permissions = %s def _super_admin_rules(self): """Generate access rules for super administrators.""" for sa in User.objects.filter(is_superuser=True): - section = "sa-%s-acr" % sa.username + section = f"sa-{sa.username}-acr" self._generate_acr(section, sa.username, ".*") def _domain_admin_rules(self): """Generate access rules for domain adminstrators.""" for da in User.objects.filter(groups__name="DomainAdmins"): for domain in Domain.objects.get_for_admin(da): - section = "da-%s-to-%s-acr" % (da.email, domain.name) - self._generate_acr(section, da.email, "%s/user/.*" % domain.name) + section = f"da-{da.email}-to-{domain.name}-acr" + self._generate_acr(section, da.email, f"{domain.name}/user/.*") def _generate_file(self, target): """ @@ -89,11 +88,10 @@ permissions = %s """ self._cfgfile = open(target, "w") self._cfgfile.write( - """# Rights management file for Radicale -# This file was generated by Modoboa on %s + f"""# Rights management file for Radicale +# This file was generated by Modoboa on {datetime.datetime.today()} # DO NOT EDIT MANUALLY! """ - % datetime.datetime.today() ) allow_calendars_administration = param_tools.get_global_parameter( diff --git a/modoboa/calendars/mocks.py b/modoboa/calendars/mocks.py index bc17cd742..c9dc21952 100644 --- a/modoboa/calendars/mocks.py +++ b/modoboa/calendars/mocks.py @@ -31,7 +31,7 @@ END:VCALENDAR """ -class Url(object): +class Url: def __init__(self, path): self.path = path @@ -85,5 +85,5 @@ class DAVClientMock: def proppatch(self, url, body, dummy=None): return Response(200) - def request(self, url, method="GET", body="", headers={}): + def request(self, url, method="GET", body="", headers=None): return Response(200) diff --git a/modoboa/calendars/models.py b/modoboa/calendars/models.py index f2ee8de1d..ec0c88ba3 100644 --- a/modoboa/calendars/models.py +++ b/modoboa/calendars/models.py @@ -53,7 +53,7 @@ class Calendar(models.Model): @property def share_url(self): """Return calendar share url.""" - return "{}?token={}".format(self.full_url, self.access_token) + return f"{self.full_url}?token={self.access_token}" @property def encoded_url(self): diff --git a/modoboa/calendars/tests.py b/modoboa/calendars/tests.py index 55a85e92c..1b817e747 100644 --- a/modoboa/calendars/tests.py +++ b/modoboa/calendars/tests.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """Radicale extension unit tests.""" import os @@ -24,13 +22,13 @@ from . import models from . import mocks -class TestDataMixin(object): +class TestDataMixin: """Create some data.""" @classmethod def setUpTestData(cls): """Create test data.""" - super(TestDataMixin, cls).setUpTestData() + super().setUpTestData() admin_factories.populate_database() cls.account = core_models.User.objects.get(username="user@test.com") cls.calendar = factories.UserCalendarFactory(mailbox=cls.account.mailbox) @@ -60,12 +58,12 @@ class AccessRuleTestCase(ModoTestCase): @classmethod def setUpTestData(cls): """Create test data.""" - super(AccessRuleTestCase, cls).setUpTestData() + super().setUpTestData() populate_database() def setUp(self): """Initialize tests.""" - super(AccessRuleTestCase, self).setUp() + super().setUp() self.rights_file_path = tempfile.mktemp() self.set_global_parameter( "rights_file_path", self.rights_file_path, app="calendars" @@ -394,12 +392,12 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase): self.client.post(reverse("core:login"), data) # FIXME: drf nested routers does not handle reverse() properly - url = "/api/v2/user-calendars/{}/events/{}/".format(self.calendar.pk, 1234) + url = f"/api/v2/user-calendars/{self.calendar.pk}/events/{1234}/" response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertTrue(response.json()["title"].startswith("Bastille")) - url = "/api/v2/user-calendars/{}/events/".format(self.calendar.pk) + url = f"/api/v2/user-calendars/{self.calendar.pk}/events/" url = "{}?start={}&end={}".format(url, "20060712T182145Z", "20070712T182145Z") response = self.client.get(url) self.assertEqual(response.status_code, 200) @@ -411,12 +409,12 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase): self.client.post(reverse("core:login"), data) # FIXME: drf nested routers does not handle reverse() properly - url = "/api/v2/shared-calendars/{}/events/{}/".format(self.scalendar.pk, 1234) + url = f"/api/v2/shared-calendars/{self.scalendar.pk}/events/{1234}/" response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertTrue(response.json()["title"].startswith("Bastille")) - url = "/api/v2/shared-calendars/{}/events/".format(self.scalendar.pk) + url = f"/api/v2/shared-calendars/{self.scalendar.pk}/events/" url = "{}?start={}&end={}".format(url, "20060712T182145Z", "20070712T182145Z") response = self.client.get(url) self.assertEqual(response.status_code, 200) @@ -427,7 +425,7 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase): data = {"username": "user@test.com", "password": "toto"} self.client.post(reverse("core:login"), data) - url = "/api/v2/user-calendars/{}/events/".format(self.calendar.pk) + url = f"/api/v2/user-calendars/{self.calendar.pk}/events/" data = { "title": "Test event", "start": "2018-03-27T00:00:00Z", @@ -441,7 +439,7 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase): self.assertEqual(response.status_code, 201) self.assertIn("id", response.json()) - url = "/api/v2/shared-calendars/{}/events/".format(self.scalendar.pk) + url = f"/api/v2/shared-calendars/{self.scalendar.pk}/events/" data = { "title": "Test event 2", "start": "2018-03-27T00:00:00Z", @@ -460,7 +458,7 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase): data = {"username": "user@test.com", "password": "toto"} self.client.post(reverse("core:login"), data) - url = "/api/v2/user-calendars/{}/events/1234/".format(self.calendar.pk) + url = f"/api/v2/user-calendars/{self.calendar.pk}/events/1234/" data = { "title": "Test event", "start": "2018-03-27T00:00:00Z", @@ -483,7 +481,7 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase): data = {"username": "user@test.com", "password": "toto"} self.client.post(reverse("core:login"), data) - url = "/api/v2/user-calendars/{}/events/1234/".format(self.calendar.pk) + url = f"/api/v2/user-calendars/{self.calendar.pk}/events/1234/" data = { "start": "2018-03-27T00:00:00Z", "end": "2018-03-28T00:00:00Z", @@ -506,7 +504,7 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase): "calendar": self.scalendar.pk, "new_calendar_type": "shared", } - url = "/api/v2/user-calendars/{}/events/1234/".format(self.calendar.pk) + url = f"/api/v2/user-calendars/{self.calendar.pk}/events/1234/" response = self.client.put(url, data=data, format="json") self.assertEqual(response.status_code, 200) @@ -515,7 +513,7 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase): data = {"username": "user@test.com", "password": "toto"} self.client.post(reverse("core:login"), data) - url = "/api/v2/shared-calendars/{}/events/1234/".format(self.scalendar.pk) + url = f"/api/v2/shared-calendars/{self.scalendar.pk}/events/1234/" response = self.client.delete(url) self.assertEqual(response.status_code, 200) @@ -546,7 +544,7 @@ class AttendeeViewSetTestCase(ModoAPITestCase): @classmethod def setUpTestData(cls): - super(AttendeeViewSetTestCase, cls).setUpTestData() + super().setUpTestData() populate_database() cls.account = core_models.User.objects.get(username="user@test.com") @@ -567,7 +565,7 @@ class MailboxViewSetTestCase(ModoAPITestCase): @classmethod def setUpTestData(cls): - super(MailboxViewSetTestCase, cls).setUpTestData() + super().setUpTestData() populate_database() cls.account = core_models.User.objects.get(username="user@test.com") diff --git a/modoboa/contacts/factories.py b/modoboa/contacts/factories.py index a0bfe9aab..4141d294a 100644 --- a/modoboa/contacts/factories.py +++ b/modoboa/contacts/factories.py @@ -51,9 +51,7 @@ class ContactFactory(factory.django.DjangoModelFactory): first_name = "Homer" last_name = "Simpson" - display_name = factory.LazyAttribute( - lambda c: "{}{}".format(c.first_name, c.last_name) - ) + display_name = factory.LazyAttribute(lambda c: f"{c.first_name}{c.last_name}") @factory.post_generation def categories(self, create, extracted, **dummy_kwargs): diff --git a/modoboa/contacts/lib/carddav.py b/modoboa/contacts/lib/carddav.py index a4126bd7c..07f7c6e48 100644 --- a/modoboa/contacts/lib/carddav.py +++ b/modoboa/contacts/lib/carddav.py @@ -53,9 +53,9 @@ nsmap = { def ns(prefix, tag=None): - name = "{%s}" % nsmap[prefix] + name = f"{{{nsmap[prefix]}}}" if tag is not None: - name = "%s%s" % (name, tag) + name = f"{name}{tag}" return name @@ -91,7 +91,7 @@ class SyncCollectionQuery(elements.base.BaseElement): tag = ns("D", "sync-collection") -class PyCardDAV(object): +class PyCardDAV: """class for interacting with a CardDAV server Since PyCardDAV relies heavily on Requests [1] its SSL verification is also diff --git a/modoboa/contacts/mocks.py b/modoboa/contacts/mocks.py index f777ec4f6..00a590c48 100644 --- a/modoboa/contacts/mocks.py +++ b/modoboa/contacts/mocks.py @@ -98,10 +98,10 @@ def report_mock(url, request): def get_mock(url, request): """Simulate a GET request.""" uid = url.path.split("/")[-1] - content = """ + content = f""" BEGIN:VCARD VERSION:3.0 -UID:{} +UID:{uid} N:Gump;Forrest FN:Forrest Gump ORG:Bubba Gump Shrimp Co. @@ -111,7 +111,5 @@ TEL;TYPE=HOME;VOICE:(404) 555-1212 ADR;TYPE=HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America EMAIL;TYPE=PREF,INTERNET:forrestgump@example.com END:VCARD -""".format( - uid - ) +""" return {"status_code": 200, "content": content} diff --git a/modoboa/contacts/models.py b/modoboa/contacts/models.py index 16b6d90e0..375859835 100644 --- a/modoboa/contacts/models.py +++ b/modoboa/contacts/models.py @@ -82,18 +82,16 @@ class Contact(models.Model): """Set uid for new object.""" super().__init__(*args, **kwargs) if not self.pk: - self.uid = "{}.vcf".format(uuid.uuid4()) + self.uid = f"{uuid.uuid4()}.vcf" @property def url(self): - return "{}/{}.vcf".format(self.addressbook.url, self.uid) + return f"{self.addressbook.url}/{self.uid}.vcf" def to_vcard(self): """Convert this contact to a vCard.""" card = vobject.vCard() - card.add("prodid").value = "-//Modoboa//Contacts plugin {}//EN".format( - __version__ - ) + card.add("prodid").value = f"-//Modoboa//Contacts plugin {__version__}//EN" card.add("uid").value = self.uid card.add("n").value = vobject.vcard.Name( family=self.last_name, given=self.first_name @@ -193,4 +191,4 @@ class PhoneNumber(models.Model): db_table = "modoboa_contacts_phonenumber" def __str__(self): - return "{}: {}".format(self.type, self.number) + return f"{self.type}: {self.number}" diff --git a/modoboa/contacts/serializers.py b/modoboa/contacts/serializers.py index 77dec85a5..f1922791a 100644 --- a/modoboa/contacts/serializers.py +++ b/modoboa/contacts/serializers.py @@ -38,7 +38,7 @@ class EmailAddressWithNameSerializer(serializers.ModelSerializer): """Return display name.""" if obj.contact.display_name: return obj.contact.display_name - return "{} {}".format(obj.contact.first_name, obj.contact.last_name) + return f"{obj.contact.first_name} {obj.contact.last_name}" class PhoneNumberSerializer(serializers.ModelSerializer): diff --git a/modoboa/contacts/tests.py b/modoboa/contacts/tests.py index 38019fa76..0535c4d93 100644 --- a/modoboa/contacts/tests.py +++ b/modoboa/contacts/tests.py @@ -1,4 +1,3 @@ -# coding: utf-8 """Contacts backend tests.""" import os @@ -172,14 +171,14 @@ class ContactViewSetTestCase(TestDataMixin, ModoAPITestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 3) - response = self.client.get("{}?search=homer".format(url)) + response = self.client.get(f"{url}?search=homer") self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 1) def test_filter_by_category(self): """Check filtering.""" url = reverse("api:contact-list") - response = self.client.get("{}?category={}".format(url, self.category.name)) + response = self.client.get(f"{url}?category={self.category.name}") self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 1) @@ -300,15 +299,15 @@ class EmailAddressViewSetTestCase(TestDataMixin, ModoAPITestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 3) - response = self.client.get("{}?search=homer".format(url)) + response = self.client.get(f"{url}?search=homer") self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 1) - response = self.client.get("{}?search=Marge".format(url)) + response = self.client.get(f"{url}?search=Marge") self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 1) - response = self.client.get("{}?search=Simpson".format(url)) + response = self.client.get(f"{url}?search=Simpson") self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 3) diff --git a/modoboa/core/api/v1/viewsets.py b/modoboa/core/api/v1/viewsets.py index 34478de7e..6cf22cb4f 100644 --- a/modoboa/core/api/v1/viewsets.py +++ b/modoboa/core/api/v1/viewsets.py @@ -29,7 +29,7 @@ class AccountViewSet(GetThrottleViewsetMixin, viewsets.ViewSet): def tfa_setup(self, request): """Initiate TFA setup.""" instance, created = TOTPDevice.objects.get_or_create( - user=request.user, defaults={"name": "{} TOTP device".format(request.user)} + user=request.user, defaults={"name": f"{request.user} TOTP device"} ) return response.Response() @@ -43,7 +43,7 @@ class AccountViewSet(GetThrottleViewsetMixin, viewsets.ViewSet): serializer.is_valid(raise_exception=True) # create static device for recovery purposes device = StaticDevice.objects.create( - user=request.user, name="{} static device".format(request.user) + user=request.user, name=f"{request.user} static device" ) for cpt in range(10): token = StaticToken.random_token() diff --git a/modoboa/core/api/v2/viewsets.py b/modoboa/core/api/v2/viewsets.py index ecde8ee79..dc4e150f3 100644 --- a/modoboa/core/api/v2/viewsets.py +++ b/modoboa/core/api/v2/viewsets.py @@ -32,12 +32,12 @@ def create_static_tokens(request): device = request.user.staticdevice_set.first() if device is None: device = StaticDevice.objects.create( - user=request.user, name="{} static device".format(request.user) + user=request.user, name=f"{request.user} static device" ) elif device is not None: return None tokens = [] - for cpt in range(10): + for _cpt in range(10): token = StaticToken.random_token() device.token_set.create(token=token) tokens.append(token) diff --git a/modoboa/core/commands/__init__.py b/modoboa/core/commands/__init__.py index b935d655f..4759b0666 100644 --- a/modoboa/core/commands/__init__.py +++ b/modoboa/core/commands/__init__.py @@ -7,7 +7,7 @@ from django.template import Context, Template from django.utils.encoding import smart_str -class Command(object): +class Command: """Base command class. A valid administrative command must inherit from this class. diff --git a/modoboa/core/commands/deploy.py b/modoboa/core/commands/deploy.py index 522fa86bc..4d3fa9ba0 100644 --- a/modoboa/core/commands/deploy.py +++ b/modoboa/core/commands/deploy.py @@ -117,7 +117,7 @@ class DeployCommand(Command): "\n".join([line.decode() for line in output if line is not None]), file=sys.stderr, ) - print("%s failed, check your configuration" % cmd, file=sys.stderr) + print(f"{cmd} failed, check your configuration", file=sys.stderr) def ask_db_info(self, name="default"): """Prompt the user for database information @@ -127,7 +127,7 @@ class DeployCommand(Command): :param name: the connection name """ - print("Configuring database connection: %s" % name) + print(f"Configuring database connection: {name}") info = { "conn_name": name, "ENGINE": input("Database type (mysql, postgres or sqlite3): "), @@ -137,7 +137,7 @@ class DeployCommand(Command): if info["ENGINE"] == "sqlite3": info["ENGINE"] = "django.db.backends.sqlite3" - info["NAME"] = "%s.db" % name + info["NAME"] = f"{name}.db" return info if info["ENGINE"] == "postgres": info["ENGINE"] = "django.db.backends.postgresql" @@ -146,7 +146,7 @@ class DeployCommand(Command): info["ENGINE"] = "django.db.backends.mysql" default_port = 3306 info["HOST"] = input("Database host (default: 'localhost'): ") - info["PORT"] = input("Database port (default: '%s'): " % default_port) + info["PORT"] = input(f"Database port (default: '{default_port}'): ") # leave port setting empty, if default value is supplied and # leave it to django if info["PORT"] == default_port: @@ -181,7 +181,7 @@ class DeployCommand(Command): for extension in extensions: module = __import__(extension[1], locals(), globals(), []) basedir = os.path.dirname(module.__file__) - if not os.path.exists("{0}/settings.py".format(basedir)): + if not os.path.exists(f"{basedir}/settings.py"): continue extra_settings.append(extension[1]) return extra_settings @@ -189,7 +189,7 @@ class DeployCommand(Command): def handle(self, parsed_args): django.setup() management.call_command("startproject", parsed_args.name, verbosity=False) - path = "%(name)s/%(name)s" % {"name": parsed_args.name} + path = f"{parsed_args.name}/{parsed_args.name}" sys.path.append(parsed_args.name) conn_tpl = Template(DBCONN_TPL) @@ -240,7 +240,7 @@ class DeployCommand(Command): mod = __import__(parsed_args.name, globals(), locals(), [smart_str("settings")]) tpl = self._render_template( - "%s/settings.py.tpl" % self._templates_dir, + f"{self._templates_dir}/settings.py.tpl", { "db_connections": connections, "secret_key": management.utils.get_random_secret_key(), @@ -254,15 +254,15 @@ class DeployCommand(Command): "extra_settings": extra_settings, }, ) - with open("%s/settings.py" % path, "w") as fp: + with open(f"{path}/settings.py", "w") as fp: fp.write(tpl) generate_rsa_private_key(parsed_args.name) - shutil.copyfile("%s/urls.py.tpl" % self._templates_dir, "%s/urls.py" % path) - os.mkdir("%s/media" % parsed_args.name) + shutil.copyfile(f"{self._templates_dir}/urls.py.tpl", f"{path}/urls.py") + os.mkdir(f"{parsed_args.name}/media") - if isfile("%s/settings.pyc" % path): - os.unlink("%s/settings.pyc" % path) + if isfile(f"{path}/settings.pyc"): + os.unlink(f"{path}/settings.pyc") self._exec_django_command("migrate", parsed_args.name, "--noinput") self._exec_django_command( "load_initial_data", diff --git a/modoboa/core/extensions.py b/modoboa/core/extensions.py index 2dfb607a6..55551fc49 100644 --- a/modoboa/core/extensions.py +++ b/modoboa/core/extensions.py @@ -124,7 +124,7 @@ class ExtensionsPool: root = "" pattern = "{}.urls_api" else: - root = r"^{}/".format(ext.get_url()) + root = rf"^{ext.get_url()}/" pattern = "{}.urls" try: result.append(re_path(root, include(pattern.format(ext_name)))) diff --git a/modoboa/core/factories.py b/modoboa/core/factories.py index 36784176e..bad1e77c7 100644 --- a/modoboa/core/factories.py +++ b/modoboa/core/factories.py @@ -10,7 +10,7 @@ from . import models class PermissionFactory(factory.django.DjangoModelFactory): """A base factory to handle permissions.""" - class Meta(object): + class Meta: abstract = True @factory.post_generation @@ -23,7 +23,7 @@ class PermissionFactory(factory.django.DjangoModelFactory): class GroupFactory(factory.django.DjangoModelFactory): """A factory to create Group instances.""" - class Meta(object): + class Meta: model = Group name = "DefaultGroup" @@ -32,7 +32,7 @@ class GroupFactory(factory.django.DjangoModelFactory): class UserFactory(PermissionFactory): """A factory to create User instances.""" - class Meta(object): + class Meta: model = models.User django_get_or_create = ("username",) @@ -52,7 +52,7 @@ class UserFactory(PermissionFactory): class LogFactory(factory.django.DjangoModelFactory): """Factory for Log.""" - class Meta(object): + class Meta: model = models.Log logger = "modoboa.admin" diff --git a/modoboa/core/management/commands/clean_inactive_accounts.py b/modoboa/core/management/commands/clean_inactive_accounts.py index fbfed94b9..dfddce767 100644 --- a/modoboa/core/management/commands/clean_inactive_accounts.py +++ b/modoboa/core/management/commands/clean_inactive_accounts.py @@ -72,7 +72,7 @@ class Command(BaseCommand): return action = "delete" if options["delete"] else "disable" if not options["silent"]: - answer = input("Do you want to {} those accounts? (y/N) ".format(action)) + answer = input(f"Do you want to {action} those accounts? (y/N) ") if not answer.lower().startswith("y"): return if action == "disable": diff --git a/modoboa/core/management/commands/generate_postfix_maps.py b/modoboa/core/management/commands/generate_postfix_maps.py index 5677d2700..6e0321867 100644 --- a/modoboa/core/management/commands/generate_postfix_maps.py +++ b/modoboa/core/management/commands/generate_postfix_maps.py @@ -126,9 +126,7 @@ query = {{ query|safe }} if os.path.exists(fullpath) and not force_overwrite: if not self.__check_file(fullpath): print( - "Cannot upgrade '{}' map because it has been modified.".format( - mapobject.filename - ) + f"Cannot upgrade '{mapobject.filename}' map because it has been modified." ) return self.__checksums[mapobject.filename] mapcontent = utils.parse_map_file(fullpath) diff --git a/modoboa/core/management/commands/load_initial_data.py b/modoboa/core/management/commands/load_initial_data.py index 2285ab8c3..1fa35758a 100644 --- a/modoboa/core/management/commands/load_initial_data.py +++ b/modoboa/core/management/commands/load_initial_data.py @@ -91,7 +91,7 @@ class Command(BaseCommand): extension.load_initial_data() except Exception as e: self.stderr.write( - "Unable to load initial data for '{}' ({}).".format(extname, str(e)) + f"Unable to load initial data for '{extname}' ({str(e)})." ) else: signals.initial_data_loaded.send(sender=self.__class__, extname=extname) @@ -141,7 +141,7 @@ class Command(BaseCommand): base_frontend_dir = os.path.join( os.path.dirname(__file__), "../../../frontend_dist/" ) - frontend_target_dir = "{}/frontend".format(settings.BASE_DIR) + frontend_target_dir = f"{settings.BASE_DIR}/frontend" if os.path.exists(base_frontend_dir): shutil.rmtree(frontend_target_dir, ignore_errors=True) shutil.copytree(base_frontend_dir, frontend_target_dir) diff --git a/modoboa/core/models.py b/modoboa/core/models.py index 8f35eb108..b88c4c4d7 100644 --- a/modoboa/core/models.py +++ b/modoboa/core/models.py @@ -517,4 +517,4 @@ class ExtensionUpdateHistory(models.Model): unique_together = [("extension", "version")] def __str__(self): - return "{}: {}".format(self.extension, self.name) + return f"{self.extension}: {self.name}" diff --git a/modoboa/core/password_hashers/utils.py b/modoboa/core/password_hashers/utils.py index 25b1155b4..a317f9104 100644 --- a/modoboa/core/password_hashers/utils.py +++ b/modoboa/core/password_hashers/utils.py @@ -58,7 +58,7 @@ def get_dovecot_schemes(): internal_name=DOVEADM_PASS_SCHEME_ALARM, ) - return ["{{{}}}".format(smart_str(scheme)) for scheme in schemes.split()], status + return [f"{{{smart_str(scheme)}}}" for scheme in schemes.split()], status def cache_available_password_hasher(bypass_cache=False): diff --git a/modoboa/core/password_validation.py b/modoboa/core/password_validation.py index e21fc3f06..7bf2d8b3a 100644 --- a/modoboa/core/password_validation.py +++ b/modoboa/core/password_validation.py @@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError from django.utils.translation import gettext as _, ngettext -class ComplexityValidator(object): +class ComplexityValidator: """Check password contains at least a few things.""" def __init__(self, upper=1, lower=1, digits=1, specials=1): diff --git a/modoboa/core/sms_backends/__init__.py b/modoboa/core/sms_backends/__init__.py index f3abe9648..4f8ebb532 100644 --- a/modoboa/core/sms_backends/__init__.py +++ b/modoboa/core/sms_backends/__init__.py @@ -24,9 +24,9 @@ class SMSBackend: def get_backend_class(name): """Return class for given backend.""" - backend_module = import_module("modoboa.core.sms_backends.{}".format(name)) + backend_module = import_module(f"modoboa.core.sms_backends.{name}") try: - backend_class = getattr(backend_module, "{}Backend".format(name.upper())) + backend_class = getattr(backend_module, f"{name.upper()}Backend") except AttributeError: return None else: diff --git a/modoboa/core/sms_backends/ovh.py b/modoboa/core/sms_backends/ovh.py index a1c260ebe..0b04da74c 100644 --- a/modoboa/core/sms_backends/ovh.py +++ b/modoboa/core/sms_backends/ovh.py @@ -148,7 +148,7 @@ class OVHBackend(SMSBackend): """Send a new SMS to given recipients.""" services = self.client.get("/sms") result = self.client.post( - "/sms/{}/jobs".format(services[0]), + f"/sms/{services[0]}/jobs", message=text, receivers=recipients, priority="high", diff --git a/modoboa/core/templatetags/core_tags.py b/modoboa/core/templatetags/core_tags.py index e7285fcf3..948f524ba 100644 --- a/modoboa/core/templatetags/core_tags.py +++ b/modoboa/core/templatetags/core_tags.py @@ -295,5 +295,5 @@ def currencyfmt(amount): """Simple temp. filter to replace babel.""" lang = get_language() if lang == "fr": - return "{} €".format(amount) - return "€{}".format(amount) + return f"{amount} €" + return f"€{amount}" diff --git a/modoboa/core/tests/test_authentication.py b/modoboa/core/tests/test_authentication.py index b40832e4e..ef118c232 100644 --- a/modoboa/core/tests/test_authentication.py +++ b/modoboa/core/tests/test_authentication.py @@ -19,7 +19,7 @@ from modoboa.core.password_hashers.utils import get_dovecot_schemes from modoboa.lib.tests import NO_SMTP, ModoTestCase from .. import factories, models -DOVEADM_TEST_PATH = "{}/doveadm".format(os.path.dirname(__file__)) +DOVEADM_TEST_PATH = f"{os.path.dirname(__file__)}/doveadm" class MockedAttestedCredentialData: @@ -38,7 +38,7 @@ class AuthenticationTestCase(ModoTestCase): @classmethod def setUpTestData(cls): # NOQA:N802 """Create test data.""" - super(AuthenticationTestCase, cls).setUpTestData() + super().setUpTestData() from modoboa.admin.factories import MailboxFactory mailbox = MailboxFactory( @@ -322,7 +322,7 @@ class PasswordResetTestCase(ModoTestCase): @classmethod def setUpTestData(cls): # NOQA:N802 """Create test data.""" - super(PasswordResetTestCase, cls).setUpTestData() + super().setUpTestData() cls.account_ok = factories.UserFactory( username="user@test.com", secondary_email="test@ext.com", diff --git a/modoboa/core/tests/test_ldap.py b/modoboa/core/tests/test_ldap.py index d91d11359..4a4b88619 100644 --- a/modoboa/core/tests/test_ldap.py +++ b/modoboa/core/tests/test_ldap.py @@ -13,7 +13,7 @@ from .. import factories, models @skipIf(NO_LDAP, "No ldap module installed") -class LDAPTestCaseMixin(object): +class LDAPTestCaseMixin: """Set of methods used to test LDAP features.""" @cached_property diff --git a/modoboa/core/tests/test_views.py b/modoboa/core/tests/test_views.py index da8c4f866..7ba41f9bc 100644 --- a/modoboa/core/tests/test_views.py +++ b/modoboa/core/tests/test_views.py @@ -236,7 +236,7 @@ class SettingsTestCase(ModoTestCase): url = reverse("core:parameters") response = self.ajax_get(url) for app in ["core", "admin", "limits"]: - self.assertIn('data-app="{}"'.format(app), response["content"]) + self.assertIn(f'data-app="{app}"', response["content"]) def test_save_settings(self): """Test settings save.""" diff --git a/modoboa/core/tests/utils.py b/modoboa/core/tests/utils.py index 74106e195..c3a9968fc 100644 --- a/modoboa/core/tests/utils.py +++ b/modoboa/core/tests/utils.py @@ -2,4 +2,4 @@ import os def get_doveadm_test_path(): - return "{}/doveadm".format(os.path.dirname(__file__)) + return f"{os.path.dirname(__file__)}/doveadm" diff --git a/modoboa/core/views/dashboard.py b/modoboa/core/views/dashboard.py index be9e01e89..2c3550b6c 100644 --- a/modoboa/core/views/dashboard.py +++ b/modoboa/core/views/dashboard.py @@ -37,7 +37,7 @@ class DashboardView(auth_mixins.AccessMixin, generic.TemplateView): lang = "en" context.update({"selection": "dashboard"}) - feed_url = "{}{}/weblog/feeds/".format(MODOBOA_WEBSITE_URL, lang) + feed_url = f"{MODOBOA_WEBSITE_URL}{lang}/weblog/feeds/" if self.request.user.role != "SuperAdmins": custom_feed_url = self.request.localconfig.parameters.get_value( "rss_feed_url" @@ -57,7 +57,7 @@ class DashboardView(auth_mixins.AccessMixin, generic.TemplateView): "hide_features_widget" ) if self.request.user.is_superuser or not hide_features_widget: - url = "{}{}/api/projects/?featured=true".format(MODOBOA_WEBSITE_URL, lang) + url = f"{MODOBOA_WEBSITE_URL}{lang}/api/projects/?featured=true" features = [] if not settings.DISABLE_DASHBOARD_EXTERNAL_QUERIES: try: diff --git a/modoboa/dmarc/lib.py b/modoboa/dmarc/lib.py index 7e2bc1ee4..1c39b6e04 100644 --- a/modoboa/dmarc/lib.py +++ b/modoboa/dmarc/lib.py @@ -129,7 +129,7 @@ def import_report(content): else: print(f"Report skipped because of malformed data (empty {attr})") return - setattr(report, "policy_{}".format(attr), node.text) + setattr(report, f"policy_{attr}", node.text) report.save() for record in root.findall("record"): import_record(record, report) @@ -174,7 +174,7 @@ def import_report_from_email(content): fpo.seek(0) if file_type in FILE_TYPES: import_archive(fpo, content_type=part.get_content_type()) - except (OSError, IOError): + except OSError: print("Error: the attachment does not match the mimetype") err = True else: @@ -219,8 +219,8 @@ def week_range(year, weeknumber): """Return start and end dates of a given week.""" tz = timezone.get_current_timezone() fmt = "%Y-%W-%w" - start_week = datetime.datetime.strptime("{}-{}-{}".format(year, weeknumber, 1), fmt) - end_week = datetime.datetime.strptime("{}-{}-{}".format(year, weeknumber, 0), fmt) + start_week = datetime.datetime.strptime(f"{year}-{weeknumber}-{1}", fmt) + end_week = datetime.datetime.strptime(f"{year}-{weeknumber}-{0}", fmt) return start_week.replace(tzinfo=tz), end_week.replace(tzinfo=tz) @@ -237,7 +237,7 @@ def insert_record(target: dict, record, name: str) -> None: } target[name][record.source_ip]["total"] += record.count for typ in ["spf", "dkim"]: - result = getattr(record, "{}_result".format(typ)) + result = getattr(record, f"{typ}_result") key = "success" if result == "pass" else "failure" target[name][record.source_ip][typ][key] += record.count diff --git a/modoboa/dmarc/management/commands/import_aggregated_report.py b/modoboa/dmarc/management/commands/import_aggregated_report.py index fd16c7ac0..badb8a647 100644 --- a/modoboa/dmarc/management/commands/import_aggregated_report.py +++ b/modoboa/dmarc/management/commands/import_aggregated_report.py @@ -1,7 +1,5 @@ """Import a DMARC aggregated report.""" -from __future__ import print_function - from django.core.management.base import BaseCommand from ... import lib diff --git a/modoboa/dmarc/models.py b/modoboa/dmarc/models.py index 08fef1f78..a72204570 100644 --- a/modoboa/dmarc/models.py +++ b/modoboa/dmarc/models.py @@ -39,7 +39,7 @@ class Reporter(models.Model): def __str__(self): """Return name and email.""" - return "{} <{}>".format(self.org_name, self.email) + return f"{self.org_name} <{self.email}>" class Report(models.Model): @@ -63,7 +63,7 @@ class Report(models.Model): def __str__(self): """Display provider and dates.""" - return "{}: {} -> {}".format(self.reporter, self.start_date, self.end_date) + return f"{self.reporter}: {self.start_date} -> {self.end_date}" class Record(models.Model): diff --git a/modoboa/dmarc/templatetags/dmarc_tags.py b/modoboa/dmarc/templatetags/dmarc_tags.py index 74c5b2eb4..25307534e 100644 --- a/modoboa/dmarc/templatetags/dmarc_tags.py +++ b/modoboa/dmarc/templatetags/dmarc_tags.py @@ -16,20 +16,16 @@ def next_period(period): """Return next period.""" current = period.split("-") if int(current[1]) == 52: - week0 = datetime.datetime.strptime( - "{}-0-1".format(int(current[0]) + 1), "%Y-%W-%w" - ) - week1 = datetime.datetime.strptime( - "{}-1-1".format(int(current[0]) + 1), "%Y-%W-%w" - ) - week52 = datetime.datetime.strptime("{}-52-1".format(current[0]), "%Y-%W-%w") + week0 = datetime.datetime.strptime(f"{int(current[0]) + 1}-0-1", "%Y-%W-%w") + week1 = datetime.datetime.strptime(f"{int(current[0]) + 1}-1-1", "%Y-%W-%w") + week52 = datetime.datetime.strptime(f"{current[0]}-52-1", "%Y-%W-%w") if week0 == week52 or week0 == week1: parts = (int(current[0]) + 1, 1) else: parts = (int(current[0]) + 1, 0) else: parts = (current[0], int(current[1]) + 1) - return mark_safe("{}-{}".format(parts[0], parts[1])) + return mark_safe(f"{parts[0]}-{parts[1]}") @register.simple_tag @@ -37,24 +33,22 @@ def previous_period(period): """Return previous period.""" current = period.split("-") if int(current[1]) == 1: - week0 = datetime.datetime.strptime("{}-0-1".format(current[0]), "%Y-%W-%w") - week1 = datetime.datetime.strptime("{}-1-1".format(current[0]), "%Y-%W-%w") + week0 = datetime.datetime.strptime(f"{current[0]}-0-1", "%Y-%W-%w") + week1 = datetime.datetime.strptime(f"{current[0]}-1-1", "%Y-%W-%w") if week0 == week1: parts = (int(current[0]) - 1, 52) else: parts = (current[0], 0) elif int(current[1]) == 0: - week0 = datetime.datetime.strptime("{}-0-1".format(current[0]), "%Y-%W-%w") - week52 = datetime.datetime.strptime( - "{}-52-1".format(int(current[0]) - 1), "%Y-%W-%w" - ) + week0 = datetime.datetime.strptime(f"{current[0]}-0-1", "%Y-%W-%w") + week52 = datetime.datetime.strptime(f"{int(current[0]) - 1}-52-1", "%Y-%W-%w") if week0 == week52: parts = (int(current[0]) - 1, 51) else: parts = (int(current[0]) - 1, 52) else: parts = (current[0], int(current[1]) - 1) - return mark_safe("{}-{}".format(parts[0], parts[1])) + return mark_safe(f"{parts[0]}-{parts[1]}") @register.filter diff --git a/modoboa/dmarc/tests/mixins.py b/modoboa/dmarc/tests/mixins.py index 3f27def1a..393ad4ba9 100644 --- a/modoboa/dmarc/tests/mixins.py +++ b/modoboa/dmarc/tests/mixins.py @@ -7,12 +7,12 @@ import sys from django.core.management import call_command -class CallCommandMixin(object): +class CallCommandMixin: """A mixin to provide command execution shortcuts.""" - def setUp(self): + def setUp(self, *args, **kwargs): """Replace stdin""" - super(CallCommandMixin, self).setUp() + super().setUp(*args, **kwargs) self.stdin = sys.stdin def tearDown(self): diff --git a/modoboa/dmarc/tests/test_views.py b/modoboa/dmarc/tests/test_views.py index 97d3bcf7e..cf605b0fc 100644 --- a/modoboa/dmarc/tests/test_views.py +++ b/modoboa/dmarc/tests/test_views.py @@ -15,7 +15,7 @@ class DMARCViewsTestCase(mixins.CallCommandMixin, ModoTestCase): @classmethod def setUpTestData(cls): """Create test data.""" - super(DMARCViewsTestCase, cls).setUpTestData() + super().setUpTestData() cls.domain = admin_factories.DomainFactory(name="ngyn.org") def test_domainlist_view(self): @@ -34,7 +34,7 @@ class DMARCViewsTestCase(mixins.CallCommandMixin, ModoTestCase): user = core_models.User.objects.get(username="admin") self.client.force_login(user) url = reverse("dmarc:domain_report", args=[self.domain.pk]) - response = self.client.get("{}?period=2015-26".format(url)) + response = self.client.get(f"{url}?period=2015-26") self.assertContains(response, "'Failed', 100.0") def test_domainreport_view_week0(self): @@ -43,7 +43,7 @@ class DMARCViewsTestCase(mixins.CallCommandMixin, ModoTestCase): user = core_models.User.objects.get(username="admin") self.client.force_login(user) url = reverse("dmarc:domain_report", args=[self.domain.pk]) - response = self.client.get("{}?period=2019-0".format(url)) + response = self.client.get(f"{url}?period=2019-0") self.assertContains(response, "Dec. 31, 2018") def test_domainreport_view_week1(self): @@ -52,7 +52,7 @@ class DMARCViewsTestCase(mixins.CallCommandMixin, ModoTestCase): user = core_models.User.objects.get(username="admin") self.client.force_login(user) url = reverse("dmarc:domain_report", args=[self.domain.pk]) - response = self.client.get("{}?period=2019-1".format(url)) + response = self.client.get(f"{url}?period=2019-1") self.assertContains(response, "Jan. 7, 2019") def test_domainreport_view_week52(self): @@ -61,7 +61,7 @@ class DMARCViewsTestCase(mixins.CallCommandMixin, ModoTestCase): user = core_models.User.objects.get(username="admin") self.client.force_login(user) url = reverse("dmarc:domain_report", args=[self.domain.pk]) - response = self.client.get("{}?period=2018-52".format(url)) + response = self.client.get(f"{url}?period=2018-52") self.assertContains(response, "Dec. 30, 2018") def test_domainreport_view_arc(self): @@ -70,7 +70,7 @@ class DMARCViewsTestCase(mixins.CallCommandMixin, ModoTestCase): user = core_models.User.objects.get(username="admin") self.client.force_login(user) url = reverse("dmarc:domain_report", args=[self.domain.pk]) - response = self.client.get("{}?period=2021-22".format(url)) + response = self.client.get(f"{url}?period=2021-22") self.assertContains(response, "'Fully aligned', 86.0") self.assertContains(response, "'Partially aligned', 8.0") self.assertContains(response, "'Forwarded', 4.0") diff --git a/modoboa/dmarc/views.py b/modoboa/dmarc/views.py index 547308bea..ca2c06d5f 100644 --- a/modoboa/dmarc/views.py +++ b/modoboa/dmarc/views.py @@ -33,7 +33,7 @@ def insert_record(target, record, name): } target[name][record.source_ip]["total"] += record.count for typ in ["spf", "dkim"]: - result = getattr(record, "{}_result".format(typ)) + result = getattr(record, f"{typ}_result") target[name][record.source_ip][typ][result] += record.count @@ -41,8 +41,8 @@ def week_range(year, weeknumber): """Return start and end dates of a given week.""" tz = timezone.get_current_timezone() fmt = "%Y-%W-%w" - start_week = datetime.datetime.strptime("{}-{}-{}".format(year, weeknumber, 1), fmt) - end_week = datetime.datetime.strptime("{}-{}-{}".format(year, weeknumber, 0), fmt) + start_week = datetime.datetime.strptime(f"{year}-{weeknumber}-{1}", fmt) + end_week = datetime.datetime.strptime(f"{year}-{weeknumber}-{0}", fmt) return start_week.replace(tzinfo=tz), end_week.replace(tzinfo=tz) @@ -58,7 +58,7 @@ class DomainReportView(auth_mixins.PermissionRequiredMixin, generic.TemplateView if not self.period: year, week, day = timezone.now().isocalendar() week -= 1 - self.period = "{}-{}".format(year, week) + self.period = f"{year}-{week}" else: year, week = self.period.split("-") diff --git a/modoboa/dnstools/lib.py b/modoboa/dnstools/lib.py index 3920e4595..cd4cfdc41 100644 --- a/modoboa/dnstools/lib.py +++ b/modoboa/dnstools/lib.py @@ -17,7 +17,7 @@ def _get_record_type_value(records, rr_type): # Multiple strings are separated by a space as described in: # https://tools.ietf.org/html/rfc4408#section-3.1.3 value = str(record).replace('" "', "").strip('"') - if value.startswith("v={}".format(rr_type)): + if value.startswith(f"v={rr_type}"): return value return None @@ -30,14 +30,14 @@ def get_spf_record(domain): def get_dkim_record(domain, selector): """Return DKIM records form domain (if any).""" - name = "{}._domainkey.{}".format(selector, domain) + name = f"{selector}._domainkey.{domain}" records = admin_lib.get_dns_records(name, "TXT") return _get_record_type_value(records, "DKIM1") def get_dmarc_record(domain): """Return DMARC record for domain (if any).""" - name = "_dmarc.{}".format(domain) + name = f"_dmarc.{domain}" records = admin_lib.get_dns_records(name, "TXT") return _get_record_type_value(records, "DMARC1") @@ -61,12 +61,12 @@ def _get_simple_record(name): def get_autoconfig_record(domain): """Return autoconfig record for domain (if any).""" - return _get_simple_record("autoconfig.{}".format(domain)) + return _get_simple_record(f"autoconfig.{domain}") def get_autodiscover_record(domain): """Return autodiscover record for domain (if any).""" - return _get_simple_record("autodiscover.{}".format(domain)) + return _get_simple_record(f"autodiscover.{domain}") class DNSSyntaxError(Exception): @@ -167,7 +167,7 @@ def check_spf_syntax(record): continue for mechanism in constants.SPF_MECHANISMS: if part.startswith(mechanism): - globals()["check_spf_{}".format(mechanism)](part) + globals()[f"check_spf_{mechanism}"](part) mechanisms.append(mechanism) break else: diff --git a/modoboa/dnstools/models.py b/modoboa/dnstools/models.py index e74e495c7..e606dbd7e 100644 --- a/modoboa/dnstools/models.py +++ b/modoboa/dnstools/models.py @@ -50,7 +50,7 @@ class DNSRecord(models.Model): objects = DNSRecordManager() def __str__(self): - return "{} ({}): {}".format(self.domain, self.type, self.value) + return f"{self.domain} ({self.type}): {self.value}" def get_dns_record(self): """Retrieve corresponding DNS record.""" @@ -59,13 +59,13 @@ class DNSRecord(models.Model): self.domain.name, self.domain.dkim_key_selector ) else: - func = getattr(lib, "get_{}_record".format(self.type)) + func = getattr(lib, f"get_{self.type}_record") self.value = func(self.domain.name) def check_syntax(self, ttl=7200): """Check record syntax.""" try: - func = getattr(lib, "check_{}_syntax".format(self.type)) + func = getattr(lib, f"check_{self.type}_syntax") result = func(self.value) except lib.DNSSyntaxError as err: self.error = str(err)[:50] diff --git a/modoboa/imap_migration/__init__.py b/modoboa/imap_migration/__init__.py index 62d56a35c..6b3d80114 100644 --- a/modoboa/imap_migration/__init__.py +++ b/modoboa/imap_migration/__init__.py @@ -1,9 +1,5 @@ -# -*- coding: utf-8 -*- - """A plugin to migrate mailboxes using IMAP.""" -from __future__ import unicode_literals - from pkg_resources import get_distribution, DistributionNotFound diff --git a/modoboa/imap_migration/api/v2/viewsets.py b/modoboa/imap_migration/api/v2/viewsets.py index ef069ac20..91d92337b 100644 --- a/modoboa/imap_migration/api/v2/viewsets.py +++ b/modoboa/imap_migration/api/v2/viewsets.py @@ -1,7 +1,6 @@ """Viewsets for API V2 for imap_migration.""" import imaplib -import socket import ssl from rest_framework import response, filters, mixins, permissions, viewsets @@ -38,7 +37,7 @@ class EmailProviderViewSet(GetThrottleViewsetMixin, viewsets.ModelViewSet): imaplib.IMAP4_SSL(address, port) else: imaplib.IMAP4(address, port) - except (socket.error, imaplib.IMAP4.error, ssl.SSLError): + except (OSError, imaplib.IMAP4.error, ssl.SSLError): return response.Response({"status": "failed"}, 400) return response.Response(status=200) diff --git a/modoboa/imap_migration/auth_backends.py b/modoboa/imap_migration/auth_backends.py index 1765e8fda..fd84b4969 100644 --- a/modoboa/imap_migration/auth_backends.py +++ b/modoboa/imap_migration/auth_backends.py @@ -1,7 +1,6 @@ """IMAP authentication backend for Django.""" import imaplib -import socket import ssl from django.utils.encoding import smart_bytes, smart_str @@ -45,8 +44,10 @@ class IMAPBackend: conn = imaplib.IMAP4_SSL(address, port) else: conn = imaplib.IMAP4(address, port) - except (socket.error, imaplib.IMAP4.error, ssl.SSLError) as error: - raise ModoboaException(_("Connection to IMAP server failed: %s") % error) + except (OSError, imaplib.IMAP4.error, ssl.SSLError) as error: + raise ModoboaException( + _(f"Connection to IMAP server failed: {error}") + ) from None try: typ, data = conn.login(smart_bytes(username), smart_str(password)) @@ -68,9 +69,7 @@ class IMAPBackend: orig_username = username # Check if old addresses must be converted if self.provider_domain.new_domain: - username = "{}@{}".format( - self.address, self.provider_domain.new_domain.name - ) + username = f"{self.address}@{self.provider_domain.new_domain.name}" user, created = core_models.User.objects.get_or_create( username__iexact=username, defaults={"username": username.lower(), "email": username.lower()}, diff --git a/modoboa/imap_migration/factories.py b/modoboa/imap_migration/factories.py index f4c8f12f8..5a567fe0b 100644 --- a/modoboa/imap_migration/factories.py +++ b/modoboa/imap_migration/factories.py @@ -13,7 +13,7 @@ class EmailProviderFactory(factory.django.DjangoModelFactory): class Meta: model = models.EmailProvider - name = factory.Sequence(lambda n: "Provider {}".format(n)) + name = factory.Sequence(lambda n: f"Provider {n}") address = "127.0.0.1" port = 143 diff --git a/modoboa/imap_migration/management/commands/generate_offlineimap_config.py b/modoboa/imap_migration/management/commands/generate_offlineimap_config.py index 7d18d77ef..aa76b79d2 100644 --- a/modoboa/imap_migration/management/commands/generate_offlineimap_config.py +++ b/modoboa/imap_migration/management/commands/generate_offlineimap_config.py @@ -33,7 +33,7 @@ class Command(BaseCommand): "imap_create_folders": conf["create_folders"], "imap_folder_filter_exclude": conf["folder_filter_exclude"], "imap_folder_filter_include": ", ".join( - "'{0}'".format(w) for w in conf["folder_filter_include"].split(",") + f"'{w}'" for w in conf["folder_filter_include"].split(",") ), "migrations": Migration.objects.select_related( "provider", "mailbox__domain" diff --git a/modoboa/imap_migration/tests.py b/modoboa/imap_migration/tests.py index 8093f9d5f..ac36ccf65 100644 --- a/modoboa/imap_migration/tests.py +++ b/modoboa/imap_migration/tests.py @@ -21,7 +21,7 @@ from . import factories from . import models -class DataMixin(object): +class DataMixin: """A mixin to provide test data.""" @classmethod diff --git a/modoboa/ldapsync/lib.py b/modoboa/ldapsync/lib.py index 1836e3b44..eb34b0bb6 100644 --- a/modoboa/ldapsync/lib.py +++ b/modoboa/ldapsync/lib.py @@ -14,7 +14,7 @@ from modoboa.lib.exceptions import InternalError def create_connection(srv_address, srv_port, config, username, password): """Create a new connection with given server.""" - uri = "{}:{}".format(srv_address, srv_port) + uri = f"{srv_address}:{srv_port}" uri = "{}://{}".format("ldaps" if config["ldap_secured"] == "ssl" else "ldap", uri) conn = ldap.initialize(uri) conn.protocol_version = 3 @@ -152,7 +152,7 @@ def find_user_groups(conn, config, dn, entry): or config["ldap_group_type"] == "groupofnames" ) if condition: - flt = "(member={})".format(dn) + flt = f"(member={dn})" elif config["ldap_group_type"] == "posixgroup": flt = "(memberUid={})".format(force_str(entry["uid"][0])) @@ -204,7 +204,7 @@ def import_accounts_from_ldap(config): break if email is None: if grp == "SimpleUsers": - print("Skipping {} because no email found".format(dn)) + print(f"Skipping {dn} because no email found") continue else: username = email @@ -235,8 +235,8 @@ def build_ldap_uri(config, node=""): node += "_" return "{}://{}:{}".format( "ldaps" if config["ldap_secured"] == "ssl" else "ldap", - config["ldap_{}server_address".format(node)], - config["ldap_{}server_port".format(node)], + config[f"ldap_{node}server_address"], + config[f"ldap_{node}server_port"], ) @@ -259,18 +259,11 @@ def update_dovecot_config_file(config): with open(conf_file, "w") as fp: fp.write( - """uris = {uris} + f"""uris = {uris} dn = "{bind_dn}" dnpass = '{bind_pwd}' base = {base} user_filter = {user_filter} -pass_filter = {pass_filter} -""".format( - uris=uris, - bind_dn=bind_dn, - bind_pwd=bind_pwd, - base=base, - user_filter=user_filter, - pass_filter=user_filter, - ) +pass_filter = {user_filter} +""" ) diff --git a/modoboa/lib/api_client.py b/modoboa/lib/api_client.py index 1f485fd55..0d1a11033 100644 --- a/modoboa/lib/api_client.py +++ b/modoboa/lib/api_client.py @@ -12,7 +12,7 @@ from django.utils.translation import gettext as _ logger = logging.getLogger("modoboa.admin") -class ModoAPIClient(object): +class ModoAPIClient: """A simple client for the public API.""" def __init__(self, api_url=None): @@ -57,10 +57,10 @@ class ModoAPIClient(object): def register_instance(self, hostname): """Register this instance.""" - url = "{}instances/search/?hostname={}".format(self._api_url, hostname) + url = f"{self._api_url}instances/search/?hostname={hostname}" instance = self.__send_request(url) if instance is None: - url = "{}instances/".format(self._api_url) + url = f"{self._api_url}instances/" data = {"hostname": hostname, "known_version": self.local_core_version} try: response = requests.post(url, data=data) @@ -76,7 +76,7 @@ class ModoAPIClient(object): def update_instance(self, pk, data): """Update instance and send stats.""" - url = "{}instances/{}/".format(self._api_url, pk) + url = f"{self._api_url}instances/{pk}/" try: response = requests.put(url, data=data) except RequestException as err: @@ -89,5 +89,5 @@ class ModoAPIClient(object): def versions(self): """Fetch core and extension versions.""" - url = "{}versions/".format(self._api_url) + url = f"{self._api_url}versions/" return self.__send_request(url) diff --git a/modoboa/lib/authbackends.py b/modoboa/lib/authbackends.py index 543efd0f8..1711dbb82 100644 --- a/modoboa/lib/authbackends.py +++ b/modoboa/lib/authbackends.py @@ -9,7 +9,7 @@ from modoboa.lib.email_utils import split_mailbox from modoboa.parameters import tools as param_tools -class SMTPBackend(object): +class SMTPBackend: """A backend to authenticate against an SMTP server.""" def authenticate(self, request, username=None, password=None): @@ -126,7 +126,7 @@ try: @classmethod def setting_fullname(cls, setting): """Return fullname for given setting.""" - return "{}{}".format(cls.settings_prefix, setting) + return f"{cls.settings_prefix}{setting}" class LDAPBackend(LDAPBackendBase): """Primary LDAP backend.""" diff --git a/modoboa/lib/email_utils.py b/modoboa/lib/email_utils.py index b216336bc..78e868948 100644 --- a/modoboa/lib/email_utils.py +++ b/modoboa/lib/email_utils.py @@ -31,7 +31,7 @@ class EmailAddress: def __init__(self, address): self.name, self.address = u2u_decode.decode_address(address) if self.name: - self.fulladdress = "{} <{}>".format(self.name, self.address) + self.fulladdress = f"{self.name} <{self.address}>" else: self.fulladdress = self.address @@ -446,7 +446,7 @@ def sendmail_fromfile(sender, rcpt, fname): """ try: fp = open(fname) - except IOError as e: + except OSError as e: return False, str(e) content = """From: %s diff --git a/modoboa/lib/form_utils.py b/modoboa/lib/form_utils.py index 0b6d3e750..06ff5a9e2 100644 --- a/modoboa/lib/form_utils.py +++ b/modoboa/lib/form_utils.py @@ -17,7 +17,7 @@ from modoboa.lib.web_utils import render_to_json_response ABC = abc.ABCMeta(force_str("ABC"), (object,), {}) -class WizardStep(object): +class WizardStep: """A wizard step.""" def __init__(self, uid, formclass, title, formtpl=None, new_args=None): @@ -188,7 +188,7 @@ class WizardForm(ABC): """ -class DynamicForm(object): +class DynamicForm: """ A form which accepts dynamic fields. @@ -234,7 +234,7 @@ class DynamicForm(object): self.data = ndata -class TabForms(object): +class TabForms: """ Simple forms container. diff --git a/modoboa/lib/paginator.py b/modoboa/lib/paginator.py index 36f534879..0ebc5ac2e 100644 --- a/modoboa/lib/paginator.py +++ b/modoboa/lib/paginator.py @@ -3,7 +3,7 @@ Custom pagination tools. """ -class Page(object): +class Page: """Class to represent a page.""" def __init__(self, paginator, pageid, id_start, id_stop, baseurl=None): @@ -66,7 +66,7 @@ class Page(object): return lid -class Paginator(object): +class Paginator: """Pagination class.""" def __init__(self, total, elems_per_page): diff --git a/modoboa/lib/sysutils.py b/modoboa/lib/sysutils.py index ded7c0a55..64c79647b 100644 --- a/modoboa/lib/sysutils.py +++ b/modoboa/lib/sysutils.py @@ -75,7 +75,7 @@ def doveadm_cmd(params, pinput=None, capture_output=True, **kwargs): sudo_user = dovecot_user if curuser != dovecot_user else None if dpath: return exec_cmd( - "{} {}".format(dpath, params), + f"{dpath} {params}", sudo_user=sudo_user, pinput=pinput, capture_output=capture_output, diff --git a/modoboa/lib/templatetags/form_tags.py b/modoboa/lib/templatetags/form_tags.py index 4bd43ceb7..f346f8e7a 100644 --- a/modoboa/lib/templatetags/form_tags.py +++ b/modoboa/lib/templatetags/form_tags.py @@ -94,4 +94,4 @@ def render_field_width(field): width = form.field_widths[field.name] else: width = 5 - return "col-sm-{0}".format(width) + return f"col-sm-{width}" diff --git a/modoboa/lib/test_utils.py b/modoboa/lib/test_utils.py index 60fc012fd..804d2c7e9 100644 --- a/modoboa/lib/test_utils.py +++ b/modoboa/lib/test_utils.py @@ -7,7 +7,7 @@ import tempfile from django.core.management import call_command -class MapFilesTestCaseMixin(object): +class MapFilesTestCaseMixin: """A generic test case to check map files generation.""" MAP_FILES = None @@ -21,13 +21,13 @@ class MapFilesTestCaseMixin(object): shutil.rmtree(self.workdir) def _test_maps_generation(self, engine): - dburl = "{0}://user:password@localhost/testdb".format(engine) + dburl = f"{engine}://user:password@localhost/testdb" call_command( "generate_postfix_maps", "--dburl", dburl, "--destdir", self.workdir ) for mapfile in self.MAP_FILES: - path = "{0}/{1}".format(self.workdir, mapfile) + path = f"{self.workdir}/{mapfile}" self.assertTrue(os.path.exists(path)) with open(path) as fpo: content = fpo.read() diff --git a/modoboa/lib/tests/__init__.py b/modoboa/lib/tests/__init__.py index 23425ef51..ca3cb975b 100644 --- a/modoboa/lib/tests/__init__.py +++ b/modoboa/lib/tests/__init__.py @@ -19,7 +19,7 @@ try: s = socket.create_connection(("127.0.0.1", 25)) s.close() NO_SMTP = False -except socket.error: +except OSError: NO_SMTP = True try: @@ -36,7 +36,7 @@ class ParametersMixin: @classmethod def setUpTestData(cls): # noqa """Set LocalConfig instance.""" - super(ParametersMixin, cls).setUpTestData() + super().setUpTestData() cls.localconfig = core_models.LocalConfig.objects.first() def set_global_parameter(self, name, value, app=None): @@ -64,7 +64,7 @@ class ModoTestCase(ParametersMixin, TestCase): @classmethod def setUpTestData(cls): # noqa """Create a default user.""" - super(ModoTestCase, cls).setUpTestData() + super().setUpTestData() management.call_command("load_initial_data", "--no-frontend") def setUp(self, username="admin", password="password"): @@ -101,7 +101,7 @@ class ModoAPITestCase(ParametersMixin, APITestCase): @classmethod def setUpTestData(cls): # noqa """Create a default user.""" - super(ModoAPITestCase, cls).setUpTestData() + super().setUpTestData() management.call_command("load_initial_data") cls.token = Token.objects.create( user=core_models.User.objects.get(username="admin") @@ -109,7 +109,7 @@ class ModoAPITestCase(ParametersMixin, APITestCase): def setUp(self): """Setup.""" - super(ModoAPITestCase, self).setUp() + super().setUp() self.client.credentials(HTTP_AUTHORIZATION="Token " + self.token.key) self.workdir = tempfile.mkdtemp() self.set_global_parameter("storage_dir", self.workdir, app="pdfcredentials") diff --git a/modoboa/lib/validators.py b/modoboa/lib/validators.py index e54c48ca5..67b868d9d 100644 --- a/modoboa/lib/validators.py +++ b/modoboa/lib/validators.py @@ -7,7 +7,7 @@ from django.core.validators import EmailValidator, URLValidator from django.utils.translation import gettext_lazy -class HostnameValidator(object): +class HostnameValidator: """Validator for fqdn.""" message = gettext_lazy("Enter a valid domain name") diff --git a/modoboa/lib/web_utils.py b/modoboa/lib/web_utils.py index ae6333e79..9ba0f31db 100644 --- a/modoboa/lib/web_utils.py +++ b/modoboa/lib/web_utils.py @@ -138,11 +138,11 @@ def size2integer(value, output_unit="B"): if m.group(2)[0] in ["G", "g"]: return int(m.group(1)) * 2**10 else: - raise ValueError("Unsupported output unit {}".format(output_unit)) + raise ValueError(f"Unsupported output unit {output_unit}") return 0 -class NavigationParameters(object): +class NavigationParameters: """ Just a simple object to manipulate navigation parameters. """ diff --git a/modoboa/limits/api/v1/tests.py b/modoboa/limits/api/v1/tests.py index 8b6c0bcfe..ffa446fb1 100644 --- a/modoboa/limits/api/v1/tests.py +++ b/modoboa/limits/api/v1/tests.py @@ -22,7 +22,7 @@ class APIAdminLimitsTestCase(lib_tests.ModoAPITestCase): """Create test data.""" super(APIAdminLimitsTestCase, cls).setUpTestData() for name, _definition in utils.get_user_limit_templates(): - cls.localconfig.parameters.set_value("deflt_user_{0}_limit".format(name), 2) + cls.localconfig.parameters.set_value(f"deflt_user_{name}_limit", 2) cls.localconfig.save() populate_database() cls.user = User.objects.get(username="admin@test.com") @@ -192,9 +192,7 @@ class APIDomainLimitsTestCase(lib_tests.ModoAPITestCase): super(APIDomainLimitsTestCase, cls).setUpTestData() cls.localconfig.parameters.set_value("enable_domain_limits", True) for name, _definition in utils.get_domain_limit_templates(): - cls.localconfig.parameters.set_value( - "deflt_domain_{0}_limit".format(name), 2 - ) + cls.localconfig.parameters.set_value(f"deflt_domain_{name}_limit", 2) cls.localconfig.save() populate_database() @@ -255,7 +253,7 @@ class ResourcesAPITestCase(lib_tests.ModoAPITestCase): """Create test data.""" super(ResourcesAPITestCase, cls).setUpTestData() for name, _definition in utils.get_user_limit_templates(): - cls.localconfig.parameters.set_value("deflt_user_{0}_limit".format(name), 2) + cls.localconfig.parameters.set_value(f"deflt_user_{name}_limit", 2) cls.localconfig.save() populate_database() cls.user = User.objects.get(username="admin@test.com") @@ -282,12 +280,12 @@ class ResourcesAPITestCase(lib_tests.ModoAPITestCase): compare(expected, response.data) # As reseller => fails - self.client.credentials(HTTP_AUTHORIZATION="Token {}".format(self.r_token.key)) + self.client.credentials(HTTP_AUTHORIZATION=f"Token {self.r_token.key}") response = self.client.get(url) self.assertEqual(response.status_code, 404) # As domain admin => fails - self.client.credentials(HTTP_AUTHORIZATION="Token {}".format(self.da_token.key)) + self.client.credentials(HTTP_AUTHORIZATION=f"Token {self.da_token.key}") response = self.client.get(url) self.assertEqual(response.status_code, 404) @@ -304,7 +302,7 @@ class ResourcesAPITestCase(lib_tests.ModoAPITestCase): ) # As domain admin => fails - self.client.credentials(HTTP_AUTHORIZATION="Token {}".format(self.da_token.key)) + self.client.credentials(HTTP_AUTHORIZATION=f"Token {self.da_token.key}") resources.update({"domains": 2, "mailboxes": 2}) url = reverse("v1:resources-detail", args=[self.user.pk]) response = self.client.put(url, resources) @@ -312,7 +310,7 @@ class ResourcesAPITestCase(lib_tests.ModoAPITestCase): # As reseller => ok permissions.grant_access_to_object(self.reseller, self.user, True) - self.client.credentials(HTTP_AUTHORIZATION="Token {}".format(self.r_token.key)) + self.client.credentials(HTTP_AUTHORIZATION=f"Token {self.r_token.key}") resources.update({"domains": 500, "mailboxes": 500}) url = reverse("v1:resources-detail", args=[self.user.pk]) response = self.client.put(url, resources) diff --git a/modoboa/limits/forms.py b/modoboa/limits/forms.py index 6c226b842..13d201a85 100644 --- a/modoboa/limits/forms.py +++ b/modoboa/limits/forms.py @@ -22,7 +22,7 @@ class ResourcePoolForm(forms.Form): ) if condition: continue - self.fields["{}_limit".format(name)] = forms.IntegerField( + self.fields[f"{name}_limit"] = forms.IntegerField( label=tpl["label"], help_text=tpl["help"] ) if self.account: @@ -49,7 +49,7 @@ class ResourcePoolForm(forms.Form): def save(self): owner = get_object_owner(self.account) for name, _definition in utils.get_user_limit_templates(): - fieldname = "{}_limit".format(name) + fieldname = f"{name}_limit" if fieldname not in self.cleaned_data: continue limit = self.account.userobjectlimit_set.get(name=name) @@ -67,7 +67,7 @@ class DomainLimitsForm(forms.Form): self.domain = kwargs.pop("instance", None) super(DomainLimitsForm, self).__init__(*args, **kwargs) for name, tpl in utils.get_domain_limit_templates(): - self.fields["{}_limit".format(name)] = forms.IntegerField( + self.fields[f"{name}_limit"] = forms.IntegerField( label=tpl["label"], help_text=tpl["help"] ) if not self.domain: @@ -91,7 +91,7 @@ class DomainLimitsForm(forms.Form): def save(self, user, **kwargs): """Set limits.""" for name, _definition in utils.get_domain_limit_templates(): - fieldname = "{}_limit".format(name) + fieldname = f"{name}_limit" if fieldname not in self.cleaned_data: continue limit = self.domain.domainobjectlimit_set.get(name=name) diff --git a/modoboa/limits/handlers.py b/modoboa/limits/handlers.py index 6b3bc325c..8d44367dd 100644 --- a/modoboa/limits/handlers.py +++ b/modoboa/limits/handlers.py @@ -51,7 +51,7 @@ def create_user_limits(sender, instance, **kwargs): max_value = 0 # creator can be None if user was created by a factory if not creator or creator.is_superuser: - max_value = global_params["deflt_user_{0}_limit".format(name)] + max_value = global_params[f"deflt_user_{name}_limit"] models.UserObjectLimit.objects.create( user=instance, name=name, content_type=ct, max_value=max_value ) @@ -64,7 +64,7 @@ def create_domain_limits(sender, instance, **kwargs): return global_params = dict(param_tools.get_global_parameters("limits")) for name, _definition in utils.get_domain_limit_templates(): - max_value = global_params["deflt_domain_{0}_limit".format(name)] + max_value = global_params[f"deflt_domain_{name}_limit"] models.DomainObjectLimit.objects.create( domain=instance, name=name, max_value=max_value ) diff --git a/modoboa/limits/models.py b/modoboa/limits/models.py index e0f59b227..d535e5bcd 100644 --- a/modoboa/limits/models.py +++ b/modoboa/limits/models.py @@ -9,7 +9,7 @@ from modoboa.core import models as core_models from . import lib, utils -class ObjectLimitMixin(object): +class ObjectLimitMixin: """Common methods.""" @property @@ -54,7 +54,7 @@ class ObjectLimitMixin(object): """Display current usage.""" if self.max_value == -1: return _("unlimited") - return "{}%".format(self.usage) + return f"{self.usage}%" class UserObjectLimit(ObjectLimitMixin, models.Model): @@ -67,7 +67,7 @@ class UserObjectLimit(ObjectLimitMixin, models.Model): ) max_value = models.IntegerField(default=0) - class Meta(object): + class Meta: unique_together = (("user", "name"),) @property @@ -117,7 +117,7 @@ class UserObjectLimit(ObjectLimitMixin, models.Model): """Display current usage.""" if self.max_value == -1: return _("unlimited") - return "{}%".format(self.usage) + return f"{self.usage}%" class DomainObjectLimit(ObjectLimitMixin, models.Model): @@ -127,7 +127,7 @@ class DomainObjectLimit(ObjectLimitMixin, models.Model): name = models.CharField(max_length=254) max_value = models.IntegerField(default=0) - class Meta(object): + class Meta: unique_together = (("domain", "name"),) @property @@ -143,7 +143,7 @@ class DomainObjectLimit(ObjectLimitMixin, models.Model): """Return the current number of objects.""" definition = self.definition if not definition: - raise RuntimeError("Bad limit {}".format(self.name)) + raise RuntimeError(f"Bad limit {self.name}") relation = getattr(self.domain, definition["relation"]) if "extra_filters" in definition: relation = relation.filter(**definition["extra_filters"]) diff --git a/modoboa/limits/tests/test_domain_limits.py b/modoboa/limits/tests/test_domain_limits.py index 2ec872fd2..2502bf47d 100644 --- a/modoboa/limits/tests/test_domain_limits.py +++ b/modoboa/limits/tests/test_domain_limits.py @@ -16,14 +16,12 @@ class DomainLimitsTestCase(lib_tests.ModoTestCase): @classmethod def setUpTestData(cls): # NOQA:N802 """Create test data.""" - super(DomainLimitsTestCase, cls).setUpTestData() + super().setUpTestData() cls.localconfig.parameters.set_values( {"enable_admin_limits": False, "enable_domain_limits": True} ) for name, _definition in utils.get_domain_limit_templates(): - cls.localconfig.parameters.set_value( - "deflt_domain_{0}_limit".format(name), 2 - ) + cls.localconfig.parameters.set_value(f"deflt_domain_{name}_limit", 2) cls.localconfig.save() admin_factories.populate_database() diff --git a/modoboa/limits/tests/test_import.py b/modoboa/limits/tests/test_import.py index 08b4a4edd..72d5f5b14 100644 --- a/modoboa/limits/tests/test_import.py +++ b/modoboa/limits/tests/test_import.py @@ -27,18 +27,16 @@ class LimitImportTestCase(ModoTestCase): self.client.force_login(self.reseller) self.assertFalse(limit.is_exceeded()) f = ContentFile( - """domainalias; domalias1.com; {domain}; True -domainalias; domalias2.com; {domain}; True -""".format( - domain=self.domain - ), + f"""domainalias; domalias1.com; {self.domain}; True +domainalias; domalias2.com; {self.domain}; True +""", name="domains.csv", ) self.client.post(reverse("admin:domain_import"), {"sourcefile": f}) self.assertTrue(limit.is_exceeded()) f = ContentFile( - "domainalias; domalias3.com; {}; True".format(self.domain), + f"domainalias; domalias3.com; {self.domain}; True", name="domains.csv", ) response = self.client.post(reverse("admin:domain_import"), {"sourcefile": f}) @@ -51,20 +49,16 @@ domainalias; domalias2.com; {domain}; True content = "" for cpt in range(initial_count, 2): content += ( - "account; admin{cpt}@{domain}; toto; User; One; True; " - "DomainAdmins; user{cpt}@{domain}; 5; {domain}\n".format( - domain=self.domain, cpt=cpt - ) + f"account; admin{cpt}@{self.domain}; toto; User; One; True; " + f"DomainAdmins; user{cpt}@{self.domain}; 5; {self.domain}\n" ) f = ContentFile(content, name="domain_admins.csv") response = self.client.post(reverse("admin:identity_import"), {"sourcefile": f}) self.assertTrue(limit.is_exceeded()) f = ContentFile( - """account; admin3@{domain}; toto; User; One; True; DomainAdmins; admin3@{domain}; 5; {domain} -""".format( - domain=self.domain - ), + f"""account; admin3@{self.domain}; toto; User; One; True; DomainAdmins; admin3@{self.domain}; 5; {self.domain} +""", name="domain_admins.csv", ) # NOQA:E501 response = self.client.post(reverse("admin:identity_import"), {"sourcefile": f}) @@ -75,21 +69,19 @@ domainalias; domalias2.com; {domain}; True self.client.force_login(self.dadmin) self.assertFalse(limit.is_exceeded()) f = ContentFile( - "account; user1@{domain}; toto; User; One; True; SimpleUsers; " - "user1@{domain}; 5\r\n" - "account; truc@{domain}; toto; René; Truc; True; SimpleUsers; " - "truc@{domain}; 5\r\n".format(domain=self.domain), + f"account; user1@{self.domain}; toto; User; One; True; SimpleUsers; " + f"user1@{self.domain}; 5\r\n" + f"account; truc@{self.domain}; toto; René; Truc; True; SimpleUsers; " + f"truc@{self.domain}; 5\r\n", name="domains.csv", ) response = self.client.post(reverse("admin:identity_import"), {"sourcefile": f}) self.assertTrue(limit.is_exceeded()) f = ContentFile( - """ -account; user3@{domain}; toto; User; One; True; SimpleUsers; user3@{domain}; 5 -""".format( - domain=self.domain - ), + f""" +account; user3@{self.domain}; toto; User; One; True; SimpleUsers; user3@{self.domain}; 5 +""", name="domains.csv", ) response = self.client.post(reverse("admin:identity_import"), {"sourcefile": f}) @@ -100,22 +92,18 @@ account; user3@{domain}; toto; User; One; True; SimpleUsers; user3@{domain}; 5 self.client.force_login(self.dadmin) self.assertFalse(limit.is_exceeded()) f = ContentFile( - """alias; alias1@{domain}; True; user@{domain} -alias; alias2@{domain}; True; user@{domain} -""".format( - domain=self.domain - ), + f"""alias; alias1@{self.domain}; True; user@{self.domain} +alias; alias2@{self.domain}; True; user@{self.domain} +""", name="aliases.csv", ) self.client.post(reverse("admin:identity_import"), {"sourcefile": f}) self.assertTrue(limit.is_exceeded()) f = ContentFile( - """ -alias; alias3@{domain}; True; user@{domain} -""".format( - domain=self.domain - ), + f""" +alias; alias3@{self.domain}; True; user@{self.domain} +""", name="aliases.csv", ) with self.assertRaises(exceptions.ValidationError): @@ -129,7 +117,7 @@ class UserLimitImportTestCase(LimitImportTestCase): """Create test data.""" localconfig = core_models.LocalConfig.objects.first() for name, _definition in utils.get_user_limit_templates(): - localconfig.parameters.set_value("deflt_user_{0}_limit".format(name), 2) + localconfig.parameters.set_value(f"deflt_user_{name}_limit", 2) localconfig.save() super(UserLimitImportTestCase, cls).setUpTestData() @@ -191,7 +179,7 @@ class DomainLimitImportTestCase(LimitImportTestCase): {"enable_domain_limits": True, "enable_admin_limits": False} ) for name, _definition in utils.get_domain_limit_templates(): - localconfig.parameters.set_value("deflt_domain_{0}_limit".format(name), 2) + localconfig.parameters.set_value(f"deflt_domain_{name}_limit", 2) localconfig.save() super(DomainLimitImportTestCase, cls).setUpTestData() mb = admin_factories.MailboxFactory( diff --git a/modoboa/limits/tests/test_user_limits.py b/modoboa/limits/tests/test_user_limits.py index 5fd184b97..a8ecb0f1e 100644 --- a/modoboa/limits/tests/test_user_limits.py +++ b/modoboa/limits/tests/test_user_limits.py @@ -54,7 +54,7 @@ class ResourceTestCase(lib_tests.ModoTestCase): {"enable_admin_limits": True, "enable_domain_limits": False} ) for name, _definition in utils.get_user_limit_templates(): - cls.localconfig.parameters.set_value("deflt_user_{0}_limit".format(name), 2) + cls.localconfig.parameters.set_value(f"deflt_user_{name}_limit", 2) cls.localconfig.save() populate_database() diff --git a/modoboa/maillog/api/v2/viewsets.py b/modoboa/maillog/api/v2/viewsets.py index 58a60546d..34b51999d 100644 --- a/modoboa/maillog/api/v2/viewsets.py +++ b/modoboa/maillog/api/v2/viewsets.py @@ -41,7 +41,7 @@ class StatisticsViewSet(GetThrottleViewsetMixin, viewsets.ViewSet): end = int(time.mktime(serializer.validated_data["end"].timetuple())) else: end = int(time.mktime(time.localtime())) - start = "-1{}".format(period) + start = f"-1{period}" graphs = graph_sets[gset].export( fname, start, end, serializer.validated_data.get("graphic") ) diff --git a/modoboa/maillog/factories.py b/modoboa/maillog/factories.py index 011740199..4b567b64f 100644 --- a/modoboa/maillog/factories.py +++ b/modoboa/maillog/factories.py @@ -15,10 +15,10 @@ class MaillogFactory(factory.django.DjangoModelFactory): class Meta: model = models.Maillog - queue_id = factory.Sequence(lambda n: "ID{}".format(n)) + queue_id = factory.Sequence(lambda n: f"ID{n}") date = timezone.now() - sender = factory.Sequence(lambda n: "sender{}@example.test".format(n)) - rcpt = factory.Sequence(lambda n: "rcpt{}@example2.test".format(n)) + sender = factory.Sequence(lambda n: f"sender{n}@example.test") + rcpt = factory.Sequence(lambda n: f"rcpt{n}@example2.test") size = 100 status = "sent" from_domain = factory.SubFactory(admin_factories.DomainFactory) diff --git a/modoboa/maillog/graphics.py b/modoboa/maillog/graphics.py index 0401e0575..51c272b01 100644 --- a/modoboa/maillog/graphics.py +++ b/modoboa/maillog/graphics.py @@ -102,9 +102,7 @@ class Graphic: cmdargs += curve.to_rrd_command_args(rrdfile) code = 0 - cmd = "{} xport --json -t --start {} --end {} ".format( - self.rrdtool_binary, str(start), str(end) - ) + cmd = f"{self.rrdtool_binary} xport --json -t --start {str(start)} --end {str(end)} " cmd += " ".join(cmdargs) code, output = exec_cmd(smart_bytes(cmd)) if code: @@ -122,7 +120,7 @@ class Graphic: return result -class GraphicSet(object): +class GraphicSet: """A set of graphics.""" domain_selector = False diff --git a/modoboa/maillog/lib.py b/modoboa/maillog/lib.py index c5cb5f90b..180dea71f 100644 --- a/modoboa/maillog/lib.py +++ b/modoboa/maillog/lib.py @@ -1,4 +1,3 @@ -# coding: utf-8 import sys import time diff --git a/modoboa/maillog/management/commands/logparser.py b/modoboa/maillog/management/commands/logparser.py index f686ea9b4..fe3e73fac 100644 --- a/modoboa/maillog/management/commands/logparser.py +++ b/modoboa/maillog/management/commands/logparser.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ Postfix log parser. @@ -230,7 +228,7 @@ class LogParser: def add_point_to_rrd(self, fname, tpl, values, ts=None): """Try to add a new point to RRD file.""" if ts: - values = "{}:{}".format(ts, values) + values = f"{ts}:{values}" if self.verbose: print("[rrd] VERBOSE update -t %s %s" % (tpl, values)) try: @@ -564,11 +562,11 @@ class LogParser: host, prog, subprog, pid, log = m.groups() try: - parser = getattr(self, "_parse_{}".format(prog)) + parser = getattr(self, f"_parse_{prog}") if not parser(log, host, pid, subprog): self._dprint("[parser] ignoring %r log: %r" % (prog, log)) except AttributeError: - self._dprint('[parser] no log handler for "{}": {}'.format(prog, log)) + self._dprint(f'[parser] no log handler for "{prog}": {log}') def process(self): """Process the log file. @@ -580,7 +578,7 @@ class LogParser: with open(self.logfile, encoding="utf-8", errors="ignore") as fp: for line in fp: self._parse_line(line) - except IOError as errno: + except OSError as errno: self._dprint("%s" % errno) sys.exit(1) diff --git a/modoboa/maillog/management/commands/update_statistics.py b/modoboa/maillog/management/commands/update_statistics.py index 031ddbc51..537ae6977 100644 --- a/modoboa/maillog/management/commands/update_statistics.py +++ b/modoboa/maillog/management/commands/update_statistics.py @@ -29,7 +29,7 @@ class Command(BaseCommand): """Create RRD file.""" step = 3600 ds_name = "new_accounts" - params = ["DS:{}:ABSOLUTE:{}:0:U".format(ds_name, step * 2)] + params = [f"DS:{ds_name}:ABSOLUTE:{step * 2}:0:U"] params += [ "RRA:AVERAGE:0.5:1:48", # 48 hours with a 1h granularity "RRA:AVERAGE:0.5:24:31", # 31 days with a 1d granularity diff --git a/modoboa/maillog/parser.py b/modoboa/maillog/parser.py index eb0fee1d0..3bacbd93b 100644 --- a/modoboa/maillog/parser.py +++ b/modoboa/maillog/parser.py @@ -1,6 +1,5 @@ """Parsing tools for maillog related files.""" -import io import re import time @@ -269,7 +268,7 @@ class MaillogParser: host, prog, subprog, pid, log = m.groups() try: - parser = getattr(self, "_parse_{}".format(prog)) + parser = getattr(self, f"_parse_{prog}") if not parser(log, host, pid, subprog): self._dprint("[parser] ignoring %r log: %r" % (prog, log)) except AttributeError: @@ -278,8 +277,8 @@ class MaillogParser: def parse(self, logfile): """Process the log file.""" try: - fp = io.open(logfile, encoding="utf-8") - except IOError as errno: + fp = open(logfile, encoding="utf-8") + except OSError as errno: self._dprint("%s" % errno) return diff --git a/modoboa/maillog/tests/test_views.py b/modoboa/maillog/tests/test_views.py index 4da688cc9..6d6fa1ef6 100644 --- a/modoboa/maillog/tests/test_views.py +++ b/modoboa/maillog/tests/test_views.py @@ -15,7 +15,7 @@ from modoboa.core import models as core_models from modoboa.lib.tests import ModoTestCase -class RunCommandsMixin(object): +class RunCommandsMixin: """Mixin to run management commands.""" def setUp(self): @@ -78,39 +78,37 @@ class ViewsTestCase(RunCommandsMixin, ModoTestCase): self.run_logparser() url = reverse("maillog:graph_list") self.ajax_get(url, status=404) - response = self.ajax_get("{}?gset=mailtraffic".format(url)) + response = self.ajax_get(f"{url}?gset=mailtraffic") self.assertIn("averagetraffic", response["graphs"]) for period in ["week", "month", "year"]: - response = self.ajax_get( - "{}?gset=mailtraffic&period={}".format(url, period) - ) + response = self.ajax_get(f"{url}?gset=mailtraffic&period={period}") self.assertIn("averagetraffic", response["graphs"]) self.assertEqual(response["period_name"], period) # custom period today = datetime.date.today() - start = "{} 11:00:00".format(today) - end = "{} 11:40:00".format(today) + start = f"{today} 11:00:00" + end = f"{today} 11:40:00" response = self.ajax_get( - "{}?gset=mailtraffic&period=custom&start={}&end={}".format(url, start, end) + f"{url}?gset=mailtraffic&period=custom&start={start}&end={end}" ) self.assertIn("averagetraffic", response["graphs"]) # unknown domain response = self.ajax_get( - "{}?gset=mailtraffic&searchquery=unknown.com".format(url), status=400 + f"{url}?gset=mailtraffic&searchquery=unknown.com", status=400 ) # check with greylist enabled self.set_global_parameter("greylist", True) - response = self.ajax_get("{}?gset=mailtraffic".format(url)) + response = self.ajax_get(f"{url}?gset=mailtraffic") self.assertIn("averagetraffic", response["graphs"]) def test_account_created_graph(self): """Check data.""" self.run_update_statistics(rebuild=True) url = reverse("maillog:graph_list") - response = self.ajax_get("{}?gset=accountgraphicset".format(url)) + response = self.ajax_get(f"{url}?gset=accountgraphicset") data = response["graphs"]["accountcreationgraphic"]["series"][0]["data"] self.assertEqual(data[-1]["y"], 5.0) @@ -122,10 +120,10 @@ class ViewsTestCase(RunCommandsMixin, ModoTestCase): response = self.ajax_get(url) self.assertIn("averagetraffic", response["graphs"]) - response = self.ajax_get("{}&searchquery=test.com".format(url)) + response = self.ajax_get(f"{url}&searchquery=test.com") self.assertIn("averagetraffic", response["graphs"]) - response = self.ajax_get("{}&searchquery=test2.com".format(url), status=403) + response = self.ajax_get(f"{url}&searchquery=test2.com", status=403) def test_get_domain_list(self): """Test get_domain_list view.""" @@ -148,7 +146,7 @@ class ManagementCommandsTestCase(RunCommandsMixin, ModoTestCase): """Test logparser command.""" self.run_logparser() for d in ["global", "test.com"]: - path = os.path.join(self.workdir, "{}.rrd".format(d)) + path = os.path.join(self.workdir, f"{d}.rrd") self.assertTrue(os.path.exists(path)) def test_logparser_with_greylist(self): @@ -156,7 +154,7 @@ class ManagementCommandsTestCase(RunCommandsMixin, ModoTestCase): self.set_global_parameter("greylist", True) self.run_logparser() for d in ["global", "test.com"]: - path = os.path.join(self.workdir, "{}.rrd".format(d)) + path = os.path.join(self.workdir, f"{d}.rrd") self.assertTrue(os.path.exists(path)) def test_update_statistics(self): diff --git a/modoboa/maillog/views.py b/modoboa/maillog/views.py index 15264d9b6..132b7b955 100644 --- a/modoboa/maillog/views.py +++ b/modoboa/maillog/views.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """Custom views.""" import re diff --git a/modoboa/parameters/tools.py b/modoboa/parameters/tools.py index 78054f6d4..bbf407d1a 100644 --- a/modoboa/parameters/tools.py +++ b/modoboa/parameters/tools.py @@ -20,8 +20,8 @@ class NotDefined(exceptions.ModoboaException): def __str__(self): if self.name is None: - return "Application {} not registered".format(self.app) - return "Parameter {} not defined for app {}".format(self.name, self.app) + return f"Application {self.app} not registered" + return f"Parameter {self.name} not defined for app {self.app}" class Registry: diff --git a/modoboa/pdfcredentials/app_settings.py b/modoboa/pdfcredentials/app_settings.py index e6f81a745..8f39db239 100644 --- a/modoboa/pdfcredentials/app_settings.py +++ b/modoboa/pdfcredentials/app_settings.py @@ -310,7 +310,7 @@ class ParametersForm(param_forms.AdminParametersForm): """Set initial values.""" super(ParametersForm, self).__init__(*args, **kwargs) if not self.fields["webpanel_url"].initial: - url = "https://{}{}".format(self.hostname, settings.LOGIN_URL) + url = f"https://{self.hostname}{settings.LOGIN_URL}" self.fields["webpanel_url"].initial = url if not self.fields["smtp_server_address"].initial: self.fields["smtp_server_address"].initial = self.hostname diff --git a/modoboa/pdfcredentials/lib.py b/modoboa/pdfcredentials/lib.py index 7ad849fb7..5dbd934b7 100644 --- a/modoboa/pdfcredentials/lib.py +++ b/modoboa/pdfcredentials/lib.py @@ -29,7 +29,7 @@ def init_storage_dir(): ) try: os.mkdir(storage_dir) - except (OSError, IOError) as inst: + except OSError as inst: raise InternalError( _("Failed to create the directory that will contain " "PDF documents (%s)") % inst @@ -123,5 +123,5 @@ def rfc_6266_content_disposition(filename): filename.replace("\\", "\\\\").replace('"', r"\"") ) except UnicodeEncodeError: - file_expr = "filename*=utf-8''{}".format(quote(filename)) + file_expr = f"filename*=utf-8''{quote(filename)}" return f"attachment; {file_expr}" diff --git a/modoboa/pdfcredentials/tests/test_lib.py b/modoboa/pdfcredentials/tests/test_lib.py index 37f4d95c0..dee8c8221 100644 --- a/modoboa/pdfcredentials/tests/test_lib.py +++ b/modoboa/pdfcredentials/tests/test_lib.py @@ -54,7 +54,7 @@ class PDFCredentialsLibTestCase(ModoAPITestCase): """Test that PDFs are decrypted.""" username = "toto1818@test.com" self._create_account(username) - fname = os.path.join(self.workdir, "{}.pdf".format(username)) + fname = os.path.join(self.workdir, f"{username}.pdf") self.assertTrue(os.path.exists(fname)) filebuff = lib.decrypt_file(fname) self.assertIn( @@ -64,7 +64,7 @@ class PDFCredentialsLibTestCase(ModoAPITestCase): .strip() .decode(), ) - with open(fname, "r") as file: + with open(fname) as file: with self.assertRaises(UnicodeDecodeError): Popen( "/usr/bin/file -b --mime -", shell=True, stdout=PIPE, stdin=PIPE diff --git a/modoboa/policyd/core.py b/modoboa/policyd/core.py index 96a085bf0..e914ae24f 100644 --- a/modoboa/policyd/core.py +++ b/modoboa/policyd/core.py @@ -135,7 +135,7 @@ async def decrement_limit(rclient, ltype, name): """Decrement the given limit by one.""" new_counter = await rclient.hincrby(constants.REDIS_HASHNAME, name, -1) if new_counter <= 0: - logger.info("Limit reached for {} {}".format(ltype, name)) + logger.info(f"Limit reached for {ltype} {name}") asyncio.ensure_future(notify_limit_reached(ltype, name)) @@ -152,13 +152,13 @@ async def apply_policies(attributes): localpart, domain = split_mailbox(sasl_username) if await rclient.hexists(constants.REDIS_HASHNAME, domain): counter = await rclient.hget(constants.REDIS_HASHNAME, domain) - logger.info("Domain {} current counter: {}".format(domain, counter)) + logger.info(f"Domain {domain} current counter: {counter}") if int(counter) <= 0: return FAILURE_ACTION decr_domain = True if await rclient.hexists(constants.REDIS_HASHNAME, sasl_username): counter = await rclient.hget(constants.REDIS_HASHNAME, sasl_username) - logger.info("Account {} current counter: {}".format(sasl_username, counter)) + logger.info(f"Account {sasl_username} current counter: {counter}") if int(counter) <= 0: return FAILURE_ACTION decr_user = True diff --git a/modoboa/policyd/management/commands/policy_daemon.py b/modoboa/policyd/management/commands/policy_daemon.py index e1abc2948..c8096c96e 100644 --- a/modoboa/policyd/management/commands/policy_daemon.py +++ b/modoboa/policyd/management/commands/policy_daemon.py @@ -45,7 +45,7 @@ class Command(BaseCommand): getattr(signal, signame), functools.partial(ask_exit, signame, loop) ) - logger.info("Serving on {}".format(server.sockets[0].getsockname())) + logger.info(f"Serving on {server.sockets[0].getsockname()}") if options["debug"]: loop.set_debug(True) diff --git a/modoboa/postfix_autoreply/__init__.py b/modoboa/postfix_autoreply/__init__.py index c1b6d5fa5..d62a6f91a 100644 --- a/modoboa/postfix_autoreply/__init__.py +++ b/modoboa/postfix_autoreply/__init__.py @@ -1,9 +1,5 @@ -# -*- coding: utf-8 -*- - """Away message editor for Modoboa (postfix compatible).""" -from __future__ import unicode_literals - from pkg_resources import DistributionNotFound, get_distribution try: diff --git a/modoboa/postfix_autoreply/api/v2/tests.py b/modoboa/postfix_autoreply/api/v2/tests.py index 782934838..7a913a600 100644 --- a/modoboa/postfix_autoreply/api/v2/tests.py +++ b/modoboa/postfix_autoreply/api/v2/tests.py @@ -27,17 +27,13 @@ class ARMessageViewSetTestCase(ModoAPITestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 2) # Test filters - response = self.client.get( - url + "?mbox={}".format(self.account.mailbox.full_address) - ) + response = self.client.get(url + f"?mbox={self.account.mailbox.full_address}") self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 1) - response = self.client.get( - url + "?mbox={}".format(self.account.mailbox.address) - ) + response = self.client.get(url + f"?mbox={self.account.mailbox.address}") self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 2) - response = self.client.get(url + "?mbox__user={}".format(self.account.pk)) + response = self.client.get(url + f"?mbox__user={self.account.pk}") self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 1) # Test retrieve diff --git a/modoboa/postfix_autoreply/factories.py b/modoboa/postfix_autoreply/factories.py index 70c2f88f6..d58471d56 100644 --- a/modoboa/postfix_autoreply/factories.py +++ b/modoboa/postfix_autoreply/factories.py @@ -1,9 +1,5 @@ -# -*- coding: utf-8 -*- - """Postfix auto reply factories.""" -from __future__ import unicode_literals - import factory from modoboa.admin import factories as admin_factories diff --git a/modoboa/postfix_autoreply/forms.py b/modoboa/postfix_autoreply/forms.py index f36641a45..a01370a1e 100644 --- a/modoboa/postfix_autoreply/forms.py +++ b/modoboa/postfix_autoreply/forms.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """Custom forms.""" from collections import OrderedDict diff --git a/modoboa/postfix_autoreply/handlers.py b/modoboa/postfix_autoreply/handlers.py index 694a78448..83629ab49 100644 --- a/modoboa/postfix_autoreply/handlers.py +++ b/modoboa/postfix_autoreply/handlers.py @@ -17,14 +17,14 @@ def manage_transport_entry(sender, instance, **kwargs): """Create or update a transport entry for this domain.""" if kwargs.get("created"): tr_models.Transport.objects.get_or_create( - pattern="autoreply.{}".format(instance), service="autoreply" + pattern=f"autoreply.{instance}", service="autoreply" ) return oldname = getattr(instance, "oldname", "None") if oldname is None or oldname == instance.name: return - tr_models.Transport.objects.filter(pattern="autoreply.{}".format(oldname)).update( - pattern="autoreply.{}".format(instance.name) + tr_models.Transport.objects.filter(pattern=f"autoreply.{oldname}").update( + pattern=f"autoreply.{instance.name}" ) qset = admin_models.AliasRecipient.objects.select_related( "alias", "r_mailbox" @@ -39,7 +39,7 @@ def manage_transport_entry(sender, instance, **kwargs): @receiver(signals.post_delete, sender=admin_models.Domain) def delete_transport_entry(sender, instance, **kwargs): """Delete a transport entry.""" - tr_models.Transport.objects.filter(pattern="autoreply.{}".format(instance)).delete() + tr_models.Transport.objects.filter(pattern=f"autoreply.{instance}").delete() @receiver(signals.post_save, sender=admin_models.Mailbox) @@ -49,24 +49,22 @@ def rename_autoreply_alias(sender, instance, **kwargs): if old_address is None or old_address == instance.full_address: return admin_models.AliasRecipient.objects.filter( - address__contains="{}@autoreply".format(old_address) - ).update(address="{}@autoreply.{}".format(instance.full_address, instance.domain)) + address__contains=f"{old_address}@autoreply" + ).update(address=f"{instance.full_address}@autoreply.{instance.domain}") @receiver(signals.post_delete, sender=admin_models.Mailbox) def delete_autoreply_alias(sender, instance, **kwargs): """Delete alias.""" admin_models.AliasRecipient.objects.filter( - address="{}@autoreply.{}".format(instance.full_address, instance.domain) + address=f"{instance.full_address}@autoreply.{instance.domain}" ).delete() @receiver(signals.post_save, sender=models.ARmessage) def manage_autoreply_alias(sender, instance, **kwargs): """Create or delete the alias.""" - ar_alias_address = "{}@autoreply.{}".format( - instance.mbox.full_address, instance.mbox.domain - ) + ar_alias_address = f"{instance.mbox.full_address}@autoreply.{instance.mbox.domain}" admin_models.Alias.objects.get( address=instance.mbox.full_address, domain=instance.mbox.domain, internal=True ) diff --git a/modoboa/postfix_autoreply/known_problems.py b/modoboa/postfix_autoreply/known_problems.py index bcb354c55..843f99621 100644 --- a/modoboa/postfix_autoreply/known_problems.py +++ b/modoboa/postfix_autoreply/known_problems.py @@ -1,9 +1,5 @@ -# -*- coding: utf-8 -*- - """Know problems and associated fixes.""" -from __future__ import unicode_literals - from modoboa.admin import models as admin_models from modoboa.admin.management.commands.subcommands import _repair from . import models @@ -22,12 +18,12 @@ def ensure_autoreplies_recipents_are_valids(**options): mbox__address=address, mbox__domain__name=domain ) if not arqs.exists(): - _repair.log("Delete {0} (AR does not exist)".format(alr), **options) + _repair.log(f"Delete {alr} (AR does not exist)", **options) alr.delete() deleted += 1 elif arqs.filter(enabled=False).exists(): - _repair.log("Delete {0} (AR is disabled)".format(alr), **options) + _repair.log(f"Delete {alr} (AR is disabled)", **options) alr.delete() deleted += 1 if deleted: - _repair.log("{0} alias recipient(s) deleted".format(deleted), **options) + _repair.log(f"{deleted} alias recipient(s) deleted", **options) diff --git a/modoboa/postfix_autoreply/management/commands/autoreply.py b/modoboa/postfix_autoreply/management/commands/autoreply.py index 7dbcb02c5..43715f083 100644 --- a/modoboa/postfix_autoreply/management/commands/autoreply.py +++ b/modoboa/postfix_autoreply/management/commands/autoreply.py @@ -5,7 +5,6 @@ import fileinput from io import StringIO import logging import smtplib -import socket import sys from logging.handlers import SysLogHandler @@ -91,7 +90,7 @@ def send_autoreply(sender, mailbox, armessage, original_msg): context.update({"untildate": localize(armessage.untildate)}) content = armessage.content % context msg = EmailMessage( - "Auto: {} Re: {}".format(armessage.subject, subject), + f"Auto: {armessage.subject} Re: {subject}", smart_str(content), mailbox.user.encoded_address, [sender], @@ -126,7 +125,7 @@ class Command(BaseCommand): def handle(self, *args, **options): try: handler = SysLogHandler(address=options["syslog_socket_path"]) - except socket.error as ex: + except OSError as ex: if ex.errno == 2: # try the default, localhost:514 handler = SysLogHandler() @@ -193,7 +192,7 @@ class Command(BaseCommand): try: mbox = Mailbox.objects.get(address=address, domain__name=domain) except Mailbox.DoesNotExist: - msg = "Unknown recipient %s" % (fulladdress) + msg = f"Unknown recipient {fulladdress}" logger.debug("autoreply %s", msg) continue try: diff --git a/modoboa/postfix_autoreply/models.py b/modoboa/postfix_autoreply/models.py index 02602124e..8feb0c875 100644 --- a/modoboa/postfix_autoreply/models.py +++ b/modoboa/postfix_autoreply/models.py @@ -29,7 +29,7 @@ class ARmessage(models.Model): db_table = "postfix_autoreply_armessage" def __str__(self): - return "AR<{}>: {}".format(self.mbox, self.enabled) + return f"AR<{self.mbox}>: {self.enabled}" class ARhistoric(models.Model): diff --git a/modoboa/postfix_autoreply/tests.py b/modoboa/postfix_autoreply/tests.py index c92e74887..64f1cea74 100644 --- a/modoboa/postfix_autoreply/tests.py +++ b/modoboa/postfix_autoreply/tests.py @@ -322,7 +322,7 @@ class RepairTestCase(ModoTestCase): mbox = admin_models.Mailbox.objects.get(user__username="user@test.com") alias = admin_models.Alias.objects.get(internal=True, address=mbox.full_address) arm = factories.ARmessageFactory(mbox=mbox, enabled=False) - ar_address = "{}@autoreply.{}".format(mbox.full_address, mbox.domain.name) + ar_address = f"{mbox.full_address}@autoreply.{mbox.domain.name}" admin_factories.AliasRecipientFactory(alias=alias, address=ar_address) management.call_command("modo", "repair", "--quiet") self.assertFalse( diff --git a/modoboa/postfix_autoreply/views.py b/modoboa/postfix_autoreply/views.py index 58ce8ee07..8495fea3c 100644 --- a/modoboa/postfix_autoreply/views.py +++ b/modoboa/postfix_autoreply/views.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from django.contrib.auth.decorators import login_required from django.template.loader import render_to_string from django.utils.translation import gettext as _ diff --git a/modoboa/relaydomains/api/v1/serializers.py b/modoboa/relaydomains/api/v1/serializers.py index 4461b45d2..aa9028228 100644 --- a/modoboa/relaydomains/api/v1/serializers.py +++ b/modoboa/relaydomains/api/v1/serializers.py @@ -26,11 +26,7 @@ class TransportSerializer(serializers.ModelSerializer): errors = self.backend.clean_fields(data["_settings"]) if errors: raise serializers.ValidationError( - { - "_settings": ",".join( - ["{}: {}".format(error[0], error[1]) for error in errors] - ) - } + {"_settings": ",".join([f"{error[0]}: {error[1]}" for error in errors])} ) return data diff --git a/modoboa/relaydomains/api/v1/tests.py b/modoboa/relaydomains/api/v1/tests.py index 7f3a1944b..e16dc59f2 100644 --- a/modoboa/relaydomains/api/v1/tests.py +++ b/modoboa/relaydomains/api/v1/tests.py @@ -9,7 +9,7 @@ from modoboa.lib.tests import ModoAPITestCase from modoboa.transport import factories as tr_factories, models as tr_models -class DataMixin(object): +class DataMixin: """A mixin to provide test data.""" @classmethod diff --git a/modoboa/relaydomains/forms.py b/modoboa/relaydomains/forms.py index 34f00c1e4..565da74f5 100644 --- a/modoboa/relaydomains/forms.py +++ b/modoboa/relaydomains/forms.py @@ -6,7 +6,7 @@ from modoboa.lib.form_utils import WizardStep from modoboa.transport import forms as tr_forms, models as tr_models -class DisableSignals(object): +class DisableSignals: """Context manager to disable signals.""" def __init__(self): diff --git a/modoboa/relaydomains/postfix_maps.py b/modoboa/relaydomains/postfix_maps.py index 00b4e49c3..3d937c36c 100644 --- a/modoboa/relaydomains/postfix_maps.py +++ b/modoboa/relaydomains/postfix_maps.py @@ -1,7 +1,7 @@ """Map file definitions for postfix.""" -class RelayDomainsMap(object): +class RelayDomainsMap: """Map file to list all relay domains.""" filename = "sql-relaydomains.cf" @@ -19,7 +19,7 @@ class RelayDomainsMap(object): ) -class SplitedDomainsTransportMap(object): +class SplitedDomainsTransportMap: """A transport map for splited domains. (ie. ones with both local and remote mailboxes) @@ -55,7 +55,7 @@ class SplitedDomainsTransportMap(object): ) -class RelayRecipientVerification(object): +class RelayRecipientVerification: """A map file to enable recipient verification.""" filename = "sql-relay-recipient-verification.cf" diff --git a/modoboa/relaydomains/tests.py b/modoboa/relaydomains/tests.py index 3e2688c54..7323a837f 100644 --- a/modoboa/relaydomains/tests.py +++ b/modoboa/relaydomains/tests.py @@ -13,7 +13,7 @@ from modoboa.transport import factories as tr_factories, models as tr_models from . import models -class Operations(object): +class Operations: def _create_relay_domain(self, name, status=200, **kwargs): values = { @@ -280,7 +280,7 @@ class LimitsTestCase(ModoTestCase, Operations): super(LimitsTestCase, cls).setUpTestData() for name, _definition in limits_utils.get_user_limit_templates(): cls.localconfig.parameters.set_value( - "deflt_user_{0}_limit".format(name), 2, app="limits" + f"deflt_user_{name}_limit", 2, app="limits" ) cls.localconfig.save() cls.user = UserFactory.create(username="reseller", groups=("Resellers",)) diff --git a/modoboa/sievefilters/__init__.py b/modoboa/sievefilters/__init__.py index 15612f649..a48e9b877 100644 --- a/modoboa/sievefilters/__init__.py +++ b/modoboa/sievefilters/__init__.py @@ -1,9 +1,5 @@ -# -*- coding: utf-8 -*- - """The sieve filters editor of Modoboa.""" -from __future__ import unicode_literals - from pkg_resources import get_distribution, DistributionNotFound diff --git a/modoboa/sievefilters/imaputils.py b/modoboa/sievefilters/imaputils.py index 0c60369bf..215daf79d 100644 --- a/modoboa/sievefilters/imaputils.py +++ b/modoboa/sievefilters/imaputils.py @@ -4,7 +4,6 @@ import base64 import imaplib import re from operator import itemgetter -import socket import ssl from django.utils.translation import gettext as _ @@ -103,8 +102,8 @@ class IMAPconnector: self.m = imaplib.IMAP4_SSL(self.address, self.port) else: self.m = imaplib.IMAP4(self.address, self.port) - except (socket.error, imaplib.IMAP4.error, ssl.SSLError) as error: - raise ImapError(_("Connection to IMAP server failed: %s" % error)) from None + except (OSError, imaplib.IMAP4.error, ssl.SSLError) as error: + raise ImapError(_(f"Connection to IMAP server failed: {error}")) from None token = ( b"n,a=" @@ -139,9 +138,7 @@ class IMAPconnector: def _listmboxes(self, topmailbox, mailboxes): """Retrieve mailboxes list.""" pattern = ( - '"{0}{1}%"'.format( - topmailbox.encode("imap4-utf-7").decode(), self.hdelimiter - ) + f'"{topmailbox.encode("imap4-utf-7").decode()}{self.hdelimiter}%"' if topmailbox else "%" ) diff --git a/modoboa/sievefilters/mocks.py b/modoboa/sievefilters/mocks.py index baad7283f..c60533798 100644 --- a/modoboa/sievefilters/mocks.py +++ b/modoboa/sievefilters/mocks.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """Mock objects.""" SAMPLE_SIEVE_SCRIPT = """require ["fileinto", "copy"]; diff --git a/modoboa/transport/backends.py b/modoboa/transport/backends.py index 85d5fe8bb..baeb65b2c 100644 --- a/modoboa/transport/backends.py +++ b/modoboa/transport/backends.py @@ -7,7 +7,7 @@ from django.utils.translation import gettext_lazy as _ from modoboa.lib import validators as lib_validators -class TransportBackend(object): +class TransportBackend: """Base backend class.""" name = None @@ -42,7 +42,7 @@ class TransportBackend(object): ftype = setting.get("type", "string") if ftype == "string": validator = setting.get("validator") - vfunc = "_validate_{}".format(validator) + vfunc = f"_validate_{validator}" if validator and hasattr(self, vfunc): if getattr(self, vfunc)(value): continue @@ -60,7 +60,7 @@ class TransportBackend(object): pass -class TransportBackendManager(object): +class TransportBackendManager: """Transport backends manager.""" def __init__(self): diff --git a/modoboa/transport/factories.py b/modoboa/transport/factories.py index 37ecc8ffe..eeda75f84 100644 --- a/modoboa/transport/factories.py +++ b/modoboa/transport/factories.py @@ -12,6 +12,6 @@ class TransportFactory(factory.django.DjangoModelFactory): model = models.Transport django_get_or_create = ("pattern",) - pattern = factory.Sequence(lambda n: "transport{}".format(n)) + pattern = factory.Sequence(lambda n: f"transport{n}") service = "relay" next_hop = "[external.host.tld]:25" diff --git a/modoboa/transport/forms.py b/modoboa/transport/forms.py index cfe61ba28..9b0266158 100644 --- a/modoboa/transport/forms.py +++ b/modoboa/transport/forms.py @@ -11,12 +11,12 @@ TYPE_TO_FIELD_MAP = { } -class BackendSettingsMixin(object): +class BackendSettingsMixin: """A mixin to deal with backend settings in a model form.""" def __init__(self, *args, **kwargs): """Constructor.""" - super(BackendSettingsMixin, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.setting_field_names = [] def inject_backend_settings(self, name, settings): @@ -44,7 +44,7 @@ class BackendSettingsMixin(object): def save(self, commit=True): """Set settings to JSON field.""" - transport = super(BackendSettingsMixin, self).save(commit=False) + transport = super().save(commit=False) transport._settings = { name: self.cleaned_data[name] for name in self.setting_field_names } @@ -64,7 +64,7 @@ class TransportForm(BackendSettingsMixin, forms.ModelForm): def __init__(self, *args, **kwargs): """Set backend list.""" - super(TransportForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields["service"].choices = backends.manager.get_backend_list() settings = backends.manager.get_all_backend_settings() for name, backend_settings in settings.items(): @@ -79,18 +79,18 @@ class TransportForm(BackendSettingsMixin, forms.ModelForm): backend_name = self.data.get("service") backend_settings = backends.manager.get_backend_settings(backend_name) for name, field in self.fields.items(): - if name.startswith("{}_".format(backend_name)): - name = name.replace("{}_".format(backend_name), "") + if name.startswith(f"{backend_name}_"): + name = name.replace(f"{backend_name}_", "") for setting in backend_settings: if setting["name"] == name: break if setting.get("required", True): field.required = True - return super(TransportForm, self)._clean_fields() + return super()._clean_fields() def clean(self): """Check values.""" - cleaned_data = super(TransportForm, self).clean() + cleaned_data = super().clean() if self.errors: return cleaned_data self.clean_backend_fields(cleaned_data["service"]) diff --git a/modoboa/transport/postfix_maps.py b/modoboa/transport/postfix_maps.py index 35cb3caa8..66e2e3c3e 100644 --- a/modoboa/transport/postfix_maps.py +++ b/modoboa/transport/postfix_maps.py @@ -1,7 +1,7 @@ """Map file definitions for postfix.""" -class TransportMap(object): +class TransportMap: """A transport map.""" filename = "sql-transport.cf" diff --git a/pylintrc b/pylintrc deleted file mode 100644 index 84b99bf45..000000000 --- a/pylintrc +++ /dev/null @@ -1,639 +0,0 @@ -[MAIN] - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Clear in-memory caches upon conclusion of linting. Useful if running pylint -# in a server-like mode. -clear-cache-post-run=no - -# Load and enable all available extensions. Use --list-extensions to see a list -# all available extensions. -#enable-all-extensions= - -# In error mode, messages with a category besides ERROR or FATAL are -# suppressed, and no reports are done by default. Error mode is compatible with -# disabling specific errors. -#errors-only= - -# Always return a 0 (non-error) status code, even if lint errors are found. -# This is primarily useful in continuous integration scripts. -#exit-zero= - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code. -extension-pkg-allow-list= - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code. (This is an alternative name to extension-pkg-allow-list -# for backward compatibility.) -extension-pkg-whitelist= - -# Return non-zero exit code if any of these messages/categories are detected, -# even if score is above --fail-under value. Syntax same as enable. Messages -# specified are enabled, while categories only check already-enabled messages. -fail-on= - -# Specify a score threshold under which the program will exit with error. -fail-under=10 - -# Interpret the stdin as a python script, whose filename needs to be passed as -# the module_or_package argument. -#from-stdin= - -# Files or directories to be skipped. They should be base names, not paths. -ignore=CVS,migrations - -# Add files or directories matching the regular expressions patterns to the -# ignore-list. The regex matches against paths and can be in Posix or Windows -# format. Because '\\' represents the directory delimiter on Windows systems, -# it can't be used as an escape character. -ignore-paths= - -# Files or directories matching the regular expression patterns are skipped. -# The regex matches against base names, not paths. The default value ignores -# Emacs file locks -ignore-patterns=^\.# - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis). It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the -# number of processors available to use, and will cap the count on Windows to -# avoid hangs. -jobs=1 - -# Control the amount of potential inferred values when inferring a single -# object. This can help the performance when dealing with large functions or -# complex, nested conditions. -limit-inference-results=100 - -# List of plugins (as comma separated values of python module names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# Minimum Python version to use for version dependent checks. Will default to -# the version used to run pylint. -py-version=3.11 - -# Discover python modules and packages in the file system subtree. -recursive=no - -# Add paths to the list of the source roots. Supports globbing patterns. The -# source root is an absolute path or a path relative to the current working -# directory used to determine a package namespace for modules located under the -# source root. -source-roots= - -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - -# In verbose mode, extra non-checker-related info will be displayed. -#verbose= - - -[BASIC] - -# Naming style matching correct argument names. -argument-naming-style=snake_case - -# Regular expression matching correct argument names. Overrides argument- -# naming-style. If left empty, argument names will be checked with the set -# naming style. -#argument-rgx= - -# Naming style matching correct attribute names. -attr-naming-style=snake_case - -# Regular expression matching correct attribute names. Overrides attr-naming- -# style. If left empty, attribute names will be checked with the set naming -# style. -#attr-rgx= - -# Bad variable names which should always be refused, separated by a comma. -bad-names=foo, - bar, - baz, - toto, - tutu, - tata - -# Bad variable names regexes, separated by a comma. If names match any regex, -# they will always be refused -bad-names-rgxs= - -# Naming style matching correct class attribute names. -class-attribute-naming-style=any - -# Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style. If left empty, class attribute names will be checked -# with the set naming style. -#class-attribute-rgx= - -# Naming style matching correct class constant names. -class-const-naming-style=UPPER_CASE - -# Regular expression matching correct class constant names. Overrides class- -# const-naming-style. If left empty, class constant names will be checked with -# the set naming style. -#class-const-rgx= - -# Naming style matching correct class names. -class-naming-style=PascalCase - -# Regular expression matching correct class names. Overrides class-naming- -# style. If left empty, class names will be checked with the set naming style. -#class-rgx= - -# Naming style matching correct constant names. -const-naming-style=UPPER_CASE - -# Regular expression matching correct constant names. Overrides const-naming- -# style. If left empty, constant names will be checked with the set naming -# style. -#const-rgx= - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming style matching correct function names. -function-naming-style=snake_case - -# Regular expression matching correct function names. Overrides function- -# naming-style. If left empty, function names will be checked with the set -# naming style. -#function-rgx= - -# Good variable names which should always be accepted, separated by a comma. -good-names=i, - j, - k, - ex, - Run, - _ - -# Good variable names regexes, separated by a comma. If names match any regex, -# they will always be accepted -good-names-rgxs= - -# Include a hint for the correct naming format with invalid-name. -include-naming-hint=no - -# Naming style matching correct inline iteration names. -inlinevar-naming-style=any - -# Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style. If left empty, inline iteration names will be checked -# with the set naming style. -#inlinevar-rgx= - -# Naming style matching correct method names. -method-naming-style=snake_case - -# Regular expression matching correct method names. Overrides method-naming- -# style. If left empty, method names will be checked with the set naming style. -#method-rgx= - -# Naming style matching correct module names. -module-naming-style=snake_case - -# Regular expression matching correct module names. Overrides module-naming- -# style. If left empty, module names will be checked with the set naming style. -#module-rgx= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -# These decorators are taken in consideration only for invalid-name. -property-classes=abc.abstractproperty - -# Regular expression matching correct type alias names. If left empty, type -# alias names will be checked with the set naming style. -#typealias-rgx= - -# Regular expression matching correct type variable names. If left empty, type -# variable names will be checked with the set naming style. -#typevar-rgx= - -# Naming style matching correct variable names. -variable-naming-style=snake_case - -# Regular expression matching correct variable names. Overrides variable- -# naming-style. If left empty, variable names will be checked with the set -# naming style. -#variable-rgx= - - -[CLASSES] - -# Warn about protected attribute access inside special methods -check-protected-access-in-special-methods=no - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp, - asyncSetUp, - __post_init__ - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[DESIGN] - -# List of regular expressions of class ancestor names to ignore when counting -# public methods (see R0903) -exclude-too-few-public-methods= - -# List of qualified class names to ignore when counting class parents (see -# R0901) -ignored-parents= - -# Maximum number of arguments for function / method. -max-args=5 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Maximum number of boolean expressions in an if statement (see R0916). -max-bool-expr=5 - -# Maximum number of branch for function / method body. -max-branches=12 - -# Maximum number of locals for function / method body. -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body. -max-returns=6 - -# Maximum number of statements in function / method body. -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when caught. -overgeneral-exceptions=builtins.BaseException,builtins.Exception - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module. -max-module-lines=1000 - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[IMPORTS] - -# List of modules that can be imported at any level, not just the top level -# one. -allow-any-import-level= - -# Allow explicit reexports by alias from a package __init__. -allow-reexport-from-package=no - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Deprecated modules which should not be used, separated by a comma. -deprecated-modules= - -# Output a graph (.gv or any supported image format) of external dependencies -# to the given file (report RP0402 must not be disabled). -ext-import-graph= - -# Output a graph (.gv or any supported image format) of all (i.e. internal and -# external) dependencies to the given file (report RP0402 must not be -# disabled). -import-graph= - -# Output a graph (.gv or any supported image format) of internal dependencies -# to the given file (report RP0402 must not be disabled). -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - -# Couples of modules and preferred modules, separated by a comma. -preferred-modules= - - -[LOGGING] - -# The type of string formatting that logging methods do. `old` means using % -# formatting, `new` is for `{}` formatting. -logging-format-style=old - -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules=logging - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, -# UNDEFINED. -confidence=HIGH, - CONTROL_FLOW, - INFERENCE, - INFERENCE_FAILURE, - UNDEFINED - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once). You can also use "--disable=all" to -# disable everything first and then re-enable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use "--disable=all --enable=classes -# --disable=W". -disable=raw-checker-failed, - bad-inline-option, - locally-disabled, - file-ignored, - suppressed-message, - useless-suppression, - deprecated-pragma, - use-symbolic-message-instead, - no-member, - locally-disabled, - too-few-public-methods, - too-many-public-methods, - too-many-ancestors, - line-too-long, - missing-class-docstring, - missing-function-docstring - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable=c-extension-no-member - - -[METHOD_ARGS] - -# List of qualified names (i.e., library.method) which require a timeout -# parameter e.g. 'requests.api.get,requests.api.post' -timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - -# Regular expression of note tags to take in consideration. -notes-rgx= - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - -# Complete name of functions that never returns. When checking for -# inconsistent-return-statements if a never returning function is called then -# it will be considered as an explicit return statement and no message will be -# printed. -never-returning-functions=sys.exit,argparse.parse_error - - -[REPORTS] - -# Python expression which should return a score less than or equal to 10. You -# have access to the variables 'fatal', 'error', 'warning', 'refactor', -# 'convention', and 'info' which contain the number of messages in each -# category, as well as 'statement' which is the total number of statements -# analyzed. This score is used by the global evaluation report (RP0004). -evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details. -msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio). You can also give a reporter class, e.g. -# mypackage.mymodule.MyReporterClass. -#output-format= - -# Tells whether to display a full report or only the messages. -reports=no - -# Activate the evaluation score. -score=yes - - -[SIMILARITIES] - -# Comments are removed from the similarity computation -ignore-comments=yes - -# Docstrings are removed from the similarity computation -ignore-docstrings=yes - -# Imports are removed from the similarity computation -ignore-imports=yes - -# Signatures are removed from the similarity computation -ignore-signatures=yes - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions=4 - -# Spelling dictionary name. No available dictionaries : You need to install -# both the python package and the system dependency for enchant to work.. -spelling-dict= - -# List of comma separated words that should be considered directives if they -# appear at the beginning of a comment and should not be checked. -spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains the private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to the private dictionary (see the -# --spelling-private-dict-file option) instead of raising a message. -spelling-store-unknown-words=no - - -[STRING] - -# This flag controls whether inconsistent-quotes generates a warning when the -# character used as a quote delimiter is used inconsistently within a module. -check-quote-consistency=no - -# This flag controls whether the implicit-str-concat should generate a warning -# on implicit string concatenation in sequences defined over several lines. -check-str-concat-over-line-jumps=no - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether to warn about missing members when the owner of the attribute -# is inferred to be None. -ignore-none=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of symbolic message names to ignore for Mixin members. -ignored-checks-for-mixins=no-member, - not-async-context-manager, - not-context-manager, - attribute-defined-outside-init - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - -# Regex pattern to define which classes are considered mixins. -mixin-class-rgx=.*[Mm]ixin - -# List of decorators that change the signature of a decorated function. -signature-mutators= - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid defining new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of names allowed to shadow builtins -allowed-redefined-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_, - _cb - -# A regular expression matching the name of dummy variables (i.e. expected to -# not be used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io diff --git a/pyproject.toml b/pyproject.toml index 0c3347bd9..142caf645 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,4 +120,4 @@ version = {attr = "modoboa.version.get_version"} exclude = ["migrations"] [tool.ruff.lint] -select = ["E4", "E7", "E9", "F", "B", "Q"] +select = ["E4", "E7", "E9", "F", "Q"]