diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 98931c989..a2d036117 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,12 +3,12 @@
repos:
- repo: local
hooks:
- - id: pylint
- name: pylint
- entry: pylint
+ - id: ruff
+ name: ruff
+ entry: ruff
language: system
types: [python]
- args: ["-rn", "-sn", "--fail-under=5"]
+ args: ["check"]
- id: prettier
name: prettier
entry: prettier --config frontend/.prettierrc.json --write --ignore-unknown
diff --git a/modoboa/admin/api/v1/serializers.py b/modoboa/admin/api/v1/serializers.py
index c2cd1b7f4..bd840b952 100644
--- a/modoboa/admin/api/v1/serializers.py
+++ b/modoboa/admin/api/v1/serializers.py
@@ -2,7 +2,6 @@
from typing import List
-from django.conf import settings
from django.contrib.auth import password_validation
from django.core.exceptions import ValidationError
from django.shortcuts import get_object_or_404
@@ -179,7 +178,7 @@ class DomainAliasSerializer(serializers.ModelSerializer):
object_type="domain_aliases",
)
except lib_exceptions.ModoboaException as inst:
- raise serializers.ValidationError({"domain": force_str(inst)})
+ raise serializers.ValidationError({"domain": force_str(inst)}) from None
domain_alias.save(creator=creator)
return domain_alias
@@ -312,7 +311,7 @@ class AccountPasswordSerializer(serializers.ModelSerializer):
try:
password_validation.validate_password(value, self.instance)
except ValidationError as exc:
- raise serializers.ValidationError(exc.messages[0])
+ raise serializers.ValidationError(exc.messages[0]) from None
return value
def update(self, instance, validated_data):
@@ -364,7 +363,7 @@ class WritableAccountSerializer(AccountSerializer):
try:
validators.UTF8EmailValidator()(username)
except ValidationError as err:
- raise ValidationError({"username": err.message})
+ raise ValidationError({"username": err.message}) from None
mailbox = data.get("mailbox")
if mailbox is None:
if not self.instance:
@@ -397,7 +396,9 @@ class WritableAccountSerializer(AccountSerializer):
object_type="mailboxes",
)
except lib_exceptions.ModoboaException as inst:
- raise serializers.ValidationError({"mailbox": force_str(inst)})
+ raise serializers.ValidationError(
+ {"mailbox": force_str(inst)}
+ ) from None
condition = not data.get("random_password") and (
data.get("password") or not self.partial
)
@@ -409,7 +410,9 @@ class WritableAccountSerializer(AccountSerializer):
data["password"], self.instance
)
except ValidationError as exc:
- raise serializers.ValidationError({"password": exc.messages[0]})
+ raise serializers.ValidationError(
+ {"password": exc.messages[0]}
+ ) from None
elif not self.instance:
raise serializers.ValidationError(
{"password": _("This field is required.")}
@@ -524,9 +527,9 @@ class AliasSerializer(serializers.ModelSerializer):
value, self.context["request"].user, instance=self.instance
)
except ValidationError as err:
- raise serializers.ValidationError(err)
+ raise serializers.ValidationError(err) from None
except AliasExists:
- raise serializers.ValidationError(_("This alias already exists"))
+ raise serializers.ValidationError(_("This alias already exists")) from None
return value.lower()
def create(self, validated_data):
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/v1/viewsets.py b/modoboa/admin/api/v1/viewsets.py
index f6ed5804f..c97a2512c 100644
--- a/modoboa/admin/api/v1/viewsets.py
+++ b/modoboa/admin/api/v1/viewsets.py
@@ -135,7 +135,7 @@ class AccountViewSet(
try:
user = core_models.User.objects.get(pk=pk)
except core_models.User.DoesNotExist:
- raise http.Http404
+ raise http.Http404 from None
serializer = self.get_serializer(user, data=request.data)
if serializer.is_valid():
serializer.save()
diff --git a/modoboa/admin/api/v2/serializers.py b/modoboa/admin/api/v2/serializers.py
index b64860118..c38f4ab77 100644
--- a/modoboa/admin/api/v2/serializers.py
+++ b/modoboa/admin/api/v2/serializers.py
@@ -94,7 +94,7 @@ class DomainSerializer(v1_serializers.DomainSerializer):
return domain
# 1. Create a domain administrator
- username = "%s@%s" % (domain_admin["username"], domain.name)
+ username = "{}@{}".format(domain_admin["username"], domain.name)
try:
da = core_models.User.objects.get(username=username)
except core_models.User.DoesNotExist:
@@ -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
)
@@ -240,13 +240,11 @@ class AdminGlobalParametersSerializer(serializers.Serializer):
if not value:
return value
try:
- ip_addresses = [
- ipaddress.ip_network(v.strip()) for v in value.split() if v.strip()
- ]
+ [ipaddress.ip_network(v.strip()) for v in value.split() if v.strip()]
except ValueError:
raise serializers.ValidationError(
_("This field only allows valid IP addresses (or networks)")
- )
+ ) from None
return value
def validate_auto_create_domain_and_mailbox(self, value):
@@ -366,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):
@@ -483,7 +479,7 @@ class WritableAccountSerializer(v1_serializers.WritableAccountSerializer):
try:
validators.UTF8EmailValidator()(username)
except ValidationError as err:
- raise ValidationError({"username": err.message})
+ raise ValidationError({"username": err.message}) from None
mailbox = data.get("mailbox")
if mailbox is None:
if not self.instance:
@@ -505,7 +501,7 @@ class WritableAccountSerializer(v1_serializers.WritableAccountSerializer):
object_type="mailboxes",
)
except lib_exceptions.ModoboaException as inst:
- raise serializers.ValidationError({"mailbox": str(inst)})
+ raise serializers.ValidationError({"mailbox": str(inst)}) from None
if len(self.address) > 64:
raise serializers.ValidationError(
{
@@ -522,7 +518,9 @@ class WritableAccountSerializer(v1_serializers.WritableAccountSerializer):
data["password"], self.instance
)
except ValidationError as exc:
- raise serializers.ValidationError({"password": exc.messages[0]})
+ raise serializers.ValidationError(
+ {"password": exc.messages[0]}
+ ) from None
elif not self.instance:
raise serializers.ValidationError(
{"password": _("This field is required.")}
diff --git a/modoboa/admin/app_settings.py b/modoboa/admin/app_settings.py
index c75292c96..8f27d0269 100644
--- a/modoboa/admin/app_settings.py
+++ b/modoboa/admin/app_settings.py
@@ -212,7 +212,7 @@ class AdminParametersForm(param_forms.AdminParametersForm):
dpath = fpath
if dpath:
try:
- code, version = exec_cmd("%s --version" % dpath)
+ code, version = exec_cmd(f"{dpath} --version")
except OSError:
hide_fields = True
else:
@@ -255,18 +255,16 @@ class AdminParametersForm(param_forms.AdminParametersForm):
if not value:
return value
try:
- ip_addresses = [
- ipaddress.ip_network(v.strip()) for v in value.split() if v.strip()
- ]
+ [ipaddress.ip_network(v.strip()) for v in value.split() if v.strip()]
except ValueError:
raise forms.ValidationError(
gettext_lazy("This field only allows valid IP addresses (or networks)")
- )
+ ) from None
return value
def clean(self):
"""Check MX options."""
- cleaned_data = super(AdminParametersForm, self).clean()
+ cleaned_data = super().clean()
condition = (
cleaned_data.get("enable_mx_checks")
and cleaned_data.get("domains_must_have_authorized_mx")
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..f43c1979a 100644
--- a/modoboa/admin/forms/account.py
+++ b/modoboa/admin/forms/account.py
@@ -71,7 +71,7 @@ class AccountFormGeneral(forms.ModelForm):
labels = {"is_active": gettext_lazy("Enabled")}
def __init__(self, user, *args, **kwargs):
- super(AccountFormGeneral, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.fields = OrderedDict(
(key, self.fields[key])
for key in [
@@ -163,7 +163,7 @@ class AccountFormGeneral(forms.ModelForm):
def clean(self):
"""Check master user mode."""
- super(AccountFormGeneral, self).clean()
+ super().clean()
if self.errors:
return self.cleaned_data
condition = (
@@ -200,7 +200,7 @@ class AccountFormGeneral(forms.ModelForm):
return self.cleaned_data
def save(self, commit=True):
- account = super(AccountFormGeneral, self).save(commit=False)
+ account = super().save(commit=False)
if self.user == account and not self.cleaned_data["is_active"]:
raise lib_exceptions.PermDeniedException(
_("You can't disable your own account")
@@ -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):
@@ -355,7 +355,7 @@ class AccountFormMail(forms.Form, DynamicForm):
Check if quota is >= 0 only when the domain value is not used.
"""
- cleaned_data = super(AccountFormMail, self).clean()
+ cleaned_data = super().clean()
use_default_domain_quota = cleaned_data["quota_act"]
condition = (
not use_default_domain_quota
@@ -510,13 +510,13 @@ class AccountPermissionsForm(forms.Form, DynamicForm):
self.account = kwargs["instance"]
del kwargs["instance"]
- super(AccountPermissionsForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
if not hasattr(self, "account") or self.account is None:
return
qset = models.Domain.objects.get_for_admin(self.account)
for pos, dom in enumerate(qset):
- name = "domains_%d" % (pos + 1)
+ name = f"domains_{pos + 1}"
self._create_field(lib_fields.DomainNameField, name, dom.name)
if len(args) and isinstance(args[0], QueryDict):
self._load_from_qdict(args[0], "domains", lib_fields.DomainNameField)
@@ -580,7 +580,7 @@ class AccountForm(TabForms):
cbargs["account"] = kwargs["instances"]["general"]
results = signals.extra_account_forms.send(sender=self.__class__, **cbargs)
self.forms += reduce(lambda a, b: a + b, [result[1] for result in results])
- super(AccountForm, self).__init__(request, *args, **kwargs)
+ super().__init__(request, *args, **kwargs)
def extra_context(self, context):
account = self.instances["general"]
@@ -604,8 +604,8 @@ class AccountForm(TabForms):
if form["id"] == "general":
return True
- if hasattr(self, "check_%s" % form["id"]):
- if not getattr(self, "check_%s" % form["id"])(self.account):
+ if hasattr(self, "check_{}".format(form["id"])):
+ if not getattr(self, "check_{}".format(form["id"]))(self.account):
return False
return True
@@ -620,9 +620,9 @@ class AccountForm(TabForms):
def is_valid(self):
"""Two steps validation."""
self.instances["general"].oldgroup = self.instances["general"].role
- if super(AccountForm, self).is_valid(mandatory_only=True):
+ if super().is_valid(mandatory_only=True):
self.account = self.forms[0]["instance"].save()
- return super(AccountForm, self).is_valid(optional_only=True)
+ return super().is_valid(optional_only=True)
return False
def save(self):
@@ -646,7 +646,7 @@ class AccountWizard(WizardForm):
"""Account creation wizard."""
def __init__(self, request):
- super(AccountWizard, self).__init__(request)
+ super().__init__(request)
self.add_step(
WizardStep(
"general", AccountFormGeneral, _("General"), new_args=[request.user]
diff --git a/modoboa/admin/forms/alias.py b/modoboa/admin/forms/alias.py
index 33794bfdb..ab95bf1a9 100644
--- a/modoboa/admin/forms/alias.py
+++ b/modoboa/admin/forms/alias.py
@@ -49,7 +49,7 @@ class AliasForm(forms.ModelForm, DynamicForm):
def __init__(self, user, *args, **kwargs):
self.user = user
- super(AliasForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.fields = OrderedDict(
(key, self.fields[key])
for key in [
@@ -80,7 +80,7 @@ class AliasForm(forms.ModelForm, DynamicForm):
self.fields["enabled"].widget.attrs["disabled"] = "disabled"
cpt = 1
for rcpt in alias.aliasrecipient_set.filter(alias__internal=False):
- name = "recipients_%d" % cpt
+ name = f"recipients_{cpt}"
self._create_field(forms.EmailField, name, rcpt.address, 2)
cpt += 1
@@ -92,7 +92,7 @@ class AliasForm(forms.ModelForm, DynamicForm):
try:
domain = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
- raise forms.ValidationError(_("Domain does not exist"))
+ raise forms.ValidationError(_("Domain does not exist")) from None
if not self.user.can_access(domain):
raise forms.ValidationError(_("You don't have access to this domain"))
if not self.instance.pk:
@@ -101,12 +101,12 @@ class AliasForm(forms.ModelForm, DynamicForm):
sender=self.__class__, context=domain, object_type="mailbox_aliases"
)
except lib_exceptions.ModoboaException as inst:
- raise forms.ValidationError(inst)
+ raise forms.ValidationError(inst) from None
return self.cleaned_data["address"].lower()
def clean(self):
"""Check it there is at least one recipient."""
- super(AliasForm, self).clean()
+ super().clean()
random_address = self.cleaned_data.get("random_address")
if not random_address:
if not self.cleaned_data.get("address"):
@@ -121,11 +121,9 @@ class AliasForm(forms.ModelForm, DynamicForm):
def save(self, commit=True):
"""Custom save method."""
- alias = super(AliasForm, self).save(commit=False)
+ alias = super().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/forms/export.py b/modoboa/admin/forms/export.py
index ba0863664..6d9c9d925 100644
--- a/modoboa/admin/forms/export.py
+++ b/modoboa/admin/forms/export.py
@@ -13,7 +13,7 @@ class ExportDataForm(forms.Form):
)
def __init__(self, *args, **kwargs):
- super(ExportDataForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.fields["sepchar"].widget.attrs = {"class": "col-md-1 form-control"}
def clean_sepchar(self):
diff --git a/modoboa/admin/forms/import_.py b/modoboa/admin/forms/import_.py
index a038d1e35..86f53dbaf 100644
--- a/modoboa/admin/forms/import_.py
+++ b/modoboa/admin/forms/import_.py
@@ -21,7 +21,7 @@ class ImportDataForm(forms.Form):
)
def __init__(self, *args, **kwargs):
- super(ImportDataForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.fields["sepchar"].widget.attrs = {"class": "col-md-1 form-control"}
def clean_sepchar(self):
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/lib.py b/modoboa/admin/lib.py
index bfbb07a8e..9715c06c7 100644
--- a/modoboa/admin/lib.py
+++ b/modoboa/admin/lib.py
@@ -205,7 +205,7 @@ def get_authoritative_server(domain, resolver=None):
return str(first_answer.target)
if hasattr(first_answer, "address"):
return str(first_answer.address)
- except dns.resolver.NoAnswer as e:
+ except dns.resolver.NoAnswer:
dnsname = dnsname.parent()
else:
dnsname = dnsname.parent()
@@ -253,7 +253,7 @@ def get_dns_records(name, typ, resolver=None):
exc_info=e,
)
except dns.name.NameTooLong as e:
- logger.error(_("DNS name is too long: %s" % name), exc_info=e)
+ logger.error(_("DNS name is too long: {}".format(name)), exc_info=e)
else:
return dns_answers
return None
@@ -359,7 +359,7 @@ def import_data(user, file_object, options: dict):
raise Conflict(
_("Object already exists: %s")
% options["sepchar"].join(row[:2])
- )
+ ) from None
cpt += 1
msg = _("%d objects imported successfully") % cpt
return True, msg
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 0be4e2575..0f2c39b62 100644
--- a/modoboa/admin/management/commands/subcommands/_import.py
+++ b/modoboa/admin/management/commands/subcommands/_import.py
@@ -1,13 +1,11 @@
"""Django management command to import admin objects."""
import csv
-import io
import os
import progressbar
from chardet.universaldetector import UniversalDetector
-from django.core.exceptions import ValidationError
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from django.utils.translation import gettext as _
@@ -50,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:
@@ -77,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)
@@ -102,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/migrations/0002_migrate_from_modoboa_admin.py b/modoboa/admin/migrations/0002_migrate_from_modoboa_admin.py
index 13d831adb..900f543ea 100644
--- a/modoboa/admin/migrations/0002_migrate_from_modoboa_admin.py
+++ b/modoboa/admin/migrations/0002_migrate_from_modoboa_admin.py
@@ -1,4 +1,4 @@
-from django.db import models, migrations
+from django.db import migrations
def rename_and_clean(apps, schema_editor):
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..1fb857ee8 100644
--- a/modoboa/admin/models/alias.py
+++ b/modoboa/admin/models/alias.py
@@ -54,7 +54,7 @@ def validate_alias_address(
object_type="mailbox_aliases",
)
except ModoboaException as inst:
- raise ValidationError(str(inst))
+ raise ValidationError(str(inst)) from None
return local_part, domain
@@ -132,7 +132,7 @@ class Alias(AdminObject):
return "---"
rcpts = self.recipients
if rcpts_count > 1:
- return "%s, ..." % rcpts[0]
+ return f"{rcpts[0]}, ..."
return rcpts[0]
@property
@@ -150,7 +150,7 @@ class Alias(AdminObject):
def post_create(self, creator):
from modoboa.lib.permissions import grant_access_to_object
- super(Alias, self).post_create(creator)
+ super().post_create(creator)
if creator.is_superuser:
for admin in self.domain.admins:
grant_access_to_object(admin, self)
@@ -174,7 +174,7 @@ class Alias(AdminObject):
address, return_extension=True
)
if domname is None:
- raise BadRequest("%s %s" % (_("Invalid address"), address))
+ raise BadRequest("{} {}".format(_("Invalid address"), address))
domain = Domain.objects.filter(name=domname).first()
kwargs = {"address": address, "alias": self}
if (domain is not None) and (
@@ -189,7 +189,7 @@ class Alias(AdminObject):
rcpt = Mailbox.objects.filter(domain=domain, address=local_part).first()
if rcpt is None:
rcpt = Alias.objects.filter(
- address="%s@%s" % (local_part, domname)
+ address=f"{local_part}@{domname}"
).first()
if rcpt is None:
raise NotFound(
@@ -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/base.py b/modoboa/admin/models/base.py
index 0fc6ef7d3..06b71029b 100644
--- a/modoboa/admin/models/base.py
+++ b/modoboa/admin/models/base.py
@@ -62,10 +62,10 @@ class AdminObject(models.Model):
def save(self, *args, **kwargs):
creator = kwargs.pop("creator", None)
- super(AdminObject, self).save(*args, **kwargs)
+ super().save(*args, **kwargs)
if creator is not None:
self.post_create(creator)
def delete(self):
ungrant_access_to_object(self)
- super(AdminObject, self).delete()
+ super().delete()
diff --git a/modoboa/admin/models/domain.py b/modoboa/admin/models/domain.py
index 6a84c2f1a..6680c97f9 100644
--- a/modoboa/admin/models/domain.py
+++ b/modoboa/admin/models/domain.py
@@ -271,22 +271,20 @@ class Domain(mixins.MessageLimitMixin, AdminObject):
@property
def bind_format_dkim_public_key(self):
"""Return DKIM public key using Bind format."""
- record = "v=DKIM1;k=rsa;p=%s" % self.dkim_public_key
+ record = f"v=DKIM1;k=rsa;p={self.dkim_public_key}"
# TXT records longer than 255 characters need split into chunks
# split record into 74 character chunks (+2 for indent, +2 for quotes(")
# == 78 characters) to make more readable in text editors.
split_record = []
while record:
if len(record) > 74:
- split_record.append(' "%s"' % record[:74])
+ split_record.append(f' "{record[:74]}"')
record = record[74:]
else:
- split_record.append(' "%s"' % record)
+ split_record.append(f' "{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.
@@ -345,7 +343,7 @@ class Domain(mixins.MessageLimitMixin, AdminObject):
if param_tools.get_global_parameter("auto_account_removal"):
User.objects.filter(mailbox__domain=self).delete()
elif self.mailbox_set.count():
- Quota.objects.filter(username__contains="@%s" % self.name).delete()
+ Quota.objects.filter(username__contains=f"@{self.name}").delete()
ungrant_access_to_objects(self.mailbox_set.all())
super().delete()
@@ -380,7 +378,9 @@ class Domain(mixins.MessageLimitMixin, AdminObject):
try:
self.quota = int(row[2].strip())
except ValueError:
- raise BadRequest(_("{}: invalid quota value for domain").format(self.name))
+ raise BadRequest(
+ _("{}: invalid quota value for domain").format(self.name)
+ ) from None
try:
self.default_mailbox_quota = int(row[3].strip())
except ValueError:
@@ -388,7 +388,7 @@ class Domain(mixins.MessageLimitMixin, AdminObject):
_("{}: invalid default mailbox quota value for domain").format(
self.name
)
- )
+ ) from None
if self.quota != 0 and self.default_mailbox_quota > self.quota:
raise BadRequest(
_(
diff --git a/modoboa/admin/models/domain_alias.py b/modoboa/admin/models/domain_alias.py
index 32a00b7f9..218795fbc 100644
--- a/modoboa/admin/models/domain_alias.py
+++ b/modoboa/admin/models/domain_alias.py
@@ -76,7 +76,7 @@ class DomainAlias(AdminObject):
try:
self.target = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
- raise BadRequest(_("Unknown domain %s") % domname)
+ raise BadRequest(_("Unknown domain %s") % domname) from None
core_signals.can_create_object.send(
sender="import", context=self.target, object_type="domain_aliases"
)
diff --git a/modoboa/admin/models/mailbox.py b/modoboa/admin/models/mailbox.py
index e87d76502..f1acaeb3c 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
@@ -114,7 +114,7 @@ class Mailbox(mixins.MessageLimitMixin, AdminObject):
return smart_str(self.full_address)
def __full_address(self, localpart):
- return "%s@%s" % (localpart, self.domain.name)
+ return f"{localpart}@{self.domain.name}"
@property
def full_address(self):
@@ -147,7 +147,7 @@ class Mailbox(mixins.MessageLimitMixin, AdminObject):
if not admin_params.get("handle_mailboxes"):
return None
if self.__mail_home is None:
- code, output = doveadm_cmd("user -f home %s" % self.full_address)
+ code, output = doveadm_cmd(f"user -f home {self.full_address}")
if code:
raise lib_exceptions.InternalError(
_("Failed to retrieve mailbox location (%s)") % output
@@ -384,5 +384,5 @@ class MailboxOperation(models.Model):
def __str__(self):
if self.type == "rename":
- return "Rename %s -> %s" % (self.argument, self.mailbox.mail_home)
- return "Delete %s" % self.argument
+ return f"Rename {self.argument} -> {self.mailbox.mail_home}"
+ return f"Delete {self.argument}"
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/templatetags/admin_tags.py b/modoboa/admin/templatetags/admin_tags.py
index 0da358765..43c5c9757 100644
--- a/modoboa/admin/templatetags/admin_tags.py
+++ b/modoboa/admin/templatetags/admin_tags.py
@@ -151,7 +151,7 @@ def domain_actions(user, domain):
actions = [
{
"name": "listidentities",
- "url": "{0}#list/?searchquery=@{1}".format(
+ "url": "{}#list/?searchquery=@{}".format(
reverse("admin:identity_list"), domain.name
),
"title": _("View the domain's identities"),
@@ -268,7 +268,7 @@ def domain_aliases(domain):
return "---"
res = ""
for alias in domain.aliases.all():
- res += "%s
" % alias.name
+ res += f"{alias.name}
"
return mark_safe(res)
@@ -286,7 +286,7 @@ def identity_modify_link(identity, active_tab="default"):
linkdef = {"label": identity.identity, "modal": True}
if identity.__class__.__name__ == "User":
linkdef["url"] = reverse("admin:account_change", args=[identity.id])
- linkdef["url"] += "?active_tab=%s" % active_tab
+ linkdef["url"] += f"?active_tab={active_tab}"
linkdef["modalcb"] = "admin.editaccount_cb"
else:
linkdef["url"] = reverse("admin:alias_change", args=[identity.id])
@@ -299,7 +299,7 @@ def domadmin_actions(daid, domid):
actions = [
{
"name": "removeperm",
- "url": "{0}?domid={1}&daid={2}".format(
+ "url": "{}?domid={}&daid={}".format(
reverse("admin:permission_remove"), domid, daid
),
"img": "fa fa-trash",
diff --git a/modoboa/admin/tests/test_account.py b/modoboa/admin/tests/test_account.py
index 561079b02..79d44a694 100644
--- a/modoboa/admin/tests/test_account.py
+++ b/modoboa/admin/tests/test_account.py
@@ -17,7 +17,7 @@ class AuthenticationTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
- super(AuthenticationTestCase, cls).setUpTestData()
+ super().setUpTestData()
cls.mb = factories.MailboxFactory(
domain__name="test.com",
address="user",
@@ -42,7 +42,7 @@ class AccountTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
- super(AccountTestCase, cls).setUpTestData()
+ super().setUpTestData()
factories.populate_database()
def test_crud(self):
@@ -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")
@@ -612,10 +612,10 @@ class PermissionsTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
- super(PermissionsTestCase, cls).setUpTestData()
+ super().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()
@@ -625,7 +625,7 @@ class PermissionsTestCase(ModoTestCase):
def setUp(self):
"""Initiate test context."""
- super(PermissionsTestCase, self).setUp()
+ super().setUp()
self.values = {
"username": self.user.username,
"role": "DomainAdmins",
diff --git a/modoboa/admin/tests/test_alarms.py b/modoboa/admin/tests/test_alarms.py
index 07691f3f0..e81e6bdc6 100644
--- a/modoboa/admin/tests/test_alarms.py
+++ b/modoboa/admin/tests/test_alarms.py
@@ -1,7 +1,5 @@
"""Alarm related tests."""
-from django.urls import reverse
-
from modoboa.lib.tests import ModoTestCase
from ..factories import populate_database
from ..models import Alarm, Domain
diff --git a/modoboa/admin/tests/test_alias.py b/modoboa/admin/tests/test_alias.py
index dafa6b2cd..f2984bdd1 100644
--- a/modoboa/admin/tests/test_alias.py
+++ b/modoboa/admin/tests/test_alias.py
@@ -13,7 +13,7 @@ class AliasTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
- super(AliasTestCase, cls).setUpTestData()
+ super().setUpTestData()
factories.populate_database()
def test_alias(self):
@@ -173,7 +173,7 @@ class AliasTestCase(ModoTestCase):
self.ajax_post(reverse("admin:alias_change", args=[fwd.id]), values)
self.assertEqual(fwd.recipients_count, 1)
- self.ajax_delete(reverse("admin:alias_delete") + "?selection=%d" % fwd.id, {})
+ self.ajax_delete(reverse("admin:alias_delete") + f"?selection={fwd.id}", {})
self.assertRaises(
Alias.DoesNotExist, Alias.objects.get, address="forward2@test.com"
)
diff --git a/modoboa/admin/tests/test_domain.py b/modoboa/admin/tests/test_domain.py
index f0b7fcd40..1b3859276 100644
--- a/modoboa/admin/tests/test_domain.py
+++ b/modoboa/admin/tests/test_domain.py
@@ -32,7 +32,7 @@ class DomainTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
- super(DomainTestCase, cls).setUpTestData()
+ super().setUpTestData()
factories.populate_database()
def test_create(self):
@@ -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"])
@@ -431,7 +431,7 @@ class DKIMTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
- super(DKIMTestCase, cls).setUpTestData()
+ super().setUpTestData()
factories.populate_database()
def setUp(self):
diff --git a/modoboa/admin/tests/test_domain_alias.py b/modoboa/admin/tests/test_domain_alias.py
index 6cf4f07cf..883e0a061 100644
--- a/modoboa/admin/tests/test_domain_alias.py
+++ b/modoboa/admin/tests/test_domain_alias.py
@@ -10,7 +10,7 @@ class DomainAliasTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
- super(DomainAliasTestCase, cls).setUpTestData()
+ super().setUpTestData()
factories.populate_database()
cls.dom = Domain.objects.get(name="test.com")
@@ -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_export.py b/modoboa/admin/tests/test_export.py
index 0de2728e0..ae4c8df90 100644
--- a/modoboa/admin/tests/test_export.py
+++ b/modoboa/admin/tests/test_export.py
@@ -17,7 +17,7 @@ class ExportTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
- super(ExportTestCase, cls).setUpTestData()
+ super().setUpTestData()
factories.populate_database()
def __export_domains(self, domfilter=""):
@@ -31,7 +31,7 @@ class ExportTestCase(ModoTestCase):
def __export_identities(self, idtfilter="", grpfilter=""):
self.client.get(
reverse("admin:_identity_list")
- + "?grpfilter=%s&idtfilter=%s" % (grpfilter, idtfilter)
+ + f"?grpfilter={grpfilter}&idtfilter={idtfilter}"
)
return self.client.post(
reverse("admin:identity_export"), {"filename": "test.csv"}
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..84a45a0de 100644
--- a/modoboa/admin/tests/test_mailbox_operations.py
+++ b/modoboa/admin/tests/test_mailbox_operations.py
@@ -13,21 +13,21 @@ 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."""
@classmethod
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
- super(MailboxOperationTestCase, cls).setUpTestData()
+ super().setUpTestData()
factories.populate_database()
def setUp(self):
"""Initiate test env."""
- super(MailboxOperationTestCase, self).setUp()
+ super().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..a2e967b2c 100644
--- a/modoboa/admin/tests/test_mx.py
+++ b/modoboa/admin/tests/test_mx.py
@@ -23,7 +23,7 @@ class MXTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls): # NOQA:N802
"""Create some data."""
- super(MXTestCase, cls).setUpTestData()
+ super().setUpTestData()
cls.domain = factories.DomainFactory(name="modoboa.org")
# should not exist
cls.bad_domain = factories.DomainFactory(name="does-not-exist.example.com")
@@ -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)
@@ -297,7 +297,7 @@ class DNSChecksTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls): # NOQA:N802
"""Create some data."""
- super(DNSChecksTestCase, cls).setUpTestData()
+ super().setUpTestData()
cls.domain = factories.DomainFactory(name="dns-checks.com")
@mock.patch("gevent.socket.gethostbyname")
diff --git a/modoboa/admin/tests/test_password_schemes.py b/modoboa/admin/tests/test_password_schemes.py
index de48eda3f..7a5d56fdc 100644
--- a/modoboa/admin/tests/test_password_schemes.py
+++ b/modoboa/admin/tests/test_password_schemes.py
@@ -10,7 +10,7 @@ class PasswordSchemesTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
- super(PasswordSchemesTestCase, cls).setUpTestData()
+ super().setUpTestData()
factories.populate_database()
def _create_account(self):
diff --git a/modoboa/admin/tests/test_repair.py b/modoboa/admin/tests/test_repair.py
index 6a6a1e6bb..3a23901c3 100644
--- a/modoboa/admin/tests/test_repair.py
+++ b/modoboa/admin/tests/test_repair.py
@@ -13,7 +13,7 @@ class RepairTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls): # NOQA:N802
"""Create some data."""
- super(RepairTestCase, cls).setUpTestData()
+ super().setUpTestData()
factories.populate_database()
def test_management_command(self):
diff --git a/modoboa/admin/tests/test_user.py b/modoboa/admin/tests/test_user.py
index f4614aab4..b5096ff92 100644
--- a/modoboa/admin/tests/test_user.py
+++ b/modoboa/admin/tests/test_user.py
@@ -12,7 +12,7 @@ class ForwardTestCase(ModoTestCase):
"""User forward test cases."""
def setUp(self):
- super(ForwardTestCase, self).setUp()
+ super().setUp()
populate_database()
def test_set_forward(self):
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/alias.py b/modoboa/admin/views/alias.py
index ac491811d..8b3e5c109 100644
--- a/modoboa/admin/views/alias.py
+++ b/modoboa/admin/views/alias.py
@@ -27,7 +27,7 @@ def _validate_alias(request, form, successmsg, callback=None):
try:
alias = form.save()
except IntegrityError:
- raise Conflict(_("Alias with this name already exists"))
+ raise Conflict(_("Alias with this name already exists")) from None
if callback:
callback(request.user, alias)
return render_to_json_response(successmsg)
@@ -110,13 +110,13 @@ class AliasDetailView(auth_mixins.PermissionRequiredMixin, generic.DetailView):
def has_permission(self):
"""Check object-level access."""
- result = super(AliasDetailView, self).has_permission()
+ result = super().has_permission()
if not result:
return result
return self.request.user.can_access(self.get_object())
def get_context_data(self, **kwargs):
"""Add information to context."""
- context = super(AliasDetailView, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
context["selection"] = "identities"
return context
diff --git a/modoboa/admin/views/dns.py b/modoboa/admin/views/dns.py
index cfdde03eb..778f24571 100644
--- a/modoboa/admin/views/dns.py
+++ b/modoboa/admin/views/dns.py
@@ -16,15 +16,11 @@ class MXDomainDetailView(auth_mixins.PermissionRequiredMixin, generic.DetailView
def get_queryset(self):
"""Add some prefetching."""
- return (
- super(MXDomainDetailView, self)
- .get_queryset()
- .prefetch_related("mxrecord_set")
- )
+ return super().get_queryset().prefetch_related("mxrecord_set")
def get_context_data(self, **kwargs):
"""Add extra variables."""
- context = super(MXDomainDetailView, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
context.update({"title": _("MX records of {}").format(self.object.name)})
return context
@@ -38,14 +34,10 @@ class DNSBLDomainDetailView(auth_mixins.PermissionRequiredMixin, generic.DetailV
def get_queryset(self):
"""Add some prefetching."""
- return (
- super(DNSBLDomainDetailView, self)
- .get_queryset()
- .prefetch_related("dnsblresult_set")
- )
+ return super().get_queryset().prefetch_related("dnsblresult_set")
def get_context_data(self, **kwargs):
"""Add extra variables."""
- context = super(DNSBLDomainDetailView, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
context.update({"title": _("DNSBL summary for {}").format(self.object.name)})
return context
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/admin/views/identity.py b/modoboa/admin/views/identity.py
index 09aa11a8f..985c8b528 100644
--- a/modoboa/admin/views/identity.py
+++ b/modoboa/admin/views/identity.py
@@ -75,7 +75,7 @@ def list_quotas(request):
)
mboxes = mboxes.exclude(quota=0)
if sort_order in ["address", "quota"]:
- mboxes = mboxes.order_by("%s%s" % (sort_dir, sort_order))
+ mboxes = mboxes.order_by(f"{sort_dir}{sort_order}")
elif sort_order in ("quota_value__bytes", "quota_usage"):
db_type = db_type()
if db_type == "mysql":
@@ -85,9 +85,9 @@ def list_quotas(request):
if sort_order == "quota_value__bytes":
mboxes = mboxes.extra(
select={"quota_value__bytes": "admin_quota.bytes"},
- where=["admin_quota.username=%s" % where],
+ where=[f"admin_quota.username={where}"],
tables=["admin_quota", "admin_domain"],
- order_by=["%s%s" % (sort_dir, sort_order)],
+ order_by=[f"{sort_dir}{sort_order}"],
)
else:
if db_type == "postgres":
@@ -102,9 +102,9 @@ def list_quotas(request):
)
mboxes = mboxes.extra(
select={"quota_usage": select},
- where=["admin_quota.username=%s" % where],
+ where=[f"admin_quota.username={where}"],
tables=["admin_quota", "admin_domain"],
- order_by=["%s%s" % (sort_dir, sort_order)],
+ order_by=[f"{sort_dir}{sort_order}"],
)
else:
raise BadRequest(_("Invalid request"))
@@ -199,7 +199,7 @@ def remove_permission(request):
account = User.objects.get(pk=daid)
domain = Domain.objects.get(pk=domid)
except (User.DoesNotExist, Domain.DoesNotExist):
- raise BadRequest(_("Invalid request"))
+ raise BadRequest(_("Invalid request")) from None
if not request.user.can_access(account) or not request.user.can_access(domain):
raise PermDeniedException
domain.remove_admin(account)
@@ -215,14 +215,14 @@ class AccountDetailView(auth_mixins.PermissionRequiredMixin, generic.DetailView)
def has_permission(self):
"""Check object-level access."""
- result = super(AccountDetailView, self).has_permission()
+ result = super().has_permission()
if not result:
return result
return self.request.user.can_access(self.get_object())
def get_context_data(self, **kwargs):
"""Add information to context."""
- context = super(AccountDetailView, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
del context["user"]
result = signals.extra_account_dashboard_widgets.send(
self.__class__, user=self.request.user, account=self.object
diff --git a/modoboa/admin/views/import_.py b/modoboa/admin/views/import_.py
index 80744e10d..fd7f184d4 100644
--- a/modoboa/admin/views/import_.py
+++ b/modoboa/admin/views/import_.py
@@ -1,8 +1,5 @@
"""Import related views."""
-import csv
-import io
-
from reversion import revisions as reversion
from django.contrib.auth.decorators import (
@@ -10,15 +7,11 @@ from django.contrib.auth.decorators import (
permission_required,
user_passes_test,
)
-from django.db import transaction
from django.shortcuts import render
from django.urls import reverse
-from django.utils.encoding import smart_str
from django.utils.translation import gettext as _
-from modoboa.lib.exceptions import Conflict, ModoboaException
from .. import lib
-from .. import signals
from ..forms import ImportDataForm, ImportIdentitiesForm
diff --git a/modoboa/calendars/apps.py b/modoboa/calendars/apps.py
index 8b847e87e..ffbace58f 100644
--- a/modoboa/calendars/apps.py
+++ b/modoboa/calendars/apps.py
@@ -10,7 +10,7 @@ class CalendarsConfig(AppConfig):
verbose_name = "Modoboa calendars"
def ready(self):
- from modoboa.calendars import handlers
+ from modoboa.calendars import handlers # noqa
from modoboa.calendars.app_settings import load_settings
load_settings()
diff --git a/modoboa/calendars/backends/__init__.py b/modoboa/calendars/backends/__init__.py
index 8d463a517..b18191dfc 100644
--- a/modoboa/calendars/backends/__init__.py
+++ b/modoboa/calendars/backends/__init__.py
@@ -2,10 +2,8 @@
from importlib import import_module
-from modoboa.lib.cryptutils import decrypt
-
-class CalendarBackend(object):
+class CalendarBackend:
"""Base backend class."""
def __init__(self, calendar=None):
@@ -31,11 +29,10 @@ 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):
"""Return a backend instance from a request."""
- # password = decrypt(request.session["password"])
return get_backend(name, request.user.email, request.auth, calendar=calendar)
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/factories.py b/modoboa/calendars/factories.py
index b8b0b32f5..ac3b37e46 100644
--- a/modoboa/calendars/factories.py
+++ b/modoboa/calendars/factories.py
@@ -12,7 +12,7 @@ class UserCalendarFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.UserCalendar
- name = factory.Sequence(lambda n: "User calendar %s" % n)
+ name = factory.Sequence(lambda n: f"User calendar {n}")
mailbox = factory.SubFactory(admin_factories.MailboxFactory)
@@ -21,7 +21,7 @@ class SharedCalendarFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.SharedCalendar
- name = factory.Sequence(lambda n: "Shared calendar %s" % n)
+ name = factory.Sequence(lambda n: f"Shared calendar {n}")
domain = factory.SubFactory(admin_factories.DomainFactory)
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..35cd954ac 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):
@@ -162,6 +162,7 @@ class AccessRule(models.Model):
access = "r" if self.read else ""
access += "w" if self.write else ""
return smart_str(
- "%s access rule to %s -> %s"
- % (self.mailbox, self.calendar, access if access else "no access")
+ "{} access rule to {} -> {}".format(
+ self.mailbox, self.calendar, access if access else "no access"
+ )
)
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/apps.py b/modoboa/contacts/apps.py
index fb3cab3ff..8f002beb8 100644
--- a/modoboa/contacts/apps.py
+++ b/modoboa/contacts/apps.py
@@ -7,7 +7,7 @@ class ModoboaContactsConfig(AppConfig):
name = "modoboa.contacts"
def ready(self):
- from . import handlers
+ from . import handlers # noqa
from modoboa.contacts.app_settings import load_settings
load_settings()
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/management/commands/import_contacts.py b/modoboa/contacts/management/commands/import_contacts.py
index f1fffc527..d13aa822b 100644
--- a/modoboa/contacts/management/commands/import_contacts.py
+++ b/modoboa/contacts/management/commands/import_contacts.py
@@ -44,7 +44,7 @@ class Command(BaseCommand):
).first()
if not addressbook:
raise CommandError(
- "Address Book for email '%s' not found" % options["email"]
+ "Address Book for email '{}' not found".format(options["email"])
)
try:
import_csv_file(
@@ -55,5 +55,5 @@ class Command(BaseCommand):
options.get("carddav_password"),
)
except RuntimeError as err:
- raise CommandError(err)
+ raise CommandError(err) from None
self.stdout.write(self.style.SUCCESS("File was imported successfuly"))
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 ce87e273b..9225399ce 100644
--- a/modoboa/contacts/tests.py
+++ b/modoboa/contacts/tests.py
@@ -1,4 +1,3 @@
-# coding: utf-8
"""Contacts backend tests."""
import os
@@ -25,7 +24,7 @@ class TestDataMixin:
@classmethod
def setUpTestData(cls):
"""Create test data."""
- super(TestDataMixin, cls).setUpTestData()
+ super().setUpTestData()
admin_factories.populate_database()
cls.user = core_models.User.objects.get(username="user@test.com")
cls.addressbook = cls.user.addressbook_set.first()
@@ -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)
@@ -339,7 +338,7 @@ class ImportTestCase(TestDataMixin, ModoTestCase):
def test_import_from_outlook_auto(self):
management.call_command("import_contacts", "user@test.com", self.path)
address = models.EmailAddress.objects.get(address="toto@titi.com")
- phone = models.PhoneNumber.objects.get(number="12345678")
+ models.PhoneNumber.objects.get(number="12345678")
self.assertEqual(address.contact.first_name, "Toto Tata")
self.assertEqual(address.contact.addressbook.user.email, "user@test.com")
self.assertEqual(address.contact.address, "Street 1 Street 2")
@@ -352,7 +351,7 @@ class ImportTestCase(TestDataMixin, ModoTestCase):
backend="outlook",
)
address = models.EmailAddress.objects.get(address="toto@titi.com")
- phone = models.PhoneNumber.objects.get(number="12345678")
+ models.PhoneNumber.objects.get(number="12345678")
self.assertEqual(address.contact.first_name, "Toto Tata")
self.assertEqual(address.contact.addressbook.user.email, "user@test.com")
self.assertEqual(address.contact.address, "Street 1 Street 2")
@@ -366,7 +365,7 @@ class ImportTestCase(TestDataMixin, ModoTestCase):
carddav_password="Toto1234",
)
address = models.EmailAddress.objects.get(address="toto@titi.com")
- phone = models.PhoneNumber.objects.get(number="12345678")
+ models.PhoneNumber.objects.get(number="12345678")
self.assertEqual(address.contact.first_name, "Toto Tata")
self.assertEqual(address.contact.addressbook.user.email, "user@test.com")
self.assertEqual(address.contact.address, "Street 1 Street 2")
diff --git a/modoboa/core/api/v1/viewsets.py b/modoboa/core/api/v1/viewsets.py
index 34478de7e..2f4dad928 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,9 +43,9 @@ 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):
+ for _cpt in range(10):
token = StaticToken.random_token()
device.token_set.create(token=token)
django_otp.login(self.request, request.user.totpdevice_set.first())
@@ -82,7 +82,7 @@ class AccountViewSet(GetThrottleViewsetMixin, viewsets.ViewSet):
if device is None:
return response.Response({"error": _("2FA is not enabled")}, status=403)
device.token_set.all().delete()
- for cpt in range(10):
+ for _cpt in range(10):
token = StaticToken.random_token()
device.token_set.create(token=token)
return response.Response(
diff --git a/modoboa/core/api/v2/serializers.py b/modoboa/core/api/v2/serializers.py
index a25196ecd..80527b975 100644
--- a/modoboa/core/api/v2/serializers.py
+++ b/modoboa/core/api/v2/serializers.py
@@ -180,21 +180,21 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
try:
value % {"user": "toto"}
except (KeyError, ValueError):
- raise serializers.ValidationError(_("Invalid syntax"))
+ raise serializers.ValidationError(_("Invalid syntax")) from None
return value
def validate_ldap_sync_account_dn_template(self, value):
try:
value % {"user": "toto"}
except (KeyError, ValueError):
- raise serializers.ValidationError(_("Invalid syntax"))
+ raise serializers.ValidationError(_("Invalid syntax")) from None
return value
def validate_ldap_search_filter(self, value):
try:
value % {"user": "toto"}
except (KeyError, ValueError, TypeError):
- raise serializers.ValidationError(_("Invalid syntax"))
+ raise serializers.ValidationError(_("Invalid syntax")) from None
return value
def validate_rounds_number(self, value):
@@ -416,7 +416,7 @@ class PasswordRecoverySmsConfirmSerializer(serializers.Serializer):
except KeyError:
raise CustomValidationError(
{"reason": "totp secret not set in session"}, 403
- )
+ ) from None
if not oath.accept_totp(self.context["totp_secret"], data["sms_totp"])[0]:
raise CustomValidationError({"reason": "Wrong totp"}, 400)
@@ -445,7 +445,7 @@ class PasswordRecoverySmsResendSerializer(serializers.Serializer):
except KeyError:
raise CustomValidationError(
{"reason": "totp secret not set in session"}, status.HTTP_403_FORBIDDEN
- )
+ ) from None
# Attempt to get user, will raise an error if pk is not valid
self.context["user"] = User.objects.filter(
@@ -501,7 +501,7 @@ class PasswordRecoveryConfirmSerializer(serializers.Serializer):
try:
password_validation.validate_password(data["new_password1"], user)
except djangoValidationError as e:
- raise PasswordRequirementsFailure(e.messages)
+ raise PasswordRequirementsFailure(e.messages) from None
self.context["user"] = user
return super().validate(data)
diff --git a/modoboa/core/api/v2/views.py b/modoboa/core/api/v2/views.py
index 40b2dce4f..5e2a6c6f0 100644
--- a/modoboa/core/api/v2/views.py
+++ b/modoboa/core/api/v2/views.py
@@ -25,11 +25,11 @@ from . import serializers
logger = logging.getLogger("modoboa.auth")
-def delete_cache_key(class_target, throttles, request):
+def delete_cache_key(class_target, throttles: list, request) -> None:
"""Attempt to delete cache key from throttling on login/password reset success."""
for throttle in throttles:
- if type(throttle) == class_target:
+ if isinstance(throttle, class_target):
throttle.reset_cache(request)
return
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/app_settings.py b/modoboa/core/app_settings.py
index 5fdb42c7f..a63f8112a 100644
--- a/modoboa/core/app_settings.py
+++ b/modoboa/core/app_settings.py
@@ -512,7 +512,7 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
}
def __init__(self, *args, **kwargs):
- super(GeneralParametersForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.fields["default_top_redirection"].choices = enabled_applications()
self._add_visibilty_rules(sms_backends.get_all_backend_visibility_rules())
self.fields["password_scheme"].choices = get_password_scheme()
@@ -532,7 +532,7 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
try:
tpl % {"user": "toto"}
except (KeyError, ValueError):
- raise forms.ValidationError(_("Invalid syntax"))
+ raise forms.ValidationError(_("Invalid syntax")) from None
return tpl
def clean_ldap_sync_account_dn_template(self):
@@ -540,7 +540,7 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
try:
tpl % {"user": "toto"}
except (KeyError, ValueError):
- raise forms.ValidationError(_("Invalid syntax"))
+ raise forms.ValidationError(_("Invalid syntax")) from None
return tpl
def clean_ldap_search_filter(self):
@@ -548,7 +548,7 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
try:
ldap_filter % {"user": "toto"}
except (KeyError, ValueError, TypeError):
- raise forms.ValidationError(_("Invalid syntax"))
+ raise forms.ValidationError(_("Invalid syntax")) from None
return ldap_filter
def clean_rounds_number(self):
@@ -613,10 +613,7 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
{"first_name": "givenName", "email": "mail", "last_name": "sn"},
)
ldap_uri = "ldaps://" if values["ldap_secured"] == "ssl" else "ldap://"
- ldap_uri += "%s:%s" % (
- values[backend.srv_address_setting_name],
- values[backend.srv_port_setting_name],
- )
+ ldap_uri += f"{values[backend.srv_address_setting_name]}:{values[backend.srv_port_setting_name]}"
setattr(settings, backend.setting_fullname("SERVER_URI"), ldap_uri)
if values["ldap_secured"] == "starttls":
setattr(settings, backend.setting_fullname("START_TLS"), True)
@@ -680,7 +677,7 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
Doing so, we can use the django_auth_ldap module.
"""
try:
- import ldap
+ import ldap # noqa
ldap_available = True
except ImportError:
diff --git a/modoboa/core/commands/__init__.py b/modoboa/core/commands/__init__.py
index b935d655f..049ee7120 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.
@@ -25,7 +25,7 @@ class Command(object):
{"BACKEND": ("django.template.backends.django.DjangoTemplates")}
]
)
- self._templates_dir = "%s/templates" % os.path.dirname(__file__)
+ self._templates_dir = f"{os.path.dirname(__file__)}/templates"
def _render_template(self, tplfile, env):
"""Render an HTML template."""
@@ -70,7 +70,7 @@ def scan_for_commands(dirname=""):
else:
cmdclassname = cmdname.capitalize()
try:
- cmdclass = getattr(cmdmod, "%sCommand" % cmdclassname)
+ cmdclass = getattr(cmdmod, f"{cmdclassname}Command")
except AttributeError:
continue
result[cmdname] = cmdclass
@@ -83,9 +83,10 @@ def handle_command_line():
parser = argparse.ArgumentParser(
description="A set of utilities to ease the installation of Modoboa.",
epilog="""Available commands:
-%s
-"""
- % "\n".join(["\t%s" % c for c in sorted(commands)]),
+{}
+""".format(
+ "\n".join([f"\t{c}" for c in sorted(commands)])
+ ),
)
parser.add_argument(
"--verbose", action="store_true", help="Activate verbose output"
@@ -94,7 +95,7 @@ def handle_command_line():
(args, remaining) = parser.parse_known_args()
if args.command not in commands:
- print("Unknown command '%s'" % args.command, file=sys.stderr)
+ print(f"Unknown command '{args.command}'", file=sys.stderr)
sys.exit(1)
commands[args.command](commands, verbose=args.verbose).run(remaining)
diff --git a/modoboa/core/commands/deploy.py b/modoboa/core/commands/deploy.py
index 0a3efc0c5..4d3fa9ba0 100644
--- a/modoboa/core/commands/deploy.py
+++ b/modoboa/core/commands/deploy.py
@@ -114,10 +114,10 @@ class DeployCommand(Command):
if p.returncode:
if output:
print(
- "\n".join([l.decode() for l in output if l is not None]),
+ "\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/commands/help.py b/modoboa/core/commands/help.py
index 2c7049b8f..bd5514711 100644
--- a/modoboa/core/commands/help.py
+++ b/modoboa/core/commands/help.py
@@ -8,14 +8,14 @@ class HelpCommand(Command):
help = "Display the help message associated to a specific command" # NOQA:A003
def __init__(self, *args, **kwargs):
- super(HelpCommand, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self._parser.add_argument("name", type=str, help="A command name")
def handle(self, parsed_args):
if parsed_args.name not in self._commands:
- print("Unknown command: %s" % parsed_args.name, file=sys.stderr)
+ print(f"Unknown command: {parsed_args.name}", file=sys.stderr)
sys.exit(1)
cmd = self._commands[parsed_args.name](self._commands)
- cmd._parser.prog = "modoboa-admin.py %s" % parsed_args.name
+ cmd._parser.prog = f"modoboa-admin.py {parsed_args.name}"
cmd._parser.description = cmd.help
cmd._parser.print_help()
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 7c77d9feb..bad1e77c7 100644
--- a/modoboa/core/factories.py
+++ b/modoboa/core/factories.py
@@ -1,7 +1,5 @@
"""Factories for core application."""
-import base64
-
import factory
from django.contrib.auth.models import Group
@@ -12,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
@@ -25,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"
@@ -34,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",)
@@ -54,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/forms.py b/modoboa/core/forms.py
index fdc80c42c..25a646304 100644
--- a/modoboa/core/forms.py
+++ b/modoboa/core/forms.py
@@ -133,7 +133,7 @@ class APIAccessForm(forms.Form):
def __init__(self, *args, **kwargs):
"""Initialize form."""
user = kwargs.pop("user")
- super(APIAccessForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.fields["enable_api_access"].initial = hasattr(user, "auth_token")
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/cleanlogs.py b/modoboa/core/management/commands/cleanlogs.py
index b7332a6e5..f44d6dabd 100644
--- a/modoboa/core/management/commands/cleanlogs.py
+++ b/modoboa/core/management/commands/cleanlogs.py
@@ -38,7 +38,7 @@ class Command(BaseCommand):
self.verbose = options["verbose"]
log_maximum_age = param_tools.get_global_parameter("log_maximum_age")
- self.__vprint("Deleting logs older than %d days..." % log_maximum_age)
+ self.__vprint(f"Deleting logs older than {log_maximum_age} days...")
limit = timezone.now() - datetime.timedelta(log_maximum_age)
Log.objects.filter(date_created__lt=limit).delete()
self.__vprint("Done.")
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/migrations/0002_delete_extension.py b/modoboa/core/migrations/0002_delete_extension.py
index c43cf011f..49ac4aa32 100644
--- a/modoboa/core/migrations/0002_delete_extension.py
+++ b/modoboa/core/migrations/0002_delete_extension.py
@@ -1,4 +1,4 @@
-from django.db import models, migrations
+from django.db import migrations
class Migration(migrations.Migration):
diff --git a/modoboa/core/models.py b/modoboa/core/models.py
index 8f35eb108..351fbb904 100644
--- a/modoboa/core/models.py
+++ b/modoboa/core/models.py
@@ -373,15 +373,17 @@ class User(AbstractUser):
if len(row) < 8 or not row[7].strip():
raise BadRequest(
_(
- "The simple user '%s' must have a valid email address"
- % self.username
+ "The simple user '{}' must have a valid email address".format(
+ self.username
+ )
)
)
if self.username != row[7].strip():
raise BadRequest(
_(
- "username and email fields must not differ for '%s'"
- % self.username
+ "username and email fields must not differ for '{}'".format(
+ self.username
+ )
)
)
@@ -517,4 +519,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/__init__.py b/modoboa/core/password_hashers/__init__.py
index 379732224..b5c1ce167 100644
--- a/modoboa/core/password_hashers/__init__.py
+++ b/modoboa/core/password_hashers/__init__.py
@@ -28,7 +28,7 @@ def get_password_hasher(scheme: str) -> type[PasswordHasher]:
"""
try:
scheme = scheme.replace("-", "")
- hasher = globals()["%sHasher" % scheme.upper()]
+ hasher = globals()[f"{scheme.upper()}Hasher"]
except KeyError:
hasher = PLAINHasher
return hasher
diff --git a/modoboa/core/password_hashers/advanced.py b/modoboa/core/password_hashers/advanced.py
index 8b44d0367..12366b9b1 100644
--- a/modoboa/core/password_hashers/advanced.py
+++ b/modoboa/core/password_hashers/advanced.py
@@ -148,7 +148,7 @@ if argon2_hasher is not None:
def __init__(
self,
):
- super(ARGON2IDHasher, self).__init__()
+ super().__init__()
parameters = dict()
diff --git a/modoboa/core/password_hashers/base.py b/modoboa/core/password_hashers/base.py
index e1ff5056d..76314dfa4 100644
--- a/modoboa/core/password_hashers/base.py
+++ b/modoboa/core/password_hashers/base.py
@@ -80,7 +80,7 @@ class PasswordHasher(metaclass=MetaHasher):
:return: encrypted password
"""
pwhash = self._b64encode(self._encrypt(force_str(clearvalue)))
- return "%s%s" % (self.scheme, force_str(pwhash))
+ return f"{self.scheme}{force_str(pwhash)}"
def verify(self, clearvalue, hashed_value):
"""Verify a password against a hashed value.
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..714a6043d 100644
--- a/modoboa/core/templatetags/core_tags.py
+++ b/modoboa/core/templatetags/core_tags.py
@@ -181,7 +181,7 @@ def colorize_level(level):
}
if level not in classes:
return level
- return "
%s
" % (classes[level], level) + return f"{level}
" @register.filter @@ -238,7 +238,7 @@ def connected_users(parser, token): except ValueError: raise template.TemplateSyntaxError( "connected_users usage: {% connected_users as users %}" - ) + ) from None return ConnectedUsers(varname) @@ -279,14 +279,13 @@ def display_messages(msgs): timeout = "undefined" return mark_safe( - """ + f""" """ - % (level, text, timeout) ) @@ -295,5 +294,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_checks.py b/modoboa/core/tests/test_checks.py index 0c538b865..51d56d9b5 100644 --- a/modoboa/core/tests/test_checks.py +++ b/modoboa/core/tests/test_checks.py @@ -6,7 +6,6 @@ from django.test.utils import override_settings from modoboa.core import checks from modoboa.lib.tests import SimpleModoTestCase -from modoboa.core.password_hashers.base import CRYPTHasher class CheckSessionCookieSecureTest(SimpleModoTestCase): diff --git a/modoboa/core/tests/test_core.py b/modoboa/core/tests/test_core.py index a7be45f93..9b5f35c71 100644 --- a/modoboa/core/tests/test_core.py +++ b/modoboa/core/tests/test_core.py @@ -21,7 +21,7 @@ class AuthenticationTestCase(ModoTestCase): @classmethod def setUpTestData(cls): # NOQA:N802 """Create test data.""" - super(AuthenticationTestCase, cls).setUpTestData() + super().setUpTestData() cls.account = factories.UserFactory( username="user@test.com", groups=("SimpleUsers",) ) @@ -115,7 +115,7 @@ class ProfileTestCase(ModoTestCase): @classmethod def setUpTestData(cls): # NOQA:N802 """Create test data.""" - super(ProfileTestCase, cls).setUpTestData() + super().setUpTestData() cls.account = factories.UserFactory( username="user@test.com", groups=("SimpleUsers",) ) @@ -231,7 +231,7 @@ class APIAccessFormTestCase(ModoTestCase): @classmethod def setUpTestData(cls): # NOQA:N802 """Create test data.""" - super(APIAccessFormTestCase, cls).setUpTestData() + super().setUpTestData() cls.account = factories.UserFactory( username="user@test.com", groups=("SimpleUsers",) ) diff --git a/modoboa/core/tests/test_ldap.py b/modoboa/core/tests/test_ldap.py index d91d11359..21c6ae290 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 @@ -84,7 +84,7 @@ class LDAPAuthenticationTestCase(LDAPTestCaseMixin, ModoTestCase): def setUp(self): """Create test data.""" - super(LDAPAuthenticationTestCase, self).setUp() + super().setUp() self.activate_ldap_authentication() def check_created_user(self, username, group="SimpleUsers", with_mb=True): @@ -142,7 +142,7 @@ class ProfileTestCase(LDAPTestCaseMixin, ModoTestCase): @classmethod def setUpTestData(cls): # NOQA:N802 """Create test data.""" - super(ProfileTestCase, cls).setUpTestData() + super().setUpTestData() cls.account = factories.UserFactory( username="user@test.com", groups=("SimpleUsers",) ) diff --git a/modoboa/core/tests/test_views.py b/modoboa/core/tests/test_views.py index da8c4f866..eb3cb61b6 100644 --- a/modoboa/core/tests/test_views.py +++ b/modoboa/core/tests/test_views.py @@ -148,7 +148,7 @@ class DashboardTestCase(ModoTestCase): @classmethod def setUpTestData(cls): # NOQA:N802 """Create some data.""" - super(DashboardTestCase, cls).setUpTestData() + super().setUpTestData() cls.dadmin = factories.UserFactory( username="admin@test.com", groups=("DomainAdmins",) ) @@ -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.""" @@ -317,13 +317,13 @@ class PreferencesTestCase(ModoTestCase): @classmethod def setUpClass(cls): """Register user form.""" - super(PreferencesTestCase, cls).setUpClass() + super().setUpClass() param_tools.registry.add("user", UserSettings, "Test") @classmethod def tearDownClass(cls): """Remove user class.""" - super(PreferencesTestCase, cls).tearDownClass() + super().tearDownClass() del param_tools.registry._registry["user"]["core"] def test_get_user_index(self): 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/admin.py b/modoboa/core/views/admin.py index a2fe6b3df..27d441458 100644 --- a/modoboa/core/views/admin.py +++ b/modoboa/core/views/admin.py @@ -88,7 +88,7 @@ def get_logs_page(request, page_id=None): if page_id is None: return None return get_listing_page( - Log.objects.all().order_by("%s%s" % (sort_dir, sort_order)), page_id + Log.objects.all().order_by(f"{sort_dir}{sort_order}"), page_id ) diff --git a/modoboa/core/views/auth.py b/modoboa/core/views/auth.py index 058603293..1faa11c2b 100644 --- a/modoboa/core/views/auth.py +++ b/modoboa/core/views/auth.py @@ -141,11 +141,10 @@ class LoginView(LoginViewMixin, auth_views.LoginView): def form_invalid(self, form): # FIXME: should we return a 401 as before ? self.logger.warning( - "Failed connection attempt from '%(addr)s' as user '%(user)s'" - % { - "addr": self.request.META["REMOTE_ADDR"], - "user": escape(form.cleaned_data["username"]), - } + "Failed connection attempt from '{addr}' as user '{user}'".format( + addr=self.request.META["REMOTE_ADDR"], + user=escape(form.cleaned_data["username"]), + ) ) return self.render_to_response(self.get_context_data(form=form), status=401) @@ -220,7 +219,7 @@ class VerifySMSCodeView(generic.FormView): try: kwargs.update({"totp_secret": self.request.session["totp_secret"]}) except KeyError: - raise Http404 + raise Http404 from None return kwargs def form_valid(self, form): @@ -241,11 +240,11 @@ class ResendSMSCodeView(generic.View): "sms_password_recovery" ) if not sms_password_recovery: - raise Http404 + raise Http404 from None try: user = models.User._default_manager.get(pk=self.request.session["user_pk"]) except KeyError: - raise Http404 + raise Http404 from None backend = sms_backends.get_active_backend(self.request.localconfig.parameters) secret = cryptutils.random_hex_key(20) code = oath.totp(secret) @@ -255,7 +254,7 @@ class ResendSMSCodeView(generic.View): ) ) if not backend.send(text, [user.phone_number]): - raise Http404 + raise Http404 from None self.request.session["totp_secret"] = secret return JsonResponse({"status": "ok"}) 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/admin.py b/modoboa/dmarc/admin.py index 8c38f3f3d..846f6b406 100644 --- a/modoboa/dmarc/admin.py +++ b/modoboa/dmarc/admin.py @@ -1,3 +1 @@ -from django.contrib import admin - # Register your models here. diff --git a/modoboa/dmarc/apps.py b/modoboa/dmarc/apps.py index d2c14dd1d..e2c202075 100644 --- a/modoboa/dmarc/apps.py +++ b/modoboa/dmarc/apps.py @@ -13,4 +13,4 @@ class DmarcConfig(AppConfig): def ready(self): load_settings() - from . import handlers + from . import handlers # noqa diff --git a/modoboa/dmarc/lib.py b/modoboa/dmarc/lib.py index 565cfacd5..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 - value = 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_management_command.py b/modoboa/dmarc/tests/test_management_command.py index 1a23be9ed..6631c37d7 100644 --- a/modoboa/dmarc/tests/test_management_command.py +++ b/modoboa/dmarc/tests/test_management_command.py @@ -12,7 +12,7 @@ class ManagementCommandTestCase(mixins.CallCommandMixin, ModoTestCase): @classmethod def setUpTestData(cls): - super(ManagementCommandTestCase, cls).setUpTestData() + super().setUpTestData() cls.domain = admin_factories.DomainFactory(name="ngyn.org") def test_import_from_archive(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..666b79a51 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("-") @@ -78,7 +78,7 @@ class DomainReportView(auth_mixins.PermissionRequiredMixin, generic.TemplateView def get_context_data(self, *args, **kwargs): """Extra context data.""" - context = super(DomainReportView, self).get_context_data(*args, **kwargs) + context = super().get_context_data(*args, **kwargs) qset = self.get_queryset() stats = {"total": 0, "aligned": 0, "trusted": 0, "forwarded": 0, "failed": 0} aligned = {} diff --git a/modoboa/dnstools/lib.py b/modoboa/dnstools/lib.py index eb48b5068..67ef8e257 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): @@ -83,7 +83,7 @@ def check_spf_ip4(value): try: ipaddress.ip_network(parts[1], False) except ValueError: - raise DNSSyntaxError(_("Wrong IPv4 address format")) + raise DNSSyntaxError(_("Wrong IPv4 address format")) from None def check_spf_ip6(value): @@ -94,7 +94,7 @@ def check_spf_ip6(value): try: ipaddress.ip_network(value, False) except ValueError: - raise DNSSyntaxError(_("Wrong IPv6 address format")) + raise DNSSyntaxError(_("Wrong IPv6 address format")) from None def _check_domain_and_mask(value, mechanism): @@ -161,15 +161,13 @@ def check_spf_syntax(record): for part in parts: if part == "": continue - qualifier = None if part[0] in ["+", "-", "~", "?"]: - qualifier = part[0] part = part[1:] if part == "all": 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: @@ -231,7 +229,7 @@ def check_dmarc_tag(tag, value): try: value = int(value) except ValueError: - raise DNSSyntaxError(error + _(" not an integer")) + raise DNSSyntaxError(error + _(" not an integer")) from None if "min_value" in tdef and value < tdef["min_value"]: raise DNSSyntaxError(error + _(" less than {}").format(tdef["min_value"])) if "max_value" in tdef and value > tdef["max_value"]: 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/dnstools/tests.py b/modoboa/dnstools/tests.py index a3e57430d..1b2bf7411 100644 --- a/modoboa/dnstools/tests.py +++ b/modoboa/dnstools/tests.py @@ -133,7 +133,7 @@ class ViewsTestCase(ModoTestCase): @classmethod def setUpTestData(cls): """Create some records.""" - super(ViewsTestCase, cls).setUpTestData() + super().setUpTestData() cls.spf_rec = factories.DNSRecordFactory( type="spf", value="v=SPF1 mx -all", is_valid=True, domain__name="test.com" ) diff --git a/modoboa/dnstools/views.py b/modoboa/dnstools/views.py index c8871c783..74472d9a3 100644 --- a/modoboa/dnstools/views.py +++ b/modoboa/dnstools/views.py @@ -16,7 +16,7 @@ class DomainAccessRequiredMixin(auth_mixins.AccessMixin): def dispatch(self, request, *args, **kwargs): if not request.user.can_access(self.get_object()): return self.handle_no_permission() - return super(DomainAccessRequiredMixin, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) class DNSRecordDetailView( @@ -29,7 +29,7 @@ class DNSRecordDetailView( def get_context_data(self, **kwargs): """Add extra variables.""" - context = super(DNSRecordDetailView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context.update( { "title": _("{} record of {}").format( @@ -59,7 +59,7 @@ class AutoConfigRecordsStatusView( def get_context_data(self, **kwargs): """Add extra data to context.""" - context = super(AutoConfigRecordsStatusView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context.update( {"title": _("Auto configuration records for {}").format(self.object.name)} ) @@ -76,6 +76,6 @@ class DomainDNSConfigurationView( def get_context_data(self, **kwargs): """Add extra variables.""" - context = super(DomainDNSConfigurationView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context.update({"title": _("DNS configuration for {}").format(self.object)}) return context 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..9e3129ad1 100644 --- a/modoboa/imap_migration/tests.py +++ b/modoboa/imap_migration/tests.py @@ -21,12 +21,12 @@ from . import factories from . import models -class DataMixin(object): +class DataMixin: """A mixin to provide test data.""" @classmethod def setUpTestData(cls): - super(DataMixin, cls).setUpTestData() + super().setUpTestData() admin_factories.populate_database() cls.mb = admin_models.Mailbox.objects.get(user__username="user@test.com") cls.migration = factories.MigrationFactory( @@ -110,7 +110,7 @@ class ManagementCommandTestCase(DataMixin, ModoTestCase): """Management command test cases.""" def setUp(self): - super(ManagementCommandTestCase, self).setUp() + super().setUp() self.workdir = tempfile.mkdtemp() def tearDown(self): diff --git a/modoboa/imap_migration/views.py b/modoboa/imap_migration/views.py index 334518a21..d6ac2468e 100644 --- a/modoboa/imap_migration/views.py +++ b/modoboa/imap_migration/views.py @@ -18,4 +18,4 @@ class IndexView( def get_context_data(self, **kwargs): """Set menu selection.""" kwargs.update({"selection": "imap_migrations"}) - return super(IndexView, self).get_context_data(**kwargs) + return super().get_context_data(**kwargs) diff --git a/modoboa/ldapsync/lib.py b/modoboa/ldapsync/lib.py index 441c3dbc8..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 @@ -79,7 +79,7 @@ def create_ldap_account(user, dn, conn): try: conn.add_s(dn, ldif) except ldap.LDAPError as e: - raise InternalError(_("Failed to create LDAP account: {}").format(e)) + raise InternalError(_("Failed to create LDAP account: {}").format(e)) from None def check_if_dn_exists(conn, dn): @@ -118,7 +118,7 @@ def update_ldap_account(user, config): try: conn.modify_s(dn, ldif) except ldap.LDAPError as e: - raise InternalError(_("Failed to update LDAP account: {}").format(e)) + raise InternalError(_("Failed to update LDAP account: {}").format(e)) from None def delete_ldap_account(user, config): @@ -131,14 +131,18 @@ def delete_ldap_account(user, config): try: conn.delete_s(dn) except ldap.LDAPError as e: - raise InternalError(_("Failed to delete LDAP account: {}").format(e)) + raise InternalError( + _("Failed to delete LDAP account: {}").format(e) + ) from None else: password = get_user_password(user, True) ldif = [(ldap.MOD_REPLACE, "userPassword", password)] try: conn.modify_s(dn, ldif) except ldap.LDAPError as e: - raise InternalError(_("Failed to disable LDAP account: {}").format(e)) + raise InternalError( + _("Failed to disable LDAP account: {}").format(e) + ) from None def find_user_groups(conn, config, dn, entry): @@ -148,13 +152,13 @@ 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])) result = conn.search_s(config["ldap_groups_search_base"], ldap.SCOPE_SUBTREE, flt) groups = [] - for dn, entry in result: + for dn, _entry in result: if not dn: continue groups.append(dn.split(",")[0].split("=")[1]) @@ -200,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 @@ -231,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"], ) @@ -255,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/connections.py b/modoboa/lib/connections.py index d830d509c..aabf460fa 100644 --- a/modoboa/lib/connections.py +++ b/modoboa/lib/connections.py @@ -9,7 +9,7 @@ class ConnectionsManager(type): """ def __init__(self, name, bases, ctx): - super(ConnectionsManager, self).__init__(name, bases, ctx) + super().__init__(name, bases, ctx) self.instances = {} def __call__(self, **kwargs): @@ -24,7 +24,7 @@ class ConnectionsManager(type): kwargs["password"] = decrypt(kwargs["password"]) if self.instances[key] is None: - self.instances[key] = super(ConnectionsManager, self).__call__(**kwargs) + self.instances[key] = super().__call__(**kwargs) else: self.instances[key].refresh(key, kwargs["password"]) return self.instances[key] diff --git a/modoboa/lib/cryptutils.py b/modoboa/lib/cryptutils.py index 19d3f004b..303aed64a 100644 --- a/modoboa/lib/cryptutils.py +++ b/modoboa/lib/cryptutils.py @@ -1,13 +1,8 @@ """Crypto related utilities.""" import base64 -import time from cryptography.fernet import Fernet -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.twofactor import InvalidToken -from cryptography.hazmat.primitives.twofactor.totp import TOTP -from cryptography.hazmat.primitives.hashes import SHA1 from django.conf import settings from django.utils.crypto import get_random_string diff --git a/modoboa/lib/db_utils.py b/modoboa/lib/db_utils.py index 6c3f0e575..c817394c8 100644 --- a/modoboa/lib/db_utils.py +++ b/modoboa/lib/db_utils.py @@ -19,7 +19,7 @@ def db_type(cname="default"): :return: a string or None """ if cname not in settings.DATABASES: - raise InternalError(_("Connection to database %s not configured" % cname)) + raise InternalError(_("Connection to database {} not configured".format(cname))) for t in ["postgres", "mysql", "sqlite"]: if settings.DATABASES[cname]["ENGINE"].find(t) != -1: return t diff --git a/modoboa/lib/email_utils.py b/modoboa/lib/email_utils.py index b216336bc..6c2501dfb 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 @@ -113,7 +113,7 @@ class Email: def body(self): if self._body is None: self._parse(self.msg) - self._body = getattr(self, "viewmail_%s" % self.dformat)() + self._body = getattr(self, f"viewmail_{self.dformat}")() return self._body @@ -215,7 +215,7 @@ class Email: # Duplicate Content-ID return - self._images[cid] = "data:%s;base64,%s" % ( + self._images[cid] = "data:{};base64,{}".format( content_type, "".join(msg.get_payload().splitlines(False)), ) @@ -263,7 +263,7 @@ class Email: contents = self.contents["plain"] else: contents = conditional_escape(contents) - return "%s" % contents + return f"
{contents}"
def viewmail_html(self, contents=None, **kwargs):
# contents and **kwargs parameters to maintain compatibility with
@@ -385,7 +385,7 @@ def set_email_headers(msg, subject, sender, rcpt):
msg["From"] = sender
msg["To"] = prepare_addresses(rcpt)
msg["Message-ID"] = make_msgid()
- msg["User-Agent"] = "Modoboa %s" % (
+ msg["User-Agent"] = "Modoboa {}".format(
pkg_resources.get_distribution("modoboa").version
)
msg["Date"] = formatdate(time.time(), True)
@@ -409,7 +409,7 @@ def _sendmail(sender, rcpt, msgstring, server="localhost", port=25):
s.sendmail(sender, [rcpt], msgstring)
s.quit()
except smtplib.SMTPException as e:
- return False, "SMTP error: %s" % str(e)
+ return False, f"SMTP error: {str(e)}"
return True, None
@@ -446,15 +446,12 @@ 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
-To: %s
-""" % (
- sender,
- rcpt,
- )
+ content = f"""From: {sender}
+To: {rcpt}
+"""
content += fp.read()
fp.close()
diff --git a/modoboa/lib/exceptions.py b/modoboa/lib/exceptions.py
index b1c603a44..1ddfb8ecd 100644
--- a/modoboa/lib/exceptions.py
+++ b/modoboa/lib/exceptions.py
@@ -4,6 +4,8 @@
"""
+from typing import Optional
+
from django.utils.translation import gettext as _
@@ -12,7 +14,7 @@ class ModoboaException(Exception):
Base class for Modoboa custom exceptions.
"""
- http_code = None
+ http_code: Optional[int] = None
def __init__(self, *args, **kwargs):
if "http_code" in kwargs:
@@ -80,5 +82,5 @@ class PermDeniedException(ModoboaException):
def __str__(self):
if self.msg:
- return _("Permission denied: %s" % self.msg)
+ return _("Permission denied: {}".format(self.msg))
return _("Permission denied")
diff --git a/modoboa/lib/fields.py b/modoboa/lib/fields.py
index 65fa08ac0..82e9a82f9 100644
--- a/modoboa/lib/fields.py
+++ b/modoboa/lib/fields.py
@@ -17,7 +17,7 @@ class DomainNameField(forms.fields.CharField):
def clean(self, value):
value = self.to_python(value).strip()
- return super(DomainNameField, self).clean(value)
+ return super().clean(value)
class UTF8EmailField(forms.fields.EmailField):
@@ -41,7 +41,7 @@ class DRFEmailFieldUTF8(serializers.CharField):
default_error_messages = {"invalid": gettext_lazy("Enter a valid email address.")}
def __init__(self, **kwargs):
- super(DRFEmailFieldUTF8, self).__init__(**kwargs)
+ super().__init__(**kwargs)
validator = validators.UTF8EmailValidator(
message=self.error_messages["invalid"]
)
@@ -54,7 +54,7 @@ class DRFEmailFieldUTF8AndEmptyUser(serializers.CharField):
default_error_messages = {"invalid": gettext_lazy("Enter a valid email address.")}
def __init__(self, **kwargs):
- super(DRFEmailFieldUTF8AndEmptyUser, self).__init__(**kwargs)
+ super().__init__(**kwargs)
validator = validators.UTF8AndEmptyUserEmailValidator(
message=self.error_messages["invalid"]
)
diff --git a/modoboa/lib/form_utils.py b/modoboa/lib/form_utils.py
index 0b6d3e750..9c645bcc6 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.
@@ -218,7 +218,7 @@ class DynamicForm(object):
:param string pattern: pattern used to find field instances
:param typ: a form field class
"""
- expr = re.compile(r"%s_\d+" % pattern)
+ expr = re.compile(rf"{pattern}_\d+")
values = []
for k, v in list(qdict.items()):
if k == pattern or expr.match(k):
@@ -234,7 +234,7 @@ class DynamicForm(object):
self.data = ndata
-class TabForms(object):
+class TabForms:
"""
Simple forms container.
@@ -258,7 +258,7 @@ class TabForms(object):
args.append(request.POST)
if instances is not None:
self.instances = instances
- mname = "check_%s" % fd["id"]
+ mname = "check_{}".format(fd["id"])
if hasattr(self, mname):
if not getattr(self, mname)(instances[fd["id"]]):
to_remove += [fd]
@@ -326,8 +326,7 @@ class TabForms(object):
return self.forward()
def forward(self):
- for form in self.forms:
- yield form
+ yield from self.forms
def extra_context(self, context):
""" "Provide additional information to template's context."""
@@ -387,7 +386,7 @@ class SeparatorField(Field):
def __init__(self, *args, **kwargs):
kwargs["required"] = False
- super(SeparatorField, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
class YesNoField(TypedChoiceField):
@@ -402,4 +401,4 @@ class YesNoField(TypedChoiceField):
"coerce": lambda x: x == "True",
}
)
- super(YesNoField, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
diff --git a/modoboa/lib/ldap_utils.py b/modoboa/lib/ldap_utils.py
index 8c0219c9f..2c14f432d 100644
--- a/modoboa/lib/ldap_utils.py
+++ b/modoboa/lib/ldap_utils.py
@@ -42,7 +42,7 @@ from modoboa.lib.exceptions import InternalError
from modoboa.parameters import tools as param_tools
-class LDAPAuthBackend(object):
+class LDAPAuthBackend:
"""LDAP authentication backend."""
def __init__(self):
@@ -112,7 +112,7 @@ class LDAPAuthBackend(object):
self.connect_to_server(user, password)
user_dn = self._find_user_dn(user)
if self.ldap_ad:
- newpassword = ('"%s"' % newpassword).encode("utf-16").lstrip("\377\376")
+ newpassword = (f'"{newpassword}"').encode("utf-16").lstrip("\377\376")
ldif = [
(
ldap.MOD_REPLACE,
@@ -123,4 +123,4 @@ class LDAPAuthBackend(object):
try:
self.conn.modify_s(force_str(user_dn), ldif)
except ldap.LDAPError as e:
- raise InternalError(_("Failed to update password: {}").format(e))
+ raise InternalError(_("Failed to update password: {}").format(e)) from None
diff --git a/modoboa/lib/migrations/0002_rename_parameters.py b/modoboa/lib/migrations/0002_rename_parameters.py
index 1127cd699..68adbbb9f 100644
--- a/modoboa/lib/migrations/0002_rename_parameters.py
+++ b/modoboa/lib/migrations/0002_rename_parameters.py
@@ -1,4 +1,4 @@
-from django.db import models, migrations
+from django.db import migrations
APPLICATIONS = [
("admin", "modoboa_admin"),
diff --git a/modoboa/lib/migrations/0003_rename_parameters.py b/modoboa/lib/migrations/0003_rename_parameters.py
index be11772ce..7baa563b2 100644
--- a/modoboa/lib/migrations/0003_rename_parameters.py
+++ b/modoboa/lib/migrations/0003_rename_parameters.py
@@ -1,4 +1,4 @@
-from django.db import models, migrations
+from django.db import migrations
APPS = (
("modoboa_admin", "admin"),
diff --git a/modoboa/lib/migrations/0004_auto_20151114_1409.py b/modoboa/lib/migrations/0004_auto_20151114_1409.py
index 2804d63ed..5fa5fb82b 100644
--- a/modoboa/lib/migrations/0004_auto_20151114_1409.py
+++ b/modoboa/lib/migrations/0004_auto_20151114_1409.py
@@ -1,4 +1,4 @@
-from django.db import models, migrations
+from django.db import migrations
def move_language_preference(apps, schema_editor):
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/singleton.py b/modoboa/lib/singleton.py
index e49acc309..c1447ced6 100644
--- a/modoboa/lib/singleton.py
+++ b/modoboa/lib/singleton.py
@@ -4,7 +4,7 @@ class _Singleton(type):
def __call__(self, *args, **kwargs):
if self not in self._instances:
- self._instances[self] = super(_Singleton, self).__call__(*args, **kwargs)
+ self._instances[self] = super().__call__(*args, **kwargs)
return self._instances[self]
diff --git a/modoboa/lib/sysutils.py b/modoboa/lib/sysutils.py
index ded7c0a55..f97c0a0f8 100644
--- a/modoboa/lib/sysutils.py
+++ b/modoboa/lib/sysutils.py
@@ -27,7 +27,7 @@ def exec_cmd(cmd, sudo_user=None, pinput=None, capture_output=True, **kwargs):
:return: return code, command output
"""
if sudo_user is not None:
- cmd = "sudo -u %s %s" % (sudo_user, cmd)
+ cmd = f"sudo -u {sudo_user} {cmd}"
kwargs["shell"] = True
if pinput is not None:
kwargs["stdin"] = subprocess.PIPE
@@ -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..69f37155f 100644
--- a/modoboa/lib/templatetags/form_tags.py
+++ b/modoboa/lib/templatetags/form_tags.py
@@ -20,7 +20,7 @@ def render_form(form, tpl=None):
ret = ""
for field in form:
- ret += "%s\n" % render_field(field)
+ ret += f"{render_field(field)}\n"
return mark_safe(ret)
@@ -42,7 +42,7 @@ def render_field(field, help_display_mode="tooltip", label_width="col-sm-4", **o
from modoboa.core.templatetags.core_tags import visirule
if isinstance(field.field, SeparatorField):
- return "