Merge pull request #1388 from fyfe/fix/spring-clean

spring clean
This commit is contained in:
Antoine Nguyen
2018-02-09 14:37:07 +01:00
committed by GitHub
201 changed files with 1704 additions and 1541 deletions
+30
View File
@@ -0,0 +1,30 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{css,js,py}]
charset = utf-8
indent_style = space
indent_size = 4
[Makefile]
indent_style = tab
[{.travis.yml,codecov.yml}]
indent_style = space
indent_size = 2
# Ignore paths
[/modoboa/bower_components/**]
charset = none
end_of_line = none
insert_final_newline = none
trim_trailing_whitespace = none
indent_style = none
indent_size = none
-1
View File
@@ -7,4 +7,3 @@ from modoboa.core.commands import handle_command_line
if __name__ == "__main__":
handle_command_line()
+63 -59
View File
@@ -11,82 +11,86 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
from __future__ import unicode_literals
import os
# import sys
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.intersphinx',
"sphinx.ext.intersphinx",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['.templates']
templates_path = [".templates"]
# The suffix of source filenames.
source_suffix = '.rst'
source_suffix = ".rst"
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
master_doc = "index"
# General information about the project.
project = u'Modoboa'
copyright = u'2018, Antoine Nguyen'
project = "Modoboa"
copyright = "2018, Antoine Nguyen" # NOQA:A001
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '1.10'
version = "1.10"
# The full version, including alpha/beta/rc tags.
release = '1.10.0'
release = "1.10.0"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['.build']
exclude_patterns = [".build"]
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
@@ -94,123 +98,123 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
on_rtd = os.environ.get("READTHEDOCS", None) == "True"
if on_rtd:
html_theme = 'default'
html_theme = "default"
else:
html_theme = 'nature'
html_theme = "nature"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_static_path = ["_static"]
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'Modoboadoc'
htmlhelp_basename = "Modoboadoc"
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'Modoboa.tex', u'Modoboa Documentation',
u'Antoine Nguyen', 'manual'),
("index", "Modoboa.tex", "Modoboa Documentation",
"Antoine Nguyen", "manual"),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# latex_show_urls = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
@@ -218,10 +222,10 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'modoboa', u'Modoboa Documentation',
[u'Antoine Nguyen'], 1)
("index", "modoboa", "Modoboa Documentation",
["Antoine Nguyen"], 1)
]
intersphinx_mapping = {
'amavis': ('http://modoboa-amavis.readthedocs.org/en/latest/', None)
"amavis": ("http://modoboa-amavis.readthedocs.org/en/latest/", None)
}
+1 -2
View File
@@ -4,8 +4,7 @@
from __future__ import unicode_literals
from pkg_resources import get_distribution, DistributionNotFound
from pkg_resources import DistributionNotFound, get_distribution
try:
__version__ = get_distribution(__name__).version
+2
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
default_app_config = "modoboa.admin.apps.AdminConfig"
+6 -7
View File
@@ -1,21 +1,20 @@
# -*- coding: utf-8 -*-
"""Admin API."""
from __future__ import unicode_literals
from django import http
from django.contrib.contenttypes.models import ContentType
from rest_framework import filters, status, viewsets
from rest_framework.decorators import detail_route, list_route
from rest_framework.exceptions import ParseError
from rest_framework.permissions import IsAuthenticated, DjangoModelPermissions
from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated
from rest_framework.response import Response
from modoboa.core import models as core_models
from . import models
from . import serializers
from . import models, serializers
class DomainViewSet(viewsets.ModelViewSet):
@@ -81,7 +80,7 @@ class AccountViewSet(viewsets.ModelViewSet):
user = self.request.user
ids = user.objectaccess_set \
.filter(content_type=ContentType.objects.get_for_model(user)) \
.values_list('object_id', flat=True)
.values_list("object_id", flat=True)
queryset = core_models.User.objects.filter(pk__in=ids)
domain = self.request.query_params.get("domain")
if domain:
@@ -139,7 +138,7 @@ class AliasViewSet(viewsets.ModelViewSet):
ids = (
user.objectaccess_set.filter(
content_type=ContentType.objects.get_for_model(models.Alias))
.values_list('object_id', flat=True)
.values_list("object_id", flat=True)
)
queryset = models.Alias.objects.filter(pk__in=ids)
domain = self.request.query_params.get("domain")
+4 -3
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Modoboa admin settings."""
from __future__ import unicode_literals
@@ -6,13 +8,12 @@ import os
from django import forms
from django.conf import settings
from django.utils.translation import ugettext as _, ugettext_lazy
from django.utils.encoding import force_text
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.lib.form_utils import YesNoField, SeparatorField
from modoboa.lib.form_utils import SeparatorField, YesNoField
from modoboa.lib.sysutils import exec_cmd
from modoboa.parameters import forms as param_forms
from . import constants
+3 -1
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""AppConfig for admin."""
from __future__ import unicode_literals
@@ -17,4 +19,4 @@ class AdminConfig(AppConfig):
def ready(self):
load_admin_settings()
from . import handlers
from . import handlers # NOQA:F401
+2 -1
View File
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
"""Admin constants."""
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
DNSBL_PROVIDERS = [
"aspews.ext.sorbs.net",
"b.barracudacentral.org",
+19 -17
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""
Fixtures factories.
"""
@@ -18,7 +20,7 @@ class DomainFactory(PermissionFactory):
model = models.Domain
django_get_or_create = ("name", )
type = "domain"
type = "domain" # NOQA:A003
quota = 0
default_mailbox_quota = 10
enabled = True
@@ -79,44 +81,44 @@ def populate_database():
"""
dom = DomainFactory(name="test.com", quota=50)
admin = UserFactory(
username="admin@test.com", groups=('DomainAdmins', ),
password='{PLAIN}toto'
username="admin@test.com", groups=("DomainAdmins", ),
password="{PLAIN}toto"
)
MailboxFactory(address='admin', domain=dom, user=admin)
MailboxFactory(address="admin", domain=dom, user=admin)
account = UserFactory.create(
username="user@test.com", groups=('SimpleUsers',),
username="user@test.com", groups=("SimpleUsers",),
)
MailboxFactory.create(address='user', domain=dom, user=account)
MailboxFactory.create(address="user", domain=dom, user=account)
al = AliasFactory.create(
address='forward@test.com', domain=dom
address="forward@test.com", domain=dom
)
AliasRecipientFactory.create(
address='user@external.com', alias=al)
address="user@external.com", alias=al)
al = AliasFactory.create(
address='alias@test.com', domain=dom
address="alias@test.com", domain=dom
)
mb = account.mailbox
AliasRecipientFactory.create(
address=mb.full_address, alias=al, r_mailbox=mb)
al = AliasFactory.create(
address='postmaster@test.com', domain=dom
address="postmaster@test.com", domain=dom
)
for address in ['toto@titi.com', 'test@truc.fr']:
for address in ["toto@titi.com", "test@truc.fr"]:
AliasRecipientFactory.create(address=address, alias=al)
dom.add_admin(admin)
dom2 = DomainFactory.create(name='test2.com', default_mailbox_quota=0)
dom2 = DomainFactory.create(name="test2.com", default_mailbox_quota=0)
admin = UserFactory.create(
username='admin@test2.com', groups=('DomainAdmins',),
password='{PLAIN}toto'
username="admin@test2.com", groups=("DomainAdmins",),
password="{PLAIN}toto"
)
MailboxFactory.create(address='admin', domain=dom2, user=admin)
MailboxFactory.create(address="admin", domain=dom2, user=admin)
u = UserFactory.create(
username='user@test2.com', groups=('SimpleUsers',)
username="user@test2.com", groups=("SimpleUsers",)
)
MailboxFactory.create(address='user', domain=dom2, user=u)
MailboxFactory.create(address="user", domain=dom2, user=u)
dom2.add_admin(admin)
+13 -11
View File
@@ -1,21 +1,23 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from .domain import (
DomainFormGeneral, DomainFormOptions, DomainForm, DomainWizard
)
from .account import (
AccountFormGeneral, AccountFormMail, AccountPermissionsForm,
AccountForm, AccountWizard
AccountForm, AccountFormGeneral, AccountFormMail, AccountPermissionsForm,
AccountWizard
)
from .alias import AliasForm
from .domain import (
DomainForm, DomainFormGeneral, DomainFormOptions, DomainWizard
)
from .export import ExportDataForm, ExportDomainsForm, ExportIdentitiesForm
from .forward import ForwardForm
from .import_ import ImportDataForm, ImportIdentitiesForm
from .export import ExportDataForm, ExportDomainsForm, ExportIdentitiesForm
__all__ = [
'DomainFormGeneral', 'DomainFormOptions', 'DomainForm',
'AccountFormGeneral', 'AccountFormMail', 'AccountPermissionsForm',
'AccountForm', 'AliasForm', 'ImportDataForm', 'ImportIdentitiesForm',
'ExportDataForm', 'ExportDomainsForm', 'ExportIdentitiesForm',
'ForwardForm', 'DomainWizard', 'AccountWizard'
"DomainFormGeneral", "DomainFormOptions", "DomainForm",
"AccountFormGeneral", "AccountFormMail", "AccountPermissionsForm",
"AccountForm", "AliasForm", "ImportDataForm", "ImportIdentitiesForm",
"ExportDataForm", "ExportDomainsForm", "ExportIdentitiesForm",
"ForwardForm", "DomainWizard", "AccountWizard"
]
+11 -14
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Forms related to accounts management."""
from __future__ import unicode_literals
@@ -7,17 +9,15 @@ from functools import reduce
from django import forms
from django.conf import settings
from django.contrib.auth import password_validation
from django.http import QueryDict
from django.urls import reverse
from django.utils.translation import ugettext as _, ugettext_lazy
from django.contrib.auth import password_validation
from modoboa.core import signals as core_signals
from modoboa.core.models import User
from modoboa.lib import exceptions as lib_exceptions, fields as lib_fields
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib import exceptions as lib_exceptions
from modoboa.lib import fields as lib_fields
from modoboa.lib.form_utils import (
DynamicForm, TabForms, WizardForm, WizardStep
)
@@ -25,10 +25,7 @@ from modoboa.lib.permissions import get_account_roles
from modoboa.lib.validators import validate_utf8_email
from modoboa.lib.web_utils import render_to_json_response
from modoboa.parameters import tools as param_tools
from .. import lib
from .. import models
from .. import signals
from .. import lib, models, signals
class AccountFormGeneral(forms.ModelForm):
@@ -527,9 +524,9 @@ class AccountForm(TabForms):
def extra_context(self, context):
account = self.instances["general"]
context.update({
'title': account.username,
'formid': 'accountform',
'action': reverse("admin:account_change",
"title": account.username,
"formid": "accountform",
"action": reverse("admin:account_change",
args=[account.id]),
})
@@ -604,9 +601,9 @@ class AccountWizard(WizardForm):
def extra_context(self, context):
context.update({
'title': _("New account"),
'action': reverse("admin:account_add"),
'formid': 'newaccount_form'
"title": _("New account"),
"action": reverse("admin:account_add"),
"formid": "newaccount_form"
})
def done(self):
+5 -7
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Forms related to aliases management."""
from __future__ import unicode_literals
@@ -9,14 +11,10 @@ from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.core import signals as core_signals
from modoboa.lib import exceptions as lib_exceptions
from modoboa.lib import exceptions as lib_exceptions, fields as lib_fields
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib import fields as lib_fields
from modoboa.lib.form_utils import (
DynamicForm
)
from ..models import Domain, Alias
from modoboa.lib.form_utils import DynamicForm
from ..models import Alias, Domain
class AliasForm(forms.ModelForm, DynamicForm):
+9 -13
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Forms related to domains management."""
from __future__ import unicode_literals
@@ -5,34 +7,28 @@ from __future__ import unicode_literals
from functools import reduce
from django import forms
from django.urls import reverse
from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_lazy
from django.urls import reverse
from django.utils.encoding import force_text
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.core import signals as core_signals
from modoboa.core.models import User
from modoboa.lib.exceptions import Conflict
from modoboa.lib.fields import DomainNameField
from modoboa.lib.form_utils import (
YesNoField, WizardForm, WizardStep, DynamicForm, TabForms
DynamicForm, TabForms, WizardForm, WizardStep, YesNoField
)
from modoboa.lib.web_utils import render_to_json_response
from modoboa.parameters import tools as param_tools
from .. import constants
from .. import lib
from .. import signals
from ..models import (
Domain, DomainAlias, Mailbox, Alias
)
from .. import constants, lib, signals
from ..models import Alias, Domain, DomainAlias, Mailbox
class DomainFormGeneral(forms.ModelForm, DynamicForm):
"""A form to create/edit a domain."""
type = forms.ChoiceField(
type = forms.ChoiceField( # NOQA:A003
label=ugettext_lazy("Type"),
)
aliases = DomainNameField(
@@ -82,7 +78,7 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
"""Check unicity and more."""
name = self.cleaned_data["name"]
label = lib.check_if_domain_exists(
name, [(DomainAlias, _('domain alias'))])
name, [(DomainAlias, _("domain alias"))])
if label is not None:
raise forms.ValidationError(
_("A %s with this name already exists") % force_text(label))
+2
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django import forms
+2
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Forms related to forwards management."""
from __future__ import unicode_literals
+2
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Forms related to import operations."""
from __future__ import unicode_literals
+8 -14
View File
@@ -1,27 +1,21 @@
# -*- coding: utf-8 -*-
"""Django signal handlers for admin."""
from __future__ import unicode_literals
from django.urls import reverse
from django.contrib.contenttypes.models import ContentType
from django.db.models import signals
from django.dispatch import receiver
from django.urls import reverse
from django.utils.translation import ugettext as _
from django.contrib.contenttypes.models import ContentType
from modoboa.core import models as core_models
from modoboa.core import signals as core_signals
from modoboa.core import models as core_models, signals as core_signals
from modoboa.lib import exceptions, permissions, signals as lib_signals
from modoboa.lib.cryptutils import encrypt
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib import exceptions
from modoboa.lib import permissions
from modoboa.lib import signals as lib_signals
from modoboa.parameters import tools as param_tools
from . import signals as admin_signals
from . import lib
from . import models
from . import postfix_maps
from . import lib, models, postfix_maps, signals as admin_signals
@receiver(signals.post_save, sender=models.Domain)
@@ -161,7 +155,7 @@ def account_auto_created(sender, user, **kwargs):
if not param_tools.get_global_parameter("auto_create_domain_and_mailbox"):
return
localpart, domname = split_mailbox(user.username)
if user.role != 'SimpleUsers' and domname is None:
if user.role != "SimpleUsers" and domname is None:
return
sadmins = core_models.User.objects.filter(is_superuser=True)
try:
+10 -12
View File
@@ -4,30 +4,28 @@
from __future__ import unicode_literals
from functools import wraps
import ipaddress
from itertools import chain
import logging
import random
import socket
import string
from functools import wraps
from itertools import chain
from dns.name import IDNA_2008_UTS_46
import dns.resolver
from dns.name import IDNA_2008_UTS_46
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.utils.encoding import smart_text
from django.utils.translation import ugettext as _
from modoboa.core.models import User
from modoboa.core import signals as core_signals
from modoboa.core.models import User
from modoboa.lib.exceptions import PermDeniedException
from modoboa.parameters import tools as param_tools
from .models import Domain, DomainAlias, Alias
from . import signals
from .models import Alias, Domain, DomainAlias
def needs_mailbox():
@@ -59,7 +57,7 @@ def get_identities(user, searchquery=None, idtfilter=None, grpfilter=None):
if idtfilter is None or not idtfilter or idtfilter == "account":
ids = user.objectaccess_set \
.filter(content_type=ContentType.objects.get_for_model(user)) \
.values_list('object_id', flat=True)
.values_list("object_id", flat=True)
q = Q(pk__in=ids)
if searchquery is not None:
q &= Q(username__icontains=searchquery) \
@@ -76,7 +74,7 @@ def get_identities(user, searchquery=None, idtfilter=None, grpfilter=None):
or (idtfilter in ["alias", "forward", "dlist"]):
alct = ContentType.objects.get_for_model(Alias)
ids = user.objectaccess_set.filter(content_type=alct) \
.values_list('object_id', flat=True)
.values_list("object_id", flat=True)
q = Q(pk__in=ids, internal=False)
if searchquery is not None:
q &= (
@@ -187,8 +185,8 @@ def get_domain_mx_list(domain):
logger.error(_("No working name servers found"), exc_info=e)
except dns.resolver.Timeout as e:
logger.warning(
_("DNS resolution timeout, unable to query %s at the moment") % domain,
exc_info=e)
_("DNS resolution timeout, unable to query %s at the moment") %
domain, exc_info=e)
else:
for dns_answer in dns_answers:
try:
@@ -225,7 +223,7 @@ def domain_has_authorized_mx(name):
valid_mxs = [ipaddress.ip_network(smart_text(v.strip()))
for v in valid_mxs.split() if v.strip()]
domain_mxs = get_domain_mx_list(name)
for mx_addr, mx_ip_addr in domain_mxs: # noqa:B007
for _mx_addr, mx_ip_addr in domain_mxs:
for subnet in valid_mxs:
if mx_ip_addr in subnet:
return True
-1
View File
@@ -1 +0,0 @@
from __future__ import unicode_literals
@@ -1 +0,0 @@
from __future__ import unicode_literals
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
"""A management command to apply mailbox operations."""
from __future__ import unicode_literals, print_function
from __future__ import print_function, unicode_literals
import logging
import os
@@ -8,10 +10,9 @@ import shutil
from django.core.management.base import BaseCommand
from modoboa.parameters import tools as param_tools
from modoboa.lib.sysutils import exec_cmd
from modoboa.lib.exceptions import InternalError
from modoboa.lib.sysutils import exec_cmd
from modoboa.parameters import tools as param_tools
from ...app_settings import load_admin_settings
from ...models import MailboxOperation
@@ -25,7 +26,7 @@ class OperationError(Exception):
class Command(BaseCommand):
"""Command definition"""
help = "Handles rename and delete operations on mailboxes"
help = "Handles rename and delete operations on mailboxes" # NOQA:A003
def __init__(self, *args, **kwargs):
super(Command, self).__init__(*args, **kwargs)
@@ -80,7 +81,7 @@ class Command(BaseCommand):
)
if not code:
return False
with open(path, 'w') as fp:
with open(path, "w") as fp:
print(os.getpid(), file=fp)
return True
+4 -2
View File
@@ -1,13 +1,15 @@
# -*- coding: utf-8 -*-
"""Modoboa main management command."""
from __future__ import unicode_literals
from subcommand.base import SubcommandCommand
from .subcommands._mx import CheckMXRecords
from .subcommands._export import ExportCommand
from .subcommands._import import ImportCommand
from .subcommands._manage_dkim_keys import ManageDKIMKeys
from .subcommands._mx import CheckMXRecords
from .subcommands._repair import Repair
@@ -17,7 +19,7 @@ class Command(SubcommandCommand):
$ python manage.py modo
"""
help = "Modoboa top management command."
help = "Modoboa top management command." # NOQA:A003
subcommands = {
"export": ExportCommand,
@@ -1 +0,0 @@
from __future__ import unicode_literals
@@ -1,21 +1,24 @@
# -*- coding: utf-8 -*-
"""Django management command to export admin objects."""
from __future__ import unicode_literals
from backports import csv
import sys
from backports import csv
from django.core.management.base import BaseCommand
from modoboa.core.models import User
from modoboa.core.extensions import exts_pool
from modoboa.core.models import User
from .... import models
class ExportCommand(BaseCommand):
"""Command class."""
help = "Export domains or identities using CSV format" # noqa:A003
help = "Export domains or identities using CSV format" # NOQA:A003
def add_arguments(self, parser):
"""Add arguments to command."""
@@ -46,12 +49,12 @@ class ExportCommand(BaseCommand):
dumped_aliases += [alias.pk]
qset = (
models.Alias.objects.exclude(pk__in=dumped_aliases)
.prefetch_related('aliasrecipient_set')
.prefetch_related("aliasrecipient_set")
)
for alias in qset:
alias.to_csv(self.csvwriter)
def handle(self, *args, **options):
exts_pool.load_all()
self.csvwriter = csv.writer(sys.stdout, delimiter=options['sepchar'])
self.csvwriter = csv.writer(sys.stdout, delimiter=options["sepchar"])
getattr(self, "export_{}".format(options["objtype"]))()
@@ -1,19 +1,20 @@
# -*- coding: utf-8 -*-
"""Django management command to import admin objects."""
from __future__ import unicode_literals
from backports import csv
import io
import os
import progressbar
from backports import csv
from django.core.management.base import BaseCommand, CommandError
from modoboa.core import models as core_models
from modoboa.core.extensions import exts_pool
from modoboa.lib.exceptions import Conflict
from .... import signals
@@ -21,7 +22,7 @@ class ImportCommand(BaseCommand):
"""Command class."""
args = "csvfile"
help = "Import identities from a csv file" # noqa:A003
help = "Import identities from a csv file" # NOQA:A003
def add_arguments(self, parser):
"""Add extra arguments to command."""
@@ -30,7 +31,7 @@ class ImportCommand(BaseCommand):
help="Separator used in file.")
parser.add_argument(
"--continue-if-exists", action="store_true",
dest='continue_if_exists', default=False,
dest="continue_if_exists", default=False,
help="Continue even if an entry already exists.")
parser.add_argument(
"--crypt-password", action="store_true",
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Management command to create DKIM keys."""
from __future__ import print_function, unicode_literals
@@ -9,7 +11,6 @@ from django.utils.encoding import smart_text
from modoboa.lib import sysutils
from modoboa.parameters import tools as param_tools
from .... import models
@@ -18,15 +18,14 @@ from django.utils.encoding import smart_text
from django.utils.functional import cached_property
from django.utils.translation import ugettext as _
from modoboa.admin import constants
from modoboa.admin import models
from modoboa.admin import constants, models
from modoboa.parameters import tools as param_tools
class CheckMXRecords(BaseCommand):
"""Command class."""
help = "Check defined domains." # noqa:A003
help = "Check defined domains." # NOQA:A003
@cached_property
def providers(self):
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Management command to check and fix known problems."""
from __future__ import print_function, unicode_literals
@@ -6,11 +8,9 @@ from django.conf import settings
from django.core.management.base import BaseCommand
from django.utils.encoding import smart_str
from modoboa.lib.permissions import grant_access_to_object
from modoboa.lib.permissions import get_object_owner
from modoboa.core.models import User
from modoboa.admin import models
from modoboa.core.models import User
from modoboa.lib.permissions import get_object_owner, grant_access_to_object
known_problems = []
@@ -97,7 +97,7 @@ def sometimes_mailbox_have_no_alias(**options):
class Repair(BaseCommand):
"""Command class."""
help = "Check and fix known problems."
help = "Check and fix known problems." # NOQA:A003
def add_arguments(self, parser):
"""Add extra arguments to command."""
@@ -113,7 +113,10 @@ class Repair(BaseCommand):
# Load known problems from extensions.
for ext in settings.MODOBOA_APPS:
try:
__import__(ext, locals(), globals(), [smart_str("known_problems")])
__import__(
ext, locals(), globals(),
[smart_str("known_problems")]
)
except ImportError:
pass
for func in known_problems:
+4 -2
View File
@@ -1,13 +1,15 @@
# -*- coding: utf-8 -*-
"""Admin models."""
from __future__ import unicode_literals
from .alias import Alias, AliasRecipient
from .base import AdminObject
from .domain import Domain
from .domain_alias import DomainAlias
from .mailbox import Mailbox, Quota, MailboxOperation, SenderAddress
from .mailbox import Mailbox, MailboxOperation, Quota, SenderAddress
from .mxrecord import DNSBLResult, MXRecord
from .alias import Alias, AliasRecipient
__all__ = [
"AdminObject",
+10 -9
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Models related to aliases management."""
from __future__ import unicode_literals
@@ -5,25 +7,24 @@ from __future__ import unicode_literals
import hashlib
import random
from django.urls import reverse
from reversion import revisions as reversion
from django.db import models
from django.urls import reverse
from django.utils.encoding import (
python_2_unicode_compatible, smart_text, force_str, force_bytes
force_bytes, force_str, python_2_unicode_compatible, smart_text
)
from django.utils.translation import ugettext as _, ugettext_lazy
from reversion import revisions as reversion
from modoboa.core import signals as core_signals
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib.exceptions import (
PermDeniedException, BadRequest, Conflict, NotFound
BadRequest, Conflict, NotFound, PermDeniedException
)
from .. import signals
from .base import AdminObject
from .domain import Domain
from .mailbox import Mailbox
from .. import signals
@python_2_unicode_compatible
@@ -48,7 +49,7 @@ class Alias(AdminObject):
ugettext_lazy("Description"), blank=True)
expire_at = models.DateTimeField(
ugettext_lazy("Expire at"), blank=True, null=True)
_objectname = 'MailboxAlias'
_objectname = "MailboxAlias"
class Meta:
permissions = (
@@ -139,7 +140,7 @@ class Alias(AdminObject):
domain=domain, address=local_part).first()
if rcpt is None:
rcpt = Alias.objects.filter(
address='%s@%s' % (local_part, domname)
address="%s@%s" % (local_part, domname)
).first()
if rcpt is None:
raise NotFound(
+3 -2
View File
@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
"""Base admin models."""
from __future__ import unicode_literals
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
from django.utils import timezone
from django.contrib.contenttypes.fields import GenericRelation
from modoboa.core import models as core_models
from modoboa.lib.permissions import (
grant_access_to_object, ungrant_access_to_object
+12 -11
View File
@@ -1,33 +1,34 @@
# -*- coding: utf-8 -*-
"""Models related to domains management."""
from __future__ import unicode_literals
import datetime
from reversion import revisions as reversion
from django.db import models
from django.utils import timezone
from django.utils.encoding import (
python_2_unicode_compatible, smart_text, force_text
force_text, python_2_unicode_compatible, smart_text
)
from django.utils.functional import cached_property
from django.utils.translation import ugettext as _, ugettext_lazy
from reversion import revisions as reversion
from modoboa.core import signals as core_signals
from modoboa.core.models import User
from modoboa.lib.exceptions import BadRequest, Conflict
from modoboa.parameters import tools as param_tools
from .base import AdminObject
from .. import constants
from .base import AdminObject
@python_2_unicode_compatible
class Domain(AdminObject):
"""Mail domain."""
name = models.CharField(ugettext_lazy('name'), max_length=100, unique=True,
name = models.CharField(ugettext_lazy("name"), max_length=100, unique=True,
help_text=ugettext_lazy("The domain name"))
quota = models.PositiveIntegerField(
default=0,
@@ -45,11 +46,11 @@ class Domain(AdminObject):
)
)
enabled = models.BooleanField(
ugettext_lazy('enabled'),
ugettext_lazy("enabled"),
help_text=ugettext_lazy("Check to activate this domain"),
default=True
)
type = models.CharField(default="domain", max_length=20)
type = models.CharField(default="domain", max_length=20) # NOQA:A003
enable_dns_checks = models.BooleanField(
ugettext_lazy("Enable DNS checks"), default=True,
help_text=ugettext_lazy("Check to enable DNS checks for this domain")
@@ -245,9 +246,9 @@ class Domain(AdminObject):
def save(self, *args, **kwargs):
"""Store current data if domain is renamed."""
if self.oldname != self.name:
self.old_mail_homes = (
dict((mb.id, mb.mail_home) for mb in self.mailbox_set.all())
)
self.old_mail_homes = {
mb.id: mb.mail_home for mb in self.mailbox_set.all()
}
super(Domain, self).save(*args, **kwargs)
def delete(self, fromuser, keepdir=False):
+11 -10
View File
@@ -1,19 +1,18 @@
# -*- coding: utf-8 -*-
"""Models related to domain aliases management."""
from __future__ import unicode_literals
from reversion import revisions as reversion
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
from django.utils.encoding import python_2_unicode_compatible, smart_text
from django.utils.translation import ugettext as _, ugettext_lazy
from django.contrib.contenttypes.fields import GenericRelation
from reversion import revisions as reversion
from modoboa.core import models as core_models
from modoboa.core import signals as core_signals
from modoboa.core import models as core_models, signals as core_signals
from modoboa.lib.exceptions import BadRequest, Conflict
from .base import AdminObject
from .domain import Domain
@@ -39,12 +38,12 @@ class DomainAlias(AdminObject):
name = models.CharField(ugettext_lazy("name"), max_length=100, unique=True,
help_text=ugettext_lazy("The alias name"))
target = models.ForeignKey(
Domain, verbose_name=ugettext_lazy('target'),
Domain, verbose_name=ugettext_lazy("target"),
help_text=ugettext_lazy("The domain this alias points to"),
on_delete=models.CASCADE
)
enabled = models.BooleanField(
ugettext_lazy('enabled'),
ugettext_lazy("enabled"),
help_text=ugettext_lazy("Check to activate this alias"),
default=True
)
@@ -65,7 +64,8 @@ class DomainAlias(AdminObject):
def from_csv(self, user, row):
"""Create a domain alias from a CSV row
Expected format: ["domainalias", domain alias name, targeted domain, enabled]
Expected format: ["domainalias", domain alias name, targeted domain,
enabled]
:param user: a ``User`` object
:param row: a list containing the alias definition
@@ -94,4 +94,5 @@ class DomainAlias(AdminObject):
csvwriter.writerow(["domainalias", self.name,
self.target.name, self.enabled])
reversion.register(DomainAlias)
+15 -14
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Models related to mailboxes management."""
from __future__ import unicode_literals
@@ -5,6 +7,8 @@ from __future__ import unicode_literals
import os
import pwd
from reversion import revisions as reversion
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import Q
@@ -12,14 +16,11 @@ from django.db.models.manager import Manager
from django.utils.encoding import python_2_unicode_compatible, smart_text
from django.utils.translation import ugettext as _, ugettext_lazy
from reversion import revisions as reversion
from modoboa.core.models import User
from modoboa.lib import exceptions as lib_exceptions
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib.sysutils import exec_cmd
from modoboa.parameters import tools as param_tools
from .base import AdminObject
from .domain import Domain
@@ -42,7 +43,7 @@ class Quota(models.Model):
"""Keeps track of Mailbox current quota."""
username = models.EmailField(primary_key=True, max_length=254)
bytes = models.BigIntegerField(default=0)
bytes = models.BigIntegerField(default=0) # NOQA:A003
messages = models.IntegerField(default=0)
objects = QuotaManager()
@@ -66,9 +67,9 @@ class MailboxManager(Manager):
"""
qf = None
if squery is not None:
if '@' in squery:
parts = squery.split('@')
addrfilter = '@'.join(parts[:-1])
if "@" in squery:
parts = squery.split("@")
addrfilter = "@".join(parts[:-1])
domfilter = parts[-1]
qf = (
Q(address__contains=addrfilter) &
@@ -81,7 +82,7 @@ class MailboxManager(Manager):
)
ids = admin.objectaccess_set \
.filter(content_type=ContentType.objects.get_for_model(Mailbox)) \
.values_list('object_id', flat=True)
.values_list("object_id", flat=True)
if qf is not None:
qf = Q(pk__in=ids) & qf
else:
@@ -94,7 +95,7 @@ class Mailbox(AdminObject):
"""User mailbox."""
address = models.CharField(
ugettext_lazy('address'), max_length=252,
ugettext_lazy("address"), max_length=252,
help_text=ugettext_lazy(
"Mailbox address (without the @domain.tld part)")
)
@@ -375,8 +376,8 @@ class MailboxOperation(models.Model):
mailbox = models.ForeignKey(Mailbox, blank=True, null=True,
on_delete=models.CASCADE)
type = models.CharField(
max_length=20, choices=(('rename', 'rename'), ('delete', 'delete'))
type = models.CharField( # NOQA:A003
max_length=20, choices=(("rename", "rename"), ("delete", "delete"))
)
argument = models.TextField()
@@ -384,6 +385,6 @@ class MailboxOperation(models.Model):
app_label = "admin"
def __str__(self):
if self.type == 'rename':
return 'Rename %s -> %s' % (self.argument, self.mailbox.mail_home)
return 'Delete %s' % self.argument
if self.type == "rename":
return "Rename %s -> %s" % (self.argument, self.mailbox.mail_home)
return "Delete %s" % self.argument
+2
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""MX records storage."""
from __future__ import unicode_literals
+6 -4
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Map file definitions for postfix."""
from __future__ import unicode_literals
@@ -7,7 +9,7 @@ class DomainsMap(object):
"""Map to list all domains."""
filename = 'sql-domains.cf'
filename = "sql-domains.cf"
mysql = (
"SELECT name FROM admin_domain "
"WHERE name='%s' AND type='domain' AND enabled=1"
@@ -26,7 +28,7 @@ class DomainsAliasesMap(object):
"""Map to list all domain aliases."""
filename = 'sql-domain-aliases.cf'
filename = "sql-domain-aliases.cf"
mysql = (
"SELECT dom.name FROM admin_domain dom "
"INNER JOIN admin_domainalias domal ON dom.id=domal.target_id "
@@ -48,7 +50,7 @@ class AliasesMap(object):
"""A map to list all mailbox aliases."""
filename = 'sql-aliases.cf'
filename = "sql-aliases.cf"
mysql = (
"SELECT alr.address FROM modoboa_admin_aliasrecipient AS alr "
"INNER JOIN admin_alias AS al ON alr.alias_id=al.id "
@@ -73,7 +75,7 @@ class MaintainMap(object):
"""Map files to list non available mailboxes."""
filename = 'sql-maintain.cf'
filename = "sql-maintain.cf"
mysql = (
"SELECT '450 Requested mail action not taken: mailbox unavailable' "
"FROM admin_mailbox mb INNER JOIN admin_domain dom "
+14 -13
View File
@@ -1,27 +1,28 @@
# -*- coding: utf-8 -*-
"""Admin serializers."""
from __future__ import unicode_literals
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
from django.utils.translation import ugettext as _, ugettext_lazy
from django.utils.encoding import force_text
from django.contrib.auth import password_validation
from django.utils.translation import ugettext as _, ugettext_lazy
from rest_framework import serializers
from modoboa.admin import models as admin_models
from modoboa.core import constants as core_constants
from modoboa.core import models as core_models
from modoboa.core import signals as core_signals
from modoboa.lib import exceptions as lib_exceptions
from modoboa.lib import permissions, email_utils, fields as lib_fields
from modoboa.core import (
constants as core_constants, models as core_models, signals as core_signals
)
from modoboa.lib import (
email_utils, exceptions as lib_exceptions, fields as lib_fields,
permissions
)
from modoboa.parameters import tools as param_tools
from . import lib
from . import models
from . import lib, models
class DomainSerializer(serializers.ModelSerializer):
@@ -62,8 +63,8 @@ class DomainSerializer(serializers.ModelSerializer):
domain = models.Domain(**validated_data)
creator = self.context["request"].user
core_signals.can_create_object.send(
sender=self.__class__, context=creator,
klass=models.Domain, instance=domain)
sender=self.__class__, context=creator,
klass=models.Domain, instance=domain)
domain.save(creator=creator)
return domain
+2
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Modoboa admin signals."""
from __future__ import unicode_literals
-1
View File
@@ -1 +0,0 @@
from __future__ import unicode_literals
+10 -9
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Admin extension tags."""
from __future__ import unicode_literals
@@ -5,15 +7,14 @@ from __future__ import unicode_literals
from functools import reduce
from django import template
from django.urls import reverse
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.core import signals as core_signals
from modoboa.lib.templatetags.lib_tags import render_link
from modoboa.lib.web_utils import render_actions
from .. import signals
register = template.Library()
@@ -75,7 +76,7 @@ def domains_menu(selection, user, ajax_mode=True):
"modalcb": "admin.exportform_cb"}
]
return render_to_string('common/menulist.html', {
return render_to_string("common/menulist.html", {
"entries": entries,
"selection": selection,
"user": user
@@ -123,7 +124,7 @@ def identities_menu(user, selection=None, ajax_mode=True):
"modalcb": "admin.exportform_cb"}
]
return render_to_string('common/menulist.html', {
return render_to_string("common/menulist.html", {
"entries": entries,
"user": user
})
@@ -157,7 +158,7 @@ def domain_actions(user, domain):
responses = signals.extra_domain_actions.send(
sender=None, user=user, domain=domain)
for receiver, response in responses:
for _receiver, response in responses:
if response:
actions += response
@@ -229,15 +230,15 @@ def domain_aliases(domain):
:rtype: str
"""
if not domain.aliases.count():
return '---'
res = ''
return "---"
res = ""
for alias in domain.aliases.all():
res += '%s<br/>' % alias.name
res += "%s<br/>" % alias.name
return mark_safe(res)
@register.simple_tag
def identity_modify_link(identity, active_tab='default'):
def identity_modify_link(identity, active_tab="default"):
"""Return the appropriate modification link.
According to the identity type, a specific modification link (URL)
-1
View File
@@ -1 +0,0 @@
from __future__ import unicode_literals
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
#!/bin/sh
echo "2.12"
+44 -47
View File
@@ -1,34 +1,31 @@
# coding: utf-8
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from unittest import skipIf
from django.urls import reverse
from django.test import override_settings
from django.urls import reverse
from modoboa.core import factories as core_factories
from modoboa.core.tests import test_ldap
from modoboa.core.models import User
from modoboa.lib.tests import ModoTestCase
from modoboa.lib.tests import NO_LDAP
from modoboa.core.tests import test_ldap
from modoboa.lib.tests import NO_LDAP, ModoTestCase
from modoboa.limits import utils as limits_utils
from .. import factories
from .. import models
from .. import factories, models
class AuthenticationTestCase(ModoTestCase):
"""Check authentication."""
@classmethod
def setUpTestData(cls):
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
super(AuthenticationTestCase, cls).setUpTestData()
cls.mb = factories.MailboxFactory(
domain__name="test.com", address="user",
user__username="user@test.com",
user__groups=('SimpleUsers',)
user__groups=("SimpleUsers",)
)
def test_authentication_unicode(self):
@@ -46,18 +43,18 @@ class AuthenticationTestCase(ModoTestCase):
class AccountTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls):
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
super(AccountTestCase, cls).setUpTestData()
factories.populate_database()
def test_crud(self):
values = dict(
username="tester@test.com", first_name="Tester", last_name="Toto",
password1="Toto1234", password2="Toto1234", role="SimpleUsers",
quota_act=True,
is_active=True, email="tester@test.com", stepid='step2'
)
values = {
"username": "tester@test.com", "first_name": "Tester",
"last_name": "Toto", "password1": "Toto1234",
"password2": "Toto1234", "role": "SimpleUsers", "quota_act": True,
"is_active": True, "email": "tester@test.com", "stepid": "step2"
}
self.ajax_post(reverse("admin:account_add"), values)
account = User.objects.get(username="tester@test.com")
@@ -344,12 +341,12 @@ class AccountTestCase(ModoTestCase):
def test_utf8_username(self):
"""Create an account with non-ASCII characters."""
values = dict(
username="téster@test.com", first_name="Tester", last_name="Toto",
password1="Toto1234", password2="Toto1234", role="SimpleUsers",
quota_act=True,
is_active=True, email="téster@test.com", stepid="step2"
)
values = {
"username": "téster@test.com", "first_name": "Tester",
"last_name": "Toto", "password1": "Toto1234",
"password2": "Toto1234", "role": "SimpleUsers", "quota_act": True,
"is_active": True, "email": "téster@test.com", "stepid": "step2"
}
self.ajax_post(reverse("admin:account_add"), values)
def _set_quota(self, email, value, expected_status=200):
@@ -459,8 +456,8 @@ class AccountTestCase(ModoTestCase):
@skipIf(NO_LDAP, "No ldap module installed")
@override_settings(AUTHENTICATION_BACKENDS=(
'modoboa.lib.authbackends.LDAPBackend',
'django.contrib.auth.backends.ModelBackend',
"modoboa.lib.authbackends.LDAPBackend",
"django.contrib.auth.backends.ModelBackend",
))
class LDAPAccountTestCase(test_ldap.LDAPTestCaseMixin, ModoTestCase):
"""Check LDAP related code."""
@@ -481,11 +478,11 @@ class LDAPAccountTestCase(test_ldap.LDAPTestCaseMixin, ModoTestCase):
class PermissionsTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls):
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
super(PermissionsTestCase, cls).setUpTestData()
parameters = {}
for name, tpl in limits_utils.get_user_limit_templates():
for name, _definition in limits_utils.get_user_limit_templates():
parameters["deflt_user_{0}_limit".format(name)] = 2
cls.localconfig.parameters.set_values(parameters, app="limits")
cls.localconfig.save()
@@ -597,7 +594,7 @@ class PermissionsTestCase(ModoTestCase):
response = self.client.get(
reverse("admin:account_change", args=[self.user.id]),
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
HTTP_X_REQUESTED_WITH="XMLHttpRequest")
self.assertNotEqual(response["Content-Type"], "application/json")
def test_domainadmin_deletes_superadmin(self):
@@ -607,12 +604,12 @@ class PermissionsTestCase(ModoTestCase):
for the associated domain, this domain admin must not be able
to access the super admin.
"""
values = dict(
username="superadmin2@test.com", first_name="Super",
last_name="Admin", password1="Toto1234", password2="Toto1234",
role="SuperAdmins", is_active=True,
email="superadmin2@test.com", stepid='step2'
)
values = {
"username": "superadmin2@test.com", "first_name": "Super",
"last_name": "Admin", "password1": "Toto1234",
"password2": "Toto1234", "role": "SuperAdmins", "is_active": True,
"email": "superadmin2@test.com", "stepid": "step2"
}
self.ajax_post(
reverse("admin:account_add"),
values
@@ -627,22 +624,22 @@ class PermissionsTestCase(ModoTestCase):
def test_domainadmin_dlist_local_domain_not_owned(self):
"""Check if a domain admin can use a local mailbox he can't
access as a recipient in a distribution list"""
values = dict(
address="all@test.com",
recipients="user@test.com",
recipients_1="user@test2.com",
enabled=True
)
values = {
"address": "all@test.com",
"recipients": "user@test.com",
"recipients_1": "user@test2.com",
"enabled": True
}
self.ajax_post(reverse("admin:alias_add"), values)
def test_domainadmin_master_user(self):
"""Check domain administrator is not allowed to access this feature."""
values = dict(
username="user10@test.com", first_name="Test",
last_name="Test", password1="Toto1234", password2="Toto1234",
role="SimpleUsers", is_active=True, master_user=True,
email="user10@test.com", stepid='step2'
)
values = {
"username": "user10@test.com", "first_name": "Test",
"last_name": "Test", "password1": "Toto1234",
"password2": "Toto1234", "role": "SimpleUsers", "is_active": True,
"master_user": True, "email": "user10@test.com", "stepid": "step2"
}
self.ajax_post(
reverse("admin:account_add"),
values, status=400
@@ -656,7 +653,7 @@ class PermissionsTestCase(ModoTestCase):
dom = models.Domain.objects.get(name="test.com")
mb = factories.MailboxFactory(
domain=dom, address="admin2",
user__username="admin2@test.com", user__groups=('DomainAdmins', ),
user__username="admin2@test.com", user__groups=("DomainAdmins", ),
user__password="{PLAIN}toto")
dom.add_admin(mb.user)
self.client.logout()
+47 -44
View File
@@ -1,34 +1,33 @@
# coding: utf-8
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
"""Admin test cases."""
from django.urls import reverse
from modoboa.core.models import User
from modoboa.lib.tests import ModoTestCase
from .. import factories
from ..models import Alias, AliasRecipient, Domain
"""Admin test cases."""
class AliasTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls):
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
super(AliasTestCase, cls).setUpTestData()
factories.populate_database()
def test_alias(self):
user = User.objects.get(username="user@test.com")
values = dict(
username="user@test.com", role=user.role,
is_active=user.is_active, email="user@test.com",
aliases="toto@test.com", aliases_1="titi@test.com",
language="en"
)
values = {
"username": "user@test.com", "role": user.role,
"is_active": user.is_active, "email": "user@test.com",
"aliases": "toto@test.com", "aliases_1": "titi@test.com",
"language": "en"
}
self.ajax_post(
reverse("admin:account_change", args=[user.id]),
values
@@ -66,11 +65,11 @@ class AliasTestCase(ModoTestCase):
def test_upper_case_alias(self):
"""Try to create an upper case alias."""
user = User.objects.get(username="user@test.com")
values = dict(
username="user@test.com", role=user.role,
is_active=user.is_active, email="user@test.com",
aliases="Toto@test.com", language="en"
)
values = {
"username": "user@test.com", "role": user.role,
"is_active": user.is_active, "email": "user@test.com",
"aliases": "Toto@test.com", "language": "en"
}
self.ajax_post(
reverse("admin:account_change", args=[user.id]),
values
@@ -96,11 +95,11 @@ class AliasTestCase(ModoTestCase):
def test_append_alias_with_tag(self):
"""Try to create a alias with tag in recipient address"""
user = User.objects.get(username="user@test.com")
values = dict(
username="user@test.com", role=user.role,
is_active=user.is_active, email="user@test.com",
language="en"
)
values = {
"username": "user@test.com", "role": user.role,
"is_active": user.is_active, "email": "user@test.com",
"language": "en"
}
self.ajax_post(
reverse("admin:account_change", args=[user.id]),
values
@@ -120,11 +119,13 @@ class AliasTestCase(ModoTestCase):
def test_utf8_alias(self):
"""Test alias with non-ASCII characters."""
values = dict(address="testé@test.com",
recipients="user@test.com",
recipients_1="admin@test.com",
recipients_2="ext@titi.com",
enabled=True)
values = {
"address": "testé@test.com",
"recipients": "user@test.com",
"recipients_1": "admin@test.com",
"recipients_2": "ext@titi.com",
"enabled": True
}
self.ajax_post(
reverse("admin:alias_add"), values
)
@@ -168,7 +169,7 @@ class AliasTestCase(ModoTestCase):
Alias.DoesNotExist, Alias.objects.get, address="all@test.com")
def test_forward(self):
values = dict(address="forward2@test.com", recipients="rcpt@dest.com")
values = {"address": "forward2@test.com", "recipients": "rcpt@dest.com"}
self.ajax_post(
reverse("admin:alias_add"), values
)
@@ -191,7 +192,7 @@ class AliasTestCase(ModoTestCase):
Alias.DoesNotExist, Alias.objects.get, address="forward2@test.com")
def test_forward_and_local_copies(self):
values = dict(address="user@test.com", recipients="rcpt@dest.com")
values = {"address": "user@test.com", "recipients": "rcpt@dest.com"}
self.ajax_post(
reverse("admin:alias_add"), values
)
@@ -242,21 +243,23 @@ class AliasTestCase(ModoTestCase):
changed.
"""
values = dict(address="list@test.com",
recipients="user@test.com",
recipients_1="admin@test.com",
enabled=True)
values = {
"address": "list@test.com",
"recipients": "user@test.com",
"recipients_1": "admin@test.com",
"enabled": True
}
self.ajax_post(
reverse("admin:alias_add"), values
)
user = User.objects.get(username="user@test.com")
values = dict(
username=user.username, first_name="Tester", last_name="Toto",
password1="Toto1234", password2="Toto1234", role="SimpleUsers",
quota_act=True, is_active=True, email=user.email,
language="en"
)
values = {
"username": user.username, "first_name": "Tester",
"last_name": "Toto", "password1": "Toto1234",
"password2": "Toto1234", "role": "SimpleUsers", "quota_act": True,
"is_active": True, "email": user.email, "language": "en"
}
self.ajax_post(reverse("admin:account_change", args=[user.id]), values)
self.assertEqual(
@@ -277,12 +280,12 @@ class AliasTestCase(ModoTestCase):
self.client.force_login(admin)
user = User.objects.get(username="user@test.com")
values = dict(
username="user@test.com", role=user.role,
is_active=user.is_active, email="user@test.com",
aliases="toto@test.com", aliases_1="titi@test2.com",
language="en"
)
values = {
"username": "user@test.com", "role": user.role,
"is_active": user.is_active, "email": "user@test.com",
"aliases": "toto@test.com", "aliases_1": "titi@test2.com",
"language": "en"
}
response = self.ajax_post(
reverse("admin:account_change", args=[user.id]),
values, 400
+15 -18
View File
@@ -16,20 +16,17 @@ from django.urls import reverse
from rest_framework.authtoken.models import Token
from modoboa.admin import models as admin_models
from modoboa.core import factories as core_factories
from modoboa.core import models as core_models
from modoboa.core import factories as core_factories, models as core_models
from modoboa.lib.tests import ModoAPITestCase
from . import utils
from .. import factories
from .. import models
from .. import factories, models
class DomainAPITestCase(ModoAPITestCase):
"""Check API."""
@classmethod
def setUpTestData(cls): # noqa: N802
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
super(DomainAPITestCase, cls).setUpTestData()
factories.populate_database()
@@ -69,7 +66,7 @@ class DomainAPITestCase(ModoAPITestCase):
self.assertIn("default_mailbox_quota", response.data)
self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.da_token.key)
HTTP_AUTHORIZATION="Token " + self.da_token.key)
response = self.client.post(
url, {"name": "test4.com", "default_mailbox_quota": 10})
self.assertEqual(response.status_code, 403)
@@ -84,7 +81,7 @@ class DomainAPITestCase(ModoAPITestCase):
reseller = core_factories.UserFactory(
username="reseller", groups=("Resellers", ))
token = Token.objects.create(user=reseller)
self.client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)
self.client.credentials(HTTP_AUTHORIZATION="Token " + token.key)
url = reverse("api:domain-list")
mock_query.side_effect = utils.mock_dns_query_result
@@ -140,7 +137,7 @@ class DomainAliasAPITestCase(ModoAPITestCase):
"""Check DomainAlias API."""
@classmethod
def setUpTestData(cls): # noqa: N802
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
super(DomainAliasAPITestCase, cls).setUpTestData()
factories.populate_database()
@@ -169,7 +166,7 @@ class DomainAliasAPITestCase(ModoAPITestCase):
self.assertEqual(len(response.data), 1)
self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.da_token.key)
HTTP_AUTHORIZATION="Token " + self.da_token.key)
response = self.client.get(reverse("api:domain_alias-list"))
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 0)
@@ -191,7 +188,7 @@ class DomainAliasAPITestCase(ModoAPITestCase):
self.assertEqual(dalias.target, target)
self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.da_token.key)
HTTP_AUTHORIZATION="Token " + self.da_token.key)
response = self.client.post(
url, {"name": "dalias4.com", "target": target.pk}, format="json")
self.assertEqual(response.status_code, 403)
@@ -234,7 +231,7 @@ class AccountAPITestCase(ModoAPITestCase):
}
@classmethod
def setUpTestData(cls): # noqa: N802
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
super(AccountAPITestCase, cls).setUpTestData()
factories.populate_database()
@@ -389,7 +386,7 @@ class AccountAPITestCase(ModoAPITestCase):
def test_create_account_as_domadmin(self):
"""As DomainAdmin, try to create a new account."""
self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.da_token.key)
HTTP_AUTHORIZATION="Token " + self.da_token.key)
data = copy.deepcopy(self.ACCOUNT_DATA)
data["mailbox"]["quota"] = 1000
url = reverse("api:account-list")
@@ -566,7 +563,7 @@ class AliasAPITestCase(ModoAPITestCase):
}
@classmethod
def setUpTestData(cls): # noqa: N802
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
super(AliasAPITestCase, cls).setUpTestData()
cls.localconfig.parameters.set_value(
@@ -604,7 +601,7 @@ class AliasAPITestCase(ModoAPITestCase):
response = self.client.post(url, self.ALIAS_DATA, format="json")
self.assertEqual(response.status_code, 201)
alias = json.loads(response.content.decode('utf-8'))
alias = json.loads(response.content.decode("utf-8"))
alias = models.Alias.objects.filter(pk=alias["pk"]).first()
domadmin = core_models.User.objects.get(username="admin@test.com")
self.assertTrue(domadmin.can_access(alias))
@@ -626,7 +623,7 @@ class AliasAPITestCase(ModoAPITestCase):
def test_create_alias_as_domadmin(self):
"""As DomainAdmin, try to create a new alias."""
self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.da_token.key)
HTTP_AUTHORIZATION="Token " + self.da_token.key)
url = reverse("api:alias-list")
response = self.client.post(url, self.ALIAS_DATA, format="json")
self.assertEqual(response.status_code, 201)
@@ -689,7 +686,7 @@ class SenderAddressAPITestCase(ModoAPITestCase):
"""Check SenderAddress API."""
@classmethod
def setUpTestData(cls): # noqa: N802
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
super(SenderAddressAPITestCase, cls).setUpTestData()
cls.localconfig.parameters.set_value(
@@ -723,7 +720,7 @@ class SenderAddressAPITestCase(ModoAPITestCase):
self.assertEqual(response.status_code, 201)
self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.da_token.key)
HTTP_AUTHORIZATION="Token " + self.da_token.key)
data = {"address": "user@test2.com", "mailbox": mbox.pk}
response = self.client.post(url, data)
self.assertEqual(response.status_code, 400)
+6 -7
View File
@@ -20,17 +20,16 @@ from modoboa.core import factories as core_factories
from modoboa.core.models import User
from modoboa.core.tests.test_views import SETTINGS_SAMPLE
from modoboa.lib.tests import ModoTestCase
from . import utils
from .. import factories
from ..models import Domain, Alias
from ..models import Alias, Domain
class DomainTestCase(ModoTestCase):
"""Test case for Domain."""
@classmethod
def setUpTestData(cls): # noqa: N802
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
super(DomainTestCase, cls).setUpTestData()
factories.populate_database()
@@ -176,7 +175,7 @@ class DomainTestCase(ModoTestCase):
values = {
"name": "pouet.com", "quota": 10, "default_mailbox_quota": 100,
"create_dom_admin": True, "dom_admin_username": "toto",
"create_aliases": True, "type": "domain", "stepid": 'step3'
"create_aliases": True, "type": "domain", "stepid": "step3"
}
response = self.ajax_post(
reverse("admin:domain_add"),
@@ -347,13 +346,13 @@ class DomainTestCase(ModoTestCase):
url = reverse("core:dashboard")
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertIn("Global statistics", response.content.decode('utf-8'))
self.assertIn("Global statistics", response.content.decode("utf-8"))
self.client.force_login(
User.objects.get(username="admin@test.com"))
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
content = response.content.decode('utf-8')
content = response.content.decode("utf-8")
self.assertNotIn("Global statistics", content)
self.assertIn("Per-domain statistics", content)
@@ -362,7 +361,7 @@ class DKIMTestCase(ModoTestCase):
"""Test case for DKIM."""
@classmethod
def setUpTestData(cls): # noqa: N802
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
super(DKIMTestCase, cls).setUpTestData()
factories.populate_database()
+4 -5
View File
@@ -1,23 +1,22 @@
# coding: utf-8
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.urls import reverse
from modoboa.lib.tests import ModoTestCase
from .. import factories
from ..models import Domain, DomainAlias, Alias
from ..models import Alias, Domain, DomainAlias
class DomainAliasTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls):
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
super(DomainAliasTestCase, cls).setUpTestData()
factories.populate_database()
cls.dom = Domain.objects.get(name='test.com')
cls.dom = Domain.objects.get(name="test.com")
def test_model(self):
dom = Domain.objects.get(name="test.com")
+8 -10
View File
@@ -1,4 +1,4 @@
# coding: utf-8
# -*- coding: utf-8 -*-
"""Export related test cases."""
@@ -8,16 +8,14 @@ from django.urls import reverse
from django.utils.encoding import force_text
from modoboa.lib.tests import ModoTestCase
from .. import factories
from .. import models
from .. import factories, models
class ExportTestCase(ModoTestCase):
"""Test case for export operations."""
@classmethod
def setUpTestData(cls):
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
super(ExportTestCase, cls).setUpTestData()
factories.populate_database()
@@ -40,7 +38,7 @@ class ExportTestCase(ModoTestCase):
{"filename": "test.csv"}
)
def assertListEqual(self, list1, list2):
def assertListEqual(self, list1, list2): # NOQA:N802
list1 = force_text(list1).split("\r\n")
list2 = force_text(list2).split("\r\n")
self.assertEqual(len(list1), len(list2))
@@ -67,7 +65,7 @@ class ExportTestCase(ModoTestCase):
def test_export_identities(self):
response = self.__export_identities()
self.assertListEqual(
"account;admin@test.com;{PLAIN}toto;;;True;DomainAdmins;admin@test.com;10;test.com\r\naccount;admin@test2.com;{PLAIN}toto;;;True;DomainAdmins;admin@test2.com;10;test2.com\r\naccount;user@test.com;{PLAIN}toto;;;True;SimpleUsers;user@test.com;10\r\naccount;user@test2.com;{PLAIN}toto;;;True;SimpleUsers;user@test2.com;10\r\nalias;alias@test.com;True;user@test.com\r\nalias;forward@test.com;True;user@external.com\r\nalias;postmaster@test.com;True;test@truc.fr;toto@titi.com\r\n",
"account;admin@test.com;{PLAIN}toto;;;True;DomainAdmins;admin@test.com;10;test.com\r\naccount;admin@test2.com;{PLAIN}toto;;;True;DomainAdmins;admin@test2.com;10;test2.com\r\naccount;user@test.com;{PLAIN}toto;;;True;SimpleUsers;user@test.com;10\r\naccount;user@test2.com;{PLAIN}toto;;;True;SimpleUsers;user@test2.com;10\r\nalias;alias@test.com;True;user@test.com\r\nalias;forward@test.com;True;user@external.com\r\nalias;postmaster@test.com;True;test@truc.fr;toto@titi.com\r\n", # NOQA:E501
response.content.strip()
)
@@ -81,7 +79,7 @@ class ExportTestCase(ModoTestCase):
idtfilter="account", grpfilter="SimpleUsers"
)
self.assertListEqual(
"account;user@test.com;{PLAIN}toto;;;True;SimpleUsers;user@test.com;10\r\naccount;user@test2.com;{PLAIN}toto;;;True;SimpleUsers;user@test2.com;10\r\naccount;toto@test.com;{PLAIN}toto;Léon;;True;SimpleUsers;toto@test.com;10",
"account;user@test.com;{PLAIN}toto;;;True;SimpleUsers;user@test.com;10\r\naccount;user@test2.com;{PLAIN}toto;;;True;SimpleUsers;user@test2.com;10\r\naccount;toto@test.com;{PLAIN}toto;Léon;;True;SimpleUsers;toto@test.com;10", # NOQA:E501
response.content.strip()
)
@@ -105,7 +103,7 @@ class ExportTestCase(ModoTestCase):
idtfilter="account", grpfilter="DomainAdmins"
)
self.assertListEqual(
"account;admin@test.com;{PLAIN}toto;;;True;DomainAdmins;admin@test.com;10;test.com\r\naccount;admin@test2.com;{PLAIN}toto;;;True;DomainAdmins;admin@test2.com;10;test2.com",
"account;admin@test.com;{PLAIN}toto;;;True;DomainAdmins;admin@test.com;10;test.com\r\naccount;admin@test2.com;{PLAIN}toto;;;True;DomainAdmins;admin@test2.com;10;test2.com", # NOQA:E501
response.content.strip()
)
@@ -113,5 +111,5 @@ class ExportTestCase(ModoTestCase):
response = self.__export_identities(idtfilter="alias")
self.assertEqual(
response.content.decode().strip(),
"alias;alias@test.com;True;user@test.com\r\nalias;forward@test.com;True;user@external.com\r\nalias;postmaster@test.com;True;test@truc.fr;toto@titi.com"
"alias;alias@test.com;True;user@test.com\r\nalias;forward@test.com;True;user@external.com\r\nalias;postmaster@test.com;True;test@truc.fr;toto@titi.com" # NOQA:E501
)
+13 -11
View File
@@ -11,16 +11,15 @@ from django.urls import reverse
from modoboa.core import factories as core_factories
from modoboa.core.models import User
from modoboa.lib.tests import ModoTestCase
from . import utils
from .. import factories
from ..models import Domain, Alias, DomainAlias
from ..models import Alias, Domain, DomainAlias
class ImportTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls): # noqa:N802
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
super(ImportTestCase, cls).setUpTestData()
cls.localconfig.parameters.set_value(
@@ -135,7 +134,7 @@ alias; alias1@test.com; True; user1@test.com
forward; alias2@test.com; True; user1+ext@test.com
forward; fwd1@test.com; True; user@extdomain.com
dlist; dlist@test.com; True; user1@test.com; user@extdomain.com
""", name="identities.csv")
""", name="identities.csv") # NOQA:E501
self.client.post(
reverse("admin:identity_import"),
{"sourcefile": f, "crypt_password": True}
@@ -201,7 +200,7 @@ dlist; dlist@test.com; True; user1@test.com; user@extdomain.com
"""Try to import an account for nonlocal domain."""
f = ContentFile(b"""
account; user1@nonlocal.com; toto; User; One; True; SimpleUsers; user1@nonlocal.com; 0
""", name="identities.csv")
""", name="identities.csv") # NOQA:E501
self.client.post(
reverse("admin:identity_import"),
{"sourcefile": f, "crypt_password": True}
@@ -212,12 +211,12 @@ account; user1@nonlocal.com; toto; User; One; True; SimpleUsers; user1@nonlocal.
def test_import_invalid_quota(self):
f = ContentFile(b"""
account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; ; test.com
""", name="identities.csv")
""", name="identities.csv") # NOQA:E501
resp = self.client.post(
reverse("admin:identity_import"),
{"sourcefile": f, "crypt_password": True}
)
self.assertIn('wrong quota value', resp.content.decode())
self.assertIn("wrong quota value", resp.content.decode())
def test_import_domain_by_domainadmin(self):
"""Check if a domain admin is not allowed to import a domain."""
@@ -230,7 +229,9 @@ domain; domain2.com; 1000; 200; False
reverse("admin:identity_import"),
{"sourcefile": f, "crypt_password": True}
)
self.assertIn("You are not allowed to import domains", resp.content.decode())
self.assertIn(
"You are not allowed to import domains",
resp.content.decode())
f = ContentFile(b"""
domainalias; domalias1.com; test.com; True
""", name="identities.csv")
@@ -239,7 +240,8 @@ domainalias; domalias1.com; test.com; True
{"sourcefile": f, "crypt_password": True}
)
self.assertIn(
"You are not allowed to import domain aliases", resp.content.decode())
"You are not allowed to import domain aliases",
resp.content.decode())
def test_import_quota_too_big(self):
self.client.logout()
@@ -271,7 +273,7 @@ account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com
f = ContentFile("""
account; admin@test.com; toto; Admin; ; True; DomainAdmins; admin@test.com; 0; test.com
account; truc@test.com; toto; René; Truc; True; DomainAdmins; truc@test.com; 0; test.com
""", name="identities.csv")
""", name="identities.csv") # NOQA:E501
self.client.post(
reverse("admin:identity_import"),
{"sourcefile": f, "crypt_password": True,
@@ -292,7 +294,7 @@ account; truc@test.com; toto; René; Truc; True; DomainAdmins; truc@test.com; 0;
)
f = ContentFile(b"""
account; sa@test.com; toto; Super; Admin; True; SuperAdmins; superadmin@test.com; 50
""", name="identities.csv")
""", name="identities.csv") # NOQA:E501
self.client.post(
reverse("admin:identity_import"),
{"sourcefile": f, "crypt_password": True,
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Management command tests."""
from __future__ import unicode_literals
@@ -9,13 +11,11 @@ import tempfile
import mock
from django.core.management import call_command
from django.urls import reverse
from django.test import override_settings
from django.urls import reverse
from modoboa.lib.tests import ModoTestCase
from .. import factories
from .. import models
from .. import factories, models
@override_settings(
@@ -24,7 +24,7 @@ class MailboxOperationTestCase(ModoTestCase):
"""Test management command."""
@classmethod
def setUpTestData(cls):
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
super(MailboxOperationTestCase, cls).setUpTestData()
factories.populate_database()
+2
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Test map files generation."""
from __future__ import unicode_literals
+10 -13
View File
@@ -8,26 +8,23 @@ import dns.resolver
from mock import patch
from testfixtures import LogCapture
from django.core import mail
from django.core import management
from django.core import mail, management
from django.test import override_settings
from django.urls import reverse
from django.utils.translation import ugettext as _
from modoboa.core import factories as core_factories
from modoboa.lib.tests import ModoTestCase
from . import utils
from .. import factories
from .. import factories, models
from ..lib import get_domain_mx_list
from .. import models
class MXTestCase(ModoTestCase):
"""TestCase for DNSBL related features."""
@classmethod
def setUpTestData(cls): # noqa:N802
def setUpTestData(cls): # NOQA:N802
"""Create some data."""
super(MXTestCase, cls).setUpTestData()
cls.domain = factories.DomainFactory(name="modoboa.org")
@@ -56,7 +53,7 @@ class MXTestCase(ModoTestCase):
@patch("socket.getaddrinfo")
@patch.object(dns.resolver.Resolver, "query")
def test_management_command(
self, mock_query, mock_getaddrinfo, mock_g_gethostbyname):
self, mock_query, mock_getaddrinfo, mock_g_gethostbyname):
"""Check that command works fine."""
mock_query.side_effect = utils.mock_dns_query_result
mock_getaddrinfo.side_effect = utils.mock_ip_query_result
@@ -84,7 +81,7 @@ class MXTestCase(ModoTestCase):
@patch("socket.getaddrinfo")
@patch.object(dns.resolver.Resolver, "query")
def test_single_domain_update(
self, mock_query, mock_getaddrinfo, mock_g_gethostbyname):
self, mock_query, mock_getaddrinfo, mock_g_gethostbyname):
"""Update only one domain."""
mock_query.side_effect = utils.mock_dns_query_result
mock_getaddrinfo.side_effect = utils.mock_ip_query_result
@@ -133,7 +130,7 @@ class MXTestCase(ModoTestCase):
("modoboa.admin", "WARNING",
_("Invalid IP address format for %(domain)s; %(addr)s")
% {"domain": "bad-response.example.com", "addr": "BAD"}),
)
)
@override_settings(DNSBL_PROVIDERS=["zen.spamhaus.org"])
@@ -141,7 +138,7 @@ class DNSBLTestCase(ModoTestCase):
"""TestCase for DNSBL related features."""
@classmethod
def setUpTestData(cls): # noqa:N802
def setUpTestData(cls): # NOQA:N802
"""Create some data."""
super(DNSBLTestCase, cls).setUpTestData()
cls.domain = factories.DomainFactory(name="modoboa.org")
@@ -156,7 +153,7 @@ class DNSBLTestCase(ModoTestCase):
@patch("socket.getaddrinfo")
@patch.object(dns.resolver.Resolver, "query")
def test_management_command(
self, mock_query, mock_getaddrinfo, mock_g_gethostbyname):
self, mock_query, mock_getaddrinfo, mock_g_gethostbyname):
"""Check that command works fine."""
mock_query.side_effect = utils.mock_dns_query_result
mock_getaddrinfo.side_effect = utils.mock_ip_query_result
@@ -177,7 +174,7 @@ class DNSBLTestCase(ModoTestCase):
@patch("socket.getaddrinfo")
@patch.object(dns.resolver.Resolver, "query")
def test_notifications(
self, mock_query, mock_getaddrinfo, mock_g_gethostbyname):
self, mock_query, mock_getaddrinfo, mock_g_gethostbyname):
"""Check notifications."""
mock_query.side_effect = utils.mock_dns_query_result
mock_getaddrinfo.side_effect = utils.mock_ip_query_result
@@ -193,7 +190,7 @@ class DNSBLTestCase(ModoTestCase):
@patch("socket.getaddrinfo")
@patch.object(dns.resolver.Resolver, "query")
def test_management_command_no_dnsbl(
self, mock_query, mock_getaddrinfo, mock_g_gethostbyname):
self, mock_query, mock_getaddrinfo, mock_g_gethostbyname):
"""Check that command works fine without dnsbl."""
mock_query.side_effect = utils.mock_dns_query_result
mock_getaddrinfo.side_effect = utils.mock_ip_query_result
+19 -18
View File
@@ -1,28 +1,29 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.urls import reverse
from modoboa.core.models import User
from modoboa.lib.tests import ModoTestCase
from .. import factories
class PasswordSchemesTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls):
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
super(PasswordSchemesTestCase, cls).setUpTestData()
factories.populate_database()
def _create_account(self):
values = dict(
username="tester@test.com", first_name="Tester", last_name="Toto",
password1="Toto1234", password2="Toto1234", role="SimpleUsers",
quota_act=True,
is_active=True, email="tester@test.com", stepid='step2'
)
values = {
"username": "tester@test.com", "first_name": "Tester",
"last_name": "Toto", "password1": "Toto1234",
"password2": "Toto1234", "role": "SimpleUsers", "quota_act": True,
"is_active": True, "email": "tester@test.com", "stepid": "step2"
}
self.ajax_post(
reverse("admin:account_add"),
values
@@ -31,30 +32,30 @@ class PasswordSchemesTestCase(ModoTestCase):
def _test_scheme(self, name, startpattern):
self.set_global_parameter("password_scheme", name, app="core")
self._create_account()
account = User.objects.get(username='tester@test.com')
account = User.objects.get(username="tester@test.com")
self.assertTrue(account.password.startswith(startpattern))
self.assertTrue(account.check_password('Toto1234'))
self.assertTrue(account.check_password("Toto1234"))
def test_bcrypt_scheme(self):
self._test_scheme('blfcrypt', '{BLF-CRYPT}')
self._test_scheme("blfcrypt", "{BLF-CRYPT}")
def test_sha512crypt_scheme(self):
self._test_scheme('sha512crypt', '{SHA512-CRYPT}')
self._test_scheme("sha512crypt", "{SHA512-CRYPT}")
def test_sha256crypt_scheme(self):
self._test_scheme('sha256crypt', '{SHA256-CRYPT}')
self._test_scheme("sha256crypt", "{SHA256-CRYPT}")
def test_md5crypt_scheme(self):
self._test_scheme('md5crypt', '{MD5-CRYPT}')
self._test_scheme("md5crypt", "{MD5-CRYPT}")
def test_sha256_scheme(self):
self._test_scheme('sha256', '{SHA256}')
self._test_scheme("sha256", "{SHA256}")
def test_md5_scheme(self):
self._test_scheme('md5', '{MD5}')
self._test_scheme("md5", "{MD5}")
def test_crypt(self):
self._test_scheme('crypt', '{CRYPT}')
self._test_scheme("crypt", "{CRYPT}")
def test_plain(self):
self._test_scheme('plain', '{PLAIN}')
self._test_scheme("plain", "{PLAIN}")
+6 -7
View File
@@ -1,22 +1,21 @@
# -*- coding: utf-8 -*-
"""Repair command tests"""
from __future__ import unicode_literals
from django.core import management
from modoboa.lib.permissions import get_object_owner
from modoboa.lib.permissions import ObjectAccess
from modoboa.lib.permissions import ObjectAccess, get_object_owner
from modoboa.lib.tests import ModoTestCase
from .. import factories
from .. import models
from .. import factories, models
class RepairTestCase(ModoTestCase):
"""TestCase for repair command."""
@classmethod
def setUpTestData(cls):
def setUpTestData(cls): # NOQA:N802
"""Create some data."""
super(RepairTestCase, cls).setUpTestData()
factories.populate_database()
@@ -49,7 +48,7 @@ class RepairTestCase(ModoTestCase):
def test_management_command_with_nul_domain(self):
"""Just assume nothing raise when an alias has no domain."""
models.Alias.objects.create(address='@modoboa.xxx')
models.Alias.objects.create(address="@modoboa.xxx")
ret = management.call_command("modo", "repair", "--quiet")
assert ret is None
+2 -1
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""User related tests."""
from __future__ import unicode_literals
@@ -6,7 +8,6 @@ from django.urls import reverse
from modoboa.core.models import User
from modoboa.lib.tests import ModoTestCase
from ..factories import populate_database
from ..models import Alias
+3 -2
View File
@@ -8,12 +8,13 @@ import socket
from dns.name import Name
from dns.rdtypes.ANY.MX import MX
from dns.resolver import NoAnswer, NoNameservers, NXDOMAIN, Timeout
from dns.resolver import NXDOMAIN, NoAnswer, NoNameservers, Timeout
_MX_RECORD_1 = MX("IN", "MX", 10, Name("mx.example.com".split(".")))
_MX_RECORD_2 = MX("IN", "MX", 10, Name("mx2.example.com".split(".")))
_BAD_MX_RECORD = MX("IN", "MX", 10, Name("bad-response.example.com".split(".")))
_DNE_MX_RECORD = MX("IN", "MX", 10, Name("does-not-exist.example.com".split(".")))
_DNE_MX_RECORD = MX("IN", "MX", 10, Name(
"does-not-exist.example.com".split(".")))
_MX_RECORDS = [_MX_RECORD_1]
_IPV4_RECORD_1 = (socket.AF_INET, socket.SOCK_STREAM, 6, "", ("192.0.2.1", 25))
+7 -8
View File
@@ -1,4 +1,5 @@
# pylint: disable=invalid-name, protected-access
# -*- coding: utf-8 -*-
"""Admin urls."""
from __future__ import unicode_literals
@@ -6,12 +7,10 @@ from __future__ import unicode_literals
from django.conf.urls import url
from . import views
from .views import import_ as import_views
from .views import alias as alias_views
from .views import domain as domain_views
from .views import export as export_views
from .views import identity as identity_views
from .views import (
alias as alias_views, domain as domain_views, export as export_views,
identity as identity_views, import_ as import_views
)
urlpatterns = [
url(r'^$', domain_views.index, name="index"),
@@ -27,7 +26,7 @@ urlpatterns = [
name="domain_quota_list"),
url(r'^domains/flatlist/$', domain_views.domains_list,
name="domain_flat_list"),
url(r'^domains/new/', domain_views.newdomain, name='domain_add'),
url(r'^domains/new/', domain_views.newdomain, name="domain_add"),
url(r'^domains/(?P<dom_id>\d+)/edit/$', domain_views.editdomain,
name="domain_change"),
url(r'^domains/(?P<dom_id>\d+)/delete/$', domain_views.deldomain,
+2 -1
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Admin API urls."""
from __future__ import unicode_literals
@@ -6,7 +8,6 @@ from rest_framework import routers
from . import api
router = routers.SimpleRouter()
router.register(r"domains", api.DomainViewSet, base_name="domain")
router.register(
+3 -2
View File
@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
"""Shortcuts."""
from __future__ import unicode_literals
from .alias import AliasDetailView
from .identity import AccountDetailView
from .dns import DNSBLDomainDetailView, MXDomainDetailView
from .domain import DomainDetailView
from .identity import AccountDetailView
__all__ = [
"AccountDetailView",
+15 -17
View File
@@ -1,24 +1,22 @@
# -*- coding: utf-8 -*-
"""Alias related views."""
from __future__ import unicode_literals
from django.urls import reverse
from reversion import revisions as reversion
from django.contrib.auth import mixins as auth_mixins
from django.contrib.auth.decorators import login_required, permission_required
from django.db import IntegrityError
from django.shortcuts import render
from django.urls import reverse
from django.utils.translation import ugettext as _, ungettext
from django.views import generic
from django.contrib.auth import mixins as auth_mixins
from django.contrib.auth.decorators import (
login_required, permission_required
)
from reversion import revisions as reversion
from modoboa.core import signals as core_signals
from modoboa.lib.exceptions import PermDeniedException, Conflict
from modoboa.lib.exceptions import Conflict, PermDeniedException
from modoboa.lib.web_utils import render_to_json_response
from ..forms import AliasForm
from ..models import Alias
@@ -37,7 +35,7 @@ def _validate_alias(request, form, successmsg, callback=None):
callback(request.user, alias)
return render_to_json_response(successmsg)
return render_to_json_response({'form_errors': form.errors}, status=400)
return render_to_json_response({"form_errors": form.errors}, status=400)
def _new_alias(request, title, action, successmsg,
@@ -87,12 +85,12 @@ def editalias(request, alid, tplname="admin/aliasform.html"):
return _validate_alias(request, form, successmsg)
ctx = {
'action': reverse("admin:alias_change", args=[alias.id]),
'formid': 'aliasform',
'title': alias.address,
'action_label': _('Update'),
'action_classes': 'submit',
'form': AliasForm(request.user, instance=alias)
"action": reverse("admin:alias_change", args=[alias.id]),
"formid": "aliasform",
"title": alias.address,
"action_label": _("Update"),
"action_classes": "submit",
"form": AliasForm(request.user, instance=alias)
}
return render(request, tplname, ctx)
+4 -4
View File
@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
"""DNSBL related views."""
from __future__ import unicode_literals
from django.views import generic
from django.utils.translation import ugettext as _
from django.contrib.auth import mixins as auth_mixins
from django.utils.translation import ugettext as _
from django.views import generic
from .. import models
+23 -25
View File
@@ -1,34 +1,34 @@
# -*- coding: utf-8 -*-
"""Domain related views."""
from __future__ import unicode_literals
from functools import reduce
from django.urls import reverse
from django.db.models import Sum
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _, ungettext
from django.views import generic
from django.views.decorators.csrf import ensure_csrf_cookie
from reversion import revisions as reversion
from django.contrib.auth import mixins as auth_mixins
from django.contrib.auth.decorators import (
login_required, permission_required, user_passes_test
)
from reversion import revisions as reversion
from django.db.models import Sum
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.translation import ugettext as _, ungettext
from django.views import generic
from django.views.decorators.csrf import ensure_csrf_cookie
from modoboa.core import signals as core_signals
from modoboa.lib.web_utils import render_to_json_response
from modoboa.lib.exceptions import PermDeniedException
from modoboa.lib.listing import get_sort_order, get_listing_page
from modoboa.lib.listing import get_listing_page, get_sort_order
from modoboa.lib.web_utils import render_to_json_response
from .. import signals
from ..forms import DomainForm, DomainWizard
from ..lib import get_domains
from ..models import Domain, Mailbox
from .. import signals
@login_required
@@ -47,20 +47,20 @@ def _domains(request):
if extra_filters:
extra_filters = reduce(
lambda a, b: a + b, [result[1] for result in extra_filters])
filters = dict(
(flt, request.GET.get(flt, None))
filters = {
flt: request.GET.get(flt, None)
for flt in ["domfilter", "searchquery"] + extra_filters
)
request.session['domains_filters'] = filters
}
request.session["domains_filters"] = filters
domainlist = get_domains(request.user, **filters)
if sort_order == 'name':
if sort_order == "name":
domainlist = sorted(
domainlist,
key=lambda d: getattr(d, sort_order), reverse=sort_dir == '-'
key=lambda d: getattr(d, sort_order), reverse=sort_dir == "-"
)
else:
domainlist = sorted(domainlist, key=lambda d: d.tags[0]["name"],
reverse=sort_dir == '-')
reverse=sort_dir == "-")
context = {
"handle_mailboxes": request.localconfig.parameters.get_value(
"handle_mailboxes", raise_exception=False),
@@ -118,8 +118,6 @@ def domains_list(request):
@login_required
@permission_required("admin.add_domain")
def list_quotas(request):
from modoboa.lib.db_utils import db_type
sort_order, sort_dir = get_sort_order(request.GET, "name")
domains = Domain.objects.get_for_admin(request.user)
domains = domains.exclude(quota=0)
@@ -164,7 +162,7 @@ def editdomain(request, dom_id):
if not request.user.can_access(domain):
raise PermDeniedException
instances = dict(general=domain)
instances = {"general": domain}
results = signals.get_domain_form_instances.send(
sender="editdomain", user=request.user, domain=domain)
for result in results:
@@ -217,7 +215,7 @@ class DomainDetailView(
"enable_mx_checks": parameters.get_value("enable_mx_checks"),
"enable_dnsbl_checks": parameters.get_value("enable_dnsbl_checks"),
})
for receiver, widgets in result:
for _receiver, widgets in result:
for widget in widgets:
context["templates"][widget["column"]].append(
widget["template"])
+7 -6
View File
@@ -1,21 +1,22 @@
# -*- coding: utf-8 -*-
"""Export related views."""
from __future__ import unicode_literals
from backports import csv
from rfc6266 import build_header
from django.contrib.auth.decorators import (
login_required, permission_required, user_passes_test
)
from django.urls import reverse
from django.http import HttpResponse
from django.shortcuts import render
from django.utils.translation import ugettext as _
from django.urls import reverse
from django.utils import six
from django.utils.translation import ugettext as _
from ..forms import ExportIdentitiesForm, ExportDomainsForm
from ..forms import ExportDomainsForm, ExportIdentitiesForm
from ..lib import get_domains, get_identities
@@ -52,7 +53,7 @@ def export_identities(request):
fp = six.StringIO()
csvwriter = csv.writer(fp, delimiter=form.cleaned_data["sepchar"])
identities = get_identities(
request.user, **request.session['identities_filters'])
request.user, **request.session["identities_filters"])
for ident in identities:
ident.to_csv(csvwriter)
content = fp.getvalue()
@@ -80,7 +81,7 @@ def export_domains(request):
fp = six.StringIO()
csvwriter = csv.writer(fp, delimiter=form.cleaned_data["sepchar"])
for dom in get_domains(request.user,
**request.session['domains_filters']):
**request.session["domains_filters"]):
dom.to_csv(csvwriter)
content = fp.getvalue()
fp.close()
+25 -26
View File
@@ -1,33 +1,29 @@
# -*- coding: utf-8 -*-
"""Identity related views."""
from __future__ import unicode_literals
from reversion import revisions as reversion
from django.contrib.auth import mixins as auth_mixins
from django.contrib.auth.decorators import (
login_required, permission_required, user_passes_test
)
from django.shortcuts import render
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _, ungettext
from django.views import generic
from django.views.decorators.csrf import ensure_csrf_cookie
from django.contrib.auth import mixins as auth_mixins
from django.contrib.auth.decorators import (
login_required, permission_required, user_passes_test
)
from reversion import revisions as reversion
from modoboa.core.models import User
from modoboa.lib.exceptions import (
PermDeniedException, BadRequest
)
from modoboa.lib.listing import (
get_sort_order, get_listing_page
)
from modoboa.lib.exceptions import BadRequest, PermDeniedException
from modoboa.lib.listing import get_listing_page, get_sort_order
from modoboa.lib.web_utils import render_to_json_response
from .. import signals
from ..forms import AccountForm, AccountWizard
from ..lib import get_identities
from ..models import Mailbox, Domain
from .. import signals
from ..models import Domain, Mailbox
@login_required
@@ -36,18 +32,20 @@ from .. import signals
u.has_perm("admin.add_alias")
)
def _identities(request):
filters = dict((fname, request.GET.get(fname, None))
for fname in ['searchquery', 'idtfilter', 'grpfilter'])
request.session['identities_filters'] = filters
filters = {
fname: request.GET.get(fname, None)
for fname in ["searchquery", "idtfilter", "grpfilter"]
}
request.session["identities_filters"] = filters
idents_list = get_identities(request.user, **filters)
sort_order, sort_dir = get_sort_order(request.GET, "identity",
["identity", "name_or_rcpt", "tags"])
if sort_order in ["identity", "name_or_rcpt"]:
objects = sorted(idents_list, key=lambda o: getattr(o, sort_order),
reverse=sort_dir == '-')
reverse=sort_dir == "-")
else:
objects = sorted(idents_list, key=lambda o: o.tags[0],
reverse=sort_dir == '-')
reverse=sort_dir == "-")
context = {
"handle_mailboxes": request.localconfig.parameters.get_value(
"handle_mailboxes", raise_exception=False)
@@ -104,7 +102,7 @@ def list_quotas(request):
"* 1048576)) * 100"
)
mboxes = mboxes.extra(
select={'quota_usage': select},
select={"quota_usage": select},
where=["admin_quota.username=%s" % where],
tables=["admin_quota", "admin_domain"],
order_by=["%s%s" % (sort_dir, sort_order)]
@@ -157,7 +155,7 @@ def identities(request, tplname="admin/identities.html"):
@permission_required("core.add_user")
def accounts_list(request):
accs = User.objects.filter(is_superuser=False) \
.exclude(groups__name='SimpleUsers')
.exclude(groups__name="SimpleUsers")
res = [a.username for a in accs.all()]
return render_to_json_response(res)
@@ -179,8 +177,9 @@ def editaccount(request, pk):
raise PermDeniedException
mb = account.mailbox if hasattr(account, "mailbox") else None
instances = dict(
general=account, profile=account, mail=mb, perms=account)
instances = {
"general": account, "profile": account, "mail": mb, "perms": account
}
results = signals.get_account_form_instances.send(
sender="editaccount", user=request.user, account=account)
for result in results:
@@ -238,7 +237,7 @@ class AccountDetailView(
result = signals.extra_account_dashboard_widgets.send(
self.__class__, user=self.request.user, account=self.object)
context["templates"] = {"left": [], "right": []}
for receiver, widgets in result:
for _receiver, widgets in result:
for widget in widgets:
context["templates"][widget["column"]].append(
widget["template"])
+11 -11
View File
@@ -1,26 +1,26 @@
# -*- coding: utf-8 -*-
"""Import related views."""
from __future__ import unicode_literals
from backports import csv
import io
from backports import csv
from reversion import revisions as reversion
from django.contrib.auth.decorators import (
login_required, permission_required, user_passes_test
)
from django.urls import reverse
from django.db import transaction
from django.shortcuts import render
from django.urls import reverse
from django.utils.encoding import smart_text
from django.utils.translation import ugettext as _
from reversion import revisions as reversion
from modoboa.lib.exceptions import ModoboaException, Conflict
from ..forms import ImportIdentitiesForm, ImportDataForm
from modoboa.lib.exceptions import Conflict, ModoboaException
from .. import signals
from ..forms import ImportDataForm, ImportIdentitiesForm
@reversion.create_revision()
@@ -39,8 +39,8 @@ def importdata(request, formclass=ImportDataForm):
if form.is_valid():
try:
infile = io.TextIOWrapper(
request.FILES['sourcefile'].file, encoding="utf8")
reader = csv.reader(infile, delimiter=form.cleaned_data['sepchar'])
request.FILES["sourcefile"].file, encoding="utf8")
reader = csv.reader(infile, delimiter=form.cleaned_data["sepchar"])
except csv.Error as inst:
error = smart_text(inst)
else:
@@ -63,7 +63,7 @@ def importdata(request, formclass=ImportDataForm):
continue
raise Conflict(
_("Object already exists: %s"
% form.cleaned_data['sepchar'].join(row[:2]))
% form.cleaned_data["sepchar"].join(row[:2]))
)
cpt += 1
msg = _("%d objects imported successfully" % cpt)
+6 -6
View File
@@ -1,16 +1,16 @@
# -*- coding: utf-8 -*-
"""SimpleUsers views."""
from __future__ import unicode_literals
from reversion import revisions as reversion
from django.contrib.auth.decorators import login_required
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
from django.contrib.auth.decorators import login_required
from reversion import revisions as reversion
from modoboa.lib.web_utils import render_to_json_response
from ..forms import ForwardForm
from ..lib import needs_mailbox
from ..models import Alias
@@ -40,7 +40,7 @@ def forward(request, tplname="admin/forward.html"):
return render_to_json_response(_("Forward updated"))
return render_to_json_response(
{'form_errors': form.errors}, status=400
{"form_errors": form.errors}, status=400
)
form = ForwardForm()
+2
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""App config for core."""
from __future__ import unicode_literals
+12 -16
View File
@@ -1,21 +1,17 @@
# coding: utf-8
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django import forms
from django.conf import settings
from django.contrib.auth import password_validation
from django.utils.translation import ugettext as _, ugettext_lazy
from django.contrib.auth import password_validation
from modoboa.lib import fields as lib_fields
from modoboa.lib.cryptutils import random_key
from modoboa.lib.form_utils import (
YesNoField, SeparatorField, HorizontalRadioSelect
HorizontalRadioSelect, SeparatorField, YesNoField
)
from modoboa.parameters import forms as param_forms
from modoboa.parameters import tools as param_tools
from modoboa.parameters import forms as param_forms, tools as param_tools
from . import constants
@@ -40,8 +36,8 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
authentication_type = forms.ChoiceField(
label=ugettext_lazy("Authentication type"),
choices=[('local', ugettext_lazy("Local")),
('ldap', "LDAP")],
choices=[("local", ugettext_lazy("Local")),
("ldap", "LDAP")],
initial="local",
help_text=ugettext_lazy("The backend used for authentication"),
widget=HorizontalRadioSelect()
@@ -115,16 +111,16 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
ldap_auth_method = forms.ChoiceField(
label=ugettext_lazy("Authentication method"),
choices=[('searchbind', ugettext_lazy("Search and bind")),
('directbind', ugettext_lazy("Direct bind"))],
initial='searchbind',
choices=[("searchbind", ugettext_lazy("Search and bind")),
("directbind", ugettext_lazy("Direct bind"))],
initial="searchbind",
help_text=ugettext_lazy("Choose the authentication method to use"),
widget=forms.Select(attrs={"class": "form-control"})
)
ldap_bind_dn = forms.CharField(
label=ugettext_lazy("Bind DN"),
initial='',
initial="",
help_text=ugettext_lazy(
"The distinguished name to use when binding to the LDAP server. "
"Leave empty for an anonymous bind"
@@ -135,7 +131,7 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
ldap_bind_password = forms.CharField(
label=ugettext_lazy("Bind password"),
initial='',
initial="",
help_text=ugettext_lazy(
"The password to use when binding to the LDAP server "
"(with 'Bind DN')"
@@ -344,7 +340,7 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
def clean_ldap_user_dn_template(self):
tpl = self.cleaned_data["ldap_user_dn_template"]
try:
test = tpl % {"user": "toto"}
tpl % {"user": "toto"}
except (KeyError, ValueError):
raise forms.ValidationError(_("Invalid syntax"))
return tpl
+3 -1
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Core config for admin."""
from __future__ import unicode_literals
@@ -30,7 +32,7 @@ class CoreConfig(AppConfig):
load_core_settings()
# Import these to force registration of checks and signals
from . import checks # noqa:F401
from . import checks # NOQA:F401
from . import handlers
signals.post_migrate.connect(handlers.create_local_config, sender=self)
+3 -1
View File
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
# Import these to force registration of checks
from . import settings_checks # noqa:F401
from . import settings_checks # NOQA:F401
-1
View File
@@ -6,7 +6,6 @@ from django.conf import settings
from django.core.checks import Warning, register
from django.utils.translation import ugettext as _
W001 = Warning(
_("You have USE_TZ set to False, this may result in issues during "
"transitions between summer/winter time (ie the same local time occuring "
+7 -7
View File
@@ -1,6 +1,6 @@
# coding: utf-8
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function
from __future__ import print_function, unicode_literals
import argparse
import os
@@ -17,7 +17,7 @@ class Command(object):
A valid administrative command must inherit from this class.
"""
help = "No help available."
help = "No help available." # NOQA:A003
def __init__(self, commands, verbose=False):
self._commands = commands
@@ -90,10 +90,10 @@ def handle_command_line():
epilog="""Available commands:
%s
""" % "\n".join(["\t%s" % c for c in sorted(commands)]))
parser.add_argument('--verbose', action='store_true',
help='Activate verbose output')
parser.add_argument('command', type=str,
help='A valid command name')
parser.add_argument("--verbose", action="store_true",
help="Activate verbose output")
parser.add_argument("command", type=str,
help="A valid command name")
(args, remaining) = parser.parse_known_args()
if args.command not in commands:
+67 -68
View File
@@ -1,8 +1,8 @@
# coding: utf-8
# -*- coding: utf-8 -*-
"""A shortcut to deploy a fresh modoboa instance."""
from __future__ import unicode_literals, print_function
from __future__ import print_function, unicode_literals
import codecs
import getpass
@@ -10,26 +10,25 @@ import os
import shutil
import subprocess
import sys
from os.path import isfile
import dj_database_url
import django
from django.core import management
from django.template import Context, Template
from django.utils.encoding import smart_str
from django.utils.six.moves import input
from modoboa.core.commands import Command
from modoboa.lib.api_client import ModoAPIClient
try:
import pip
except ImportError:
sys.stderr.write("Error: pip is required to install extensions.\n")
sys.exit(2)
import django
from django.core import management
from django.template import Context, Template
from django.utils.encoding import smart_str
import dj_database_url
from modoboa.core.commands import Command
from modoboa.lib.api_client import ModoAPIClient
if sys.version_info < (3, 0, 0):
input = raw_input
DBCONN_TPL = """
'{{ conn_name }}': {
@@ -51,47 +50,47 @@ class DeployCommand(Command):
"""The ``deploy`` command."""
help = (
help = ( # NOQA:A003
"Create a fresh django project (calling startproject)"
" and apply Modoboa specific settings."
)
def __init__(self, *args, **kwargs):
super(DeployCommand, self).__init__(*args, **kwargs)
self._parser.add_argument('name', type=str,
help='The name of your Modoboa instance')
self._parser.add_argument("name", type=str,
help="The name of your Modoboa instance")
self._parser.add_argument(
'--collectstatic', action='store_true', default=False,
help='Run django collectstatic command'
"--collectstatic", action="store_true", default=False,
help="Run django collectstatic command"
)
self._parser.add_argument(
'--dburl', type=str, nargs="+", default=None,
help='A database-url with a name')
"--dburl", type=str, nargs="+", default=None,
help="A database-url with a name")
self._parser.add_argument(
'--domain', type=str, default=None,
help='The domain under which you want to deploy modoboa')
"--domain", type=str, default=None,
help="The domain under which you want to deploy modoboa")
self._parser.add_argument(
'--lang', type=str, default="en-us",
"--lang", type=str, default="en-us",
help="Set the default language"
)
self._parser.add_argument(
'--timezone', type=str, default="UTC",
"--timezone", type=str, default="UTC",
help="Set the local timezone"
)
self._parser.add_argument(
'--devel', action='store_true', default=False,
help='Create a development instance'
"--devel", action="store_true", default=False,
help="Create a development instance"
)
self._parser.add_argument(
'--extensions', type=str, nargs='*',
"--extensions", type=str, nargs="*",
help="The list of extension to deploy"
)
self._parser.add_argument(
'--dont-install-extensions', action='store_true', default=False,
help='Do not install extensions using pip'
"--dont-install-extensions", action="store_true", default=False,
help="Do not install extensions using pip"
)
self._parser.add_argument(
'--admin-username', default='admin',
"--admin-username", default="admin",
help="Username of the initial super administrator"
)
@@ -101,7 +100,7 @@ class DeployCommand(Command):
:param name: the command name
:param cwd: the directory where the command must be executed
"""
cmd = [sys.executable, 'manage.py', name]
cmd = [sys.executable, "manage.py", name]
cmd.extend(args)
if not self._verbose:
p = subprocess.Popen(
@@ -120,7 +119,7 @@ class DeployCommand(Command):
)
print("%s failed, check your configuration" % cmd, file=sys.stderr)
def ask_db_info(self, name='default'):
def ask_db_info(self, name="default"):
"""Prompt the user for database information
Gather all information required to create a new database
@@ -130,33 +129,33 @@ class DeployCommand(Command):
"""
print("Configuring database connection: %s" % name)
info = {
'conn_name': name,
'ENGINE': input(
'Database type (mysql, postgres or sqlite3): ')
"conn_name": name,
"ENGINE": input(
"Database type (mysql, postgres or sqlite3): ")
}
if info['ENGINE'] not in ['mysql', 'postgres', 'sqlite3']:
raise RuntimeError('Unsupported database engine')
if info["ENGINE"] not in ["mysql", "postgres", "sqlite3"]:
raise RuntimeError("Unsupported database engine")
if info['ENGINE'] == 'sqlite3':
info['ENGINE'] = 'django.db.backends.sqlite3'
info['NAME'] = '%s.db' % name
if info["ENGINE"] == "sqlite3":
info["ENGINE"] = "django.db.backends.sqlite3"
info["NAME"] = "%s.db" % name
return info
if info['ENGINE'] == 'postgres':
info['ENGINE'] = 'django.db.backends.postgresql_psycopg2'
if info["ENGINE"] == "postgres":
info["ENGINE"] = "django.db.backends.postgresql_psycopg2"
default_port = 5432
else:
info['ENGINE'] = 'django.db.backends.mysql'
info["ENGINE"] = "django.db.backends.mysql"
default_port = 3306
info['HOST'] = input("Database host (default: 'localhost'): ")
info['PORT'] = input(
info["HOST"] = input("Database host (default: 'localhost'): ")
info["PORT"] = input(
"Database port (default: '%s'): " % default_port)
# leave port setting empty, if default value is supplied and
# leave it to django
if info['PORT'] == default_port:
info['PORT'] = ''
info['NAME'] = input('Database name: ')
info['USER'] = input('Username: ')
info['PASSWORD'] = getpass.getpass('Password: ')
if info["PORT"] == default_port:
info["PORT"] = ""
info["NAME"] = input("Database name: ")
info["USER"] = input("Username: ")
info["PASSWORD"] = getpass.getpass("Password: ")
return info
def _get_extension_list(self):
@@ -188,9 +187,9 @@ class DeployCommand(Command):
def handle(self, parsed_args):
django.setup()
management.call_command(
'startproject', parsed_args.name, verbosity=False
"startproject", parsed_args.name, verbosity=False
)
path = "%(name)s/%(name)s" % {'name': parsed_args.name}
path = "%(name)s/%(name)s" % {"name": parsed_args.name}
sys.path.append(parsed_args.name)
conn_tpl = Template(DBCONN_TPL)
@@ -206,7 +205,7 @@ class DeployCommand(Command):
info = self.ask_db_info(conn_name)
# If we set this earlier, our fallback method will never
# be triggered
info['conn_name'] = conn_name
info["conn_name"] = conn_name
connections[conn_name] = conn_tpl.render(Context(info))
else:
connections["default"] = conn_tpl.render(
@@ -216,7 +215,7 @@ class DeployCommand(Command):
allowed_host = parsed_args.domain
else:
allowed_host = input(
'What will be the hostname used to access Modoboa? ')
"What will be the hostname used to access Modoboa? ")
if not allowed_host:
allowed_host = "localhost"
extra_settings = []
@@ -238,20 +237,20 @@ class DeployCommand(Command):
)
mod = __import__(
parsed_args.name, globals(), locals(), [smart_str('settings')]
parsed_args.name, globals(), locals(), [smart_str("settings")]
)
tpl = self._render_template(
"%s/settings.py.tpl" % self._templates_dir, {
'db_connections': connections,
'secret_key': mod.settings.SECRET_KEY,
'name': parsed_args.name,
'allowed_host': allowed_host,
'lang': parsed_args.lang,
'timezone': parsed_args.timezone,
'bower_components_dir': bower_components_dir,
'devmode': parsed_args.devel,
'extensions': extensions,
'extra_settings': extra_settings
"db_connections": connections,
"secret_key": mod.settings.SECRET_KEY,
"name": parsed_args.name,
"allowed_host": allowed_host,
"lang": parsed_args.lang,
"timezone": parsed_args.timezone,
"bower_components_dir": bower_components_dir,
"devmode": parsed_args.devel,
"extensions": extensions,
"extra_settings": extra_settings
}
)
with codecs.open("%s/settings.py" % path, "w", "utf-8") as fp:
@@ -264,7 +263,7 @@ class DeployCommand(Command):
if isfile("%s/settings.pyc" % path):
os.unlink("%s/settings.pyc" % path)
self._exec_django_command(
"migrate", parsed_args.name, '--noinput'
"migrate", parsed_args.name, "--noinput"
)
self._exec_django_command(
"load_initial_data", parsed_args.name,
@@ -272,7 +271,7 @@ class DeployCommand(Command):
)
if parsed_args.collectstatic:
self._exec_django_command(
"collectstatic", parsed_args.name, '--noinput'
"collectstatic", parsed_args.name, "--noinput"
)
self._exec_django_command(
"set_default_site", parsed_args.name, allowed_host
+5 -5
View File
@@ -1,6 +1,6 @@
# coding: utf-8
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function
from __future__ import print_function, unicode_literals
import sys
@@ -9,12 +9,12 @@ from . import Command
class HelpCommand(Command):
help = "Display the help message associated to a specific command"
help = "Display the help message associated to a specific command" # NOQA:A003
def __init__(self, *args, **kwargs):
super(HelpCommand, self).__init__(*args, **kwargs)
self._parser.add_argument('name', type=str,
help='A command name')
self._parser.add_argument("name", type=str,
help="A command name")
def handle(self, parsed_args):
if parsed_args.name not in self._commands:
+1 -1
View File
@@ -1,4 +1,4 @@
# coding: utf-8
# -*- coding: utf-8 -*-
"""Core application constants."""
+2
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Custom context processors."""
from __future__ import unicode_literals
+2
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
# Development settings
from __future__ import unicode_literals
+4 -2
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Extension management."""
from __future__ import unicode_literals
@@ -60,7 +62,7 @@ class ExtensionsPool(object):
:param ext: a class inheriting from ``Extension``
:param show: list the extension or not
"""
self.extensions[ext.name] = dict(cls=ext, show=show)
self.extensions[ext.name] = {"cls": ext, "show": show}
def get_extension(self, name):
"""Retrieve the current instance of an extension."""
@@ -79,7 +81,7 @@ class ExtensionsPool(object):
def load_extension(self, name):
"""Load a registered extension."""
__import__(name, locals(), globals(), [smart_str('modo_extension')])
__import__(name, locals(), globals(), [smart_str("modo_extension")])
extinstance = self.get_extension(name)
if extinstance is None:
return None
+2
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Factories for core application."""
from __future__ import unicode_literals
+4 -6
View File
@@ -1,18 +1,16 @@
# coding: utf-8
# -*- coding: utf-8 -*-
"""Core forms."""
from __future__ import unicode_literals
from django import forms
from django.contrib.auth import (
forms as auth_forms, get_user_model, password_validation
)
from django.db.models import Q
from django.utils.translation import ugettext as _, ugettext_lazy
from django.contrib.auth import forms as auth_forms
from django.contrib.auth import get_user_model
from django.contrib.auth import password_validation
from modoboa.core.models import User
from modoboa.parameters import tools as param_tools
+8 -11
View File
@@ -1,27 +1,24 @@
# -*- coding: utf-8 -*-
"""Modoboa core signal handlers."""
from __future__ import unicode_literals
import logging
from reversion import revisions as reversion
from reversion.models import Version
from django.contrib.sites import models as sites_models
from django.db.models import signals
from django.dispatch import receiver
from django.urls import reverse
from django.utils.encoding import smart_text
from django.utils.translation import ugettext as _
from django.contrib.sites import models as sites_models
from reversion import revisions as reversion
from reversion.models import Version
from modoboa.lib import exceptions
from modoboa.lib import permissions
from modoboa.lib import exceptions, permissions
from modoboa.lib.signals import get_request
from . import models
from . import signals as core_signals
from . import utils
from . import models, signals as core_signals, utils
@receiver(reversion.post_revision_commit)
+2
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import logging
-1
View File
@@ -1 +0,0 @@
from __future__ import unicode_literals
@@ -1 +0,0 @@
from __future__ import unicode_literals
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Inactive accounts cleanup tool."""
from __future__ import print_function, unicode_literals
@@ -9,14 +11,13 @@ from django.core.management.base import BaseCommand
from django.utils import timezone
from modoboa.parameters import tools as param_tools
from ... import models
class Command(BaseCommand):
"""Management command to clean inactive accounts."""
help = "Inactive accounts cleanup"
help = "Inactive accounts cleanup" # NOQA:A003
def add_arguments(self, parser):
"""Add extra arguments to command line."""
@@ -1,4 +1,4 @@
# coding: utf-8
# -*- coding: utf-8 -*-
from __future__ import print_function, unicode_literals
@@ -15,7 +15,7 @@ from modoboa.parameters import tools as param_tools
class Command(BaseCommand):
"""Command class."""
help = "Log table cleanup"
help = "Log table cleanup" # NOQA:A003
def add_arguments(self, parser):
"""Add extra arguments to command line."""
@@ -33,9 +33,9 @@ class Command(BaseCommand):
def handle(self, *args, **options):
if options["debug"]:
l = logging.getLogger("django.db.backends")
l.setLevel(logging.DEBUG)
l.addHandler(logging.StreamHandler())
log = logging.getLogger("django.db.backends")
log.setLevel(logging.DEBUG)
log.addHandler(logging.StreamHandler())
self.verbose = options["verbose"]
log_maximum_age = param_tools.get_global_parameter("log_maximum_age")
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Management command to communicate with public API."""
from __future__ import unicode_literals
@@ -7,14 +9,13 @@ from django.core.management.base import BaseCommand, CommandError
from modoboa.admin import models as admin_models
from modoboa.core.extensions import exts_pool
from modoboa.lib import api_client
from ... import models
class Command(BaseCommand):
"""Command class."""
help = "Communicate with Modoboa public API."
help = "Communicate with Modoboa public API." # NOQA:A003
def handle(self, *args, **options):
"""Command entry point."""
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*-
"""Management command to generate/update postfix map files."""
from __future__ import print_function, unicode_literals
import copy
@@ -6,16 +9,15 @@ import hashlib
import os
import sys
import dj_database_url
from django.conf import settings
from django.core.management.base import BaseCommand
from django.template import Context, Template
from django.utils import timezone
from django.utils.encoding import force_bytes
import dj_database_url
from ... import signals
from ... import utils
from ... import signals, utils
MAP_FILE_TEMPLATE = """# This file was generated on {{ date }} by running:
# {{ commandline }}
@@ -26,7 +28,7 @@ MAP_FILE_TEMPLATE = """# This file was generated on {{ date }} by running:
class Command(BaseCommand):
"""Command class."""
help = "Generate/update postfix map files."
help = "Generate/update postfix map files." # NOQA:A003
def add_arguments(self, parser):
"""Add extra arguments."""
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""A management command to load Modoboa initial data:
* Create a default super admin if none exists
@@ -12,19 +14,14 @@ from functools import reduce
from django.contrib.auth.models import Group
from django.core.management.base import BaseCommand
from modoboa.lib.cryptutils import random_key
from modoboa.lib.permissions import add_permissions_to_group
from ... import constants
from ... import extensions
from ... import models
from ... import signals
from ... import constants, extensions, models, signals
class Command(BaseCommand):
"""Command definition."""
help = "Load Modoboa initial data"
help = "Load Modoboa initial data" # NOQA:A003
def add_arguments(self, parser):
"""Add extra arguments to command."""
@@ -1,4 +1,5 @@
# coding: utf-8
# -*- coding: utf-8 -*-
"""
A simple managemenent command to update the default site.
@@ -7,16 +8,15 @@ See `https://docs.djangoproject.com/en/dev/ref/contrib/sites/`_.
from __future__ import unicode_literals
from django.core.management.base import BaseCommand, CommandError
from django.contrib.sites.models import Site
from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
"""Management command to set the default site."""
help = "Set default site (see django.contrib.sites)"
help = "Set default site (see django.contrib.sites)" # NOQA:A003
def add_arguments(self, parser):
"""Define command arguments."""
+2
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Core middlewares."""
from __future__ import unicode_literals
+3 -1
View File
@@ -1,12 +1,14 @@
# -*- coding: utf-8 -*-
"""Mocks used for testing."""
from __future__ import unicode_literals
import httmock
# Modoboa API mocks
@httmock.urlmatch(
netloc=r"api\.modoboa\.org$", path=r"^/1/instances/search/", method="post")
def modo_api_instance_search(url, request):
+19 -21
View File
@@ -1,35 +1,33 @@
# -*- coding: utf-8 -*-
"""Core models."""
from __future__ import unicode_literals
from email.header import Header
import re
from django.conf import settings
from django.db import models
from django.urls import reverse
from django.utils.encoding import (
python_2_unicode_compatible, smart_bytes, smart_text, force_str
)
from django.utils.functional import cached_property
from django.utils.translation import ugettext as _, ugettext_lazy
from django.contrib.auth.models import Group, AbstractUser
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from email.header import Header
import jsonfield
from reversion import revisions as reversion
from django.conf import settings
from django.contrib.auth.models import AbstractUser, Group
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.urls import reverse
from django.utils.encoding import (
force_str, python_2_unicode_compatible, smart_bytes, smart_text
)
from django.utils.functional import cached_property
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.core.password_hashers import get_password_hasher
from modoboa.lib.exceptions import (
PermDeniedException, InternalError, BadRequest, Conflict
BadRequest, Conflict, InternalError, PermDeniedException
)
from modoboa.parameters import tools as param_tools
from . import constants
from . import signals
from . import constants, signals
try:
from modoboa.lib.ldap_utils import LDAPAuthBackend
@@ -83,7 +81,7 @@ class User(AbstractUser):
class Meta(object):
ordering = ["username"]
index_together = [
['email', 'is_active']
["email", "is_active"]
]
password_expr = re.compile(r'\{([\w\-]+)\}(.+)')
@@ -412,7 +410,7 @@ class ObjectAccess(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
content_object = GenericForeignKey("content_type", "object_id")
is_owner = models.BooleanField(default=False)
class Meta(object):
+8 -6
View File
@@ -1,15 +1,17 @@
# -*- coding: utf-8 -*-
"""
Password hashers for Modoboa.
"""
from __future__ import unicode_literals
from modoboa.core.password_hashers.base import (
PLAINHasher, CRYPTHasher, MD5Hasher, SHA256Hasher
)
from modoboa.core.password_hashers.advanced import (
from modoboa.core.password_hashers.advanced import ( # NOQA:F401
BLFCRYPTHasher, MD5CRYPTHasher, SHA256CRYPTHasher, SHA512CRYPTHasher
)
from modoboa.core.password_hashers.base import ( # NOQA:F401
CRYPTHasher, MD5Hasher, PLAINHasher, SHA256Hasher
)
def get_password_hasher(scheme):
@@ -22,8 +24,8 @@ def get_password_hasher(scheme):
:return: The hasher class
"""
try:
scheme = scheme.replace('-', '')
hasher = globals()['%sHasher' % scheme.upper()]
scheme = scheme.replace("-", "")
hasher = globals()["%sHasher" % scheme.upper()]
except KeyError:
hasher = PLAINHasher
return hasher
+6 -5
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""
Advanced (ie. stronger) password hashers.
@@ -9,7 +11,6 @@ from __future__ import unicode_literals
from passlib.hash import bcrypt, md5_crypt, sha256_crypt, sha512_crypt
from modoboa.parameters import tools as param_tools
from .base import PasswordHasher
@@ -22,7 +23,7 @@ class BLFCRYPTHasher(PasswordHasher):
"""
@property
def scheme(self):
return '{BLF-CRYPT}' if self._target == 'local' else '{CRYPT}'
return "{BLF-CRYPT}" if self._target == "local" else "{CRYPT}"
def _b64encode(self, pwhash):
return pwhash
@@ -50,7 +51,7 @@ class MD5CRYPTHasher(PasswordHasher):
"""
@property
def scheme(self):
return '{MD5-CRYPT}' if self._target == 'local' else '{CRYPT}'
return "{MD5-CRYPT}" if self._target == "local" else "{CRYPT}"
def _b64encode(self, pwhash):
return pwhash
@@ -72,7 +73,7 @@ class SHA256CRYPTHasher(PasswordHasher):
"""
@property
def scheme(self):
return '{SHA256-CRYPT}' if self._target == 'local' else '{CRYPT}'
return "{SHA256-CRYPT}" if self._target == "local" else "{CRYPT}"
def _b64encode(self, pwhash):
return pwhash
@@ -96,7 +97,7 @@ class SHA512CRYPTHasher(PasswordHasher):
"""
@property
def scheme(self):
return '{SHA512-CRYPT}' if self._target == 'local' else '{CRYPT}'
return "{SHA512-CRYPT}" if self._target == "local" else "{CRYPT}"
def _b64encode(self, pwhash):
return pwhash
+18 -10
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""
Base password hashers.
@@ -9,11 +11,11 @@ from __future__ import unicode_literals
import base64
import crypt
import hashlib
from random import Random
import string
from random import Random
from django.utils.crypto import constant_time_compare
from django.utils.encoding import force_text, force_bytes
from django.utils.encoding import force_bytes, force_text
class PasswordHasher(object):
@@ -22,7 +24,7 @@ class PasswordHasher(object):
Base class of all hashers.
"""
def __init__(self, target='local'):
def __init__(self, target="local"):
self._target = target
def _encrypt(self, clearvalue, salt=None):
@@ -34,7 +36,7 @@ class PasswordHasher(object):
:param str pwhash: password hash
:return: base64 encoded hash or original hash
"""
if self._target == 'ldap':
if self._target == "ldap":
return base64.b64encode(pwhash)
return pwhash
@@ -52,7 +54,7 @@ class PasswordHasher(object):
:return: encrypted password
"""
pwhash = self._b64encode(self._encrypt(force_text(clearvalue)))
return '%s%s' % (self.scheme, force_text(pwhash))
return "%s%s" % (self.scheme, force_text(pwhash))
def verify(self, clearvalue, hashed_value):
"""Verify a password against a hashed value.
@@ -74,7 +76,7 @@ class PLAINHasher(PasswordHasher):
"""
@property
def scheme(self):
return '{PLAIN}'
return "{PLAIN}"
def _encrypt(self, clearvalue, salt=None):
return clearvalue
@@ -89,11 +91,17 @@ class CRYPTHasher(PasswordHasher):
"""
@property
def scheme(self):
return '{CRYPT}'
return "{CRYPT}"
def _encrypt(self, clearvalue, salt=None):
if salt is None:
salt = "".join(Random().sample(string.ascii_letters + string.digits, 2))
salt = "".join(
Random().sample(
string.ascii_letters +
string.digits,
2
)
)
return crypt.crypt(clearvalue, salt)
@@ -106,7 +114,7 @@ class MD5Hasher(PasswordHasher):
"""
@property
def scheme(self):
return '{MD5}'
return "{MD5}"
def _encrypt(self, clearvalue, salt=None):
obj = hashlib.md5(force_bytes(clearvalue))
@@ -122,7 +130,7 @@ class SHA256Hasher(PasswordHasher):
"""
@property
def scheme(self):
return '{SHA256}'
return "{SHA256}"
def _encrypt(self, clearvalue, salt=None):
return hashlib.sha256(force_bytes(clearvalue)).digest()
+6 -4
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Custom password validators."""
from __future__ import unicode_literals
@@ -19,7 +21,7 @@ class ComplexityValidator(object):
special_characters = "~!@#$%^&*()_+{}\":;,'[]"
condition = (
self.digits > 0 and
sum([1 for char in password if char.isdigit()]) < self.digits)
sum(1 for char in password if char.isdigit()) < self.digits)
if condition:
raise ValidationError(
ungettext(
@@ -29,7 +31,7 @@ class ComplexityValidator(object):
).format(self.digits))
condition = (
self.lower > 0 and
sum([1 for char in password if char.islower()]) < self.lower)
sum(1 for char in password if char.islower()) < self.lower)
if condition:
raise ValidationError(
ungettext(
@@ -40,7 +42,7 @@ class ComplexityValidator(object):
.format(self.lower))
condition = (
self.upper > 0 and
sum([1 for char in password if char.isupper()]) < self.upper)
sum(1 for char in password if char.isupper()) < self.upper)
if condition:
raise ValidationError(
ungettext(
@@ -51,7 +53,7 @@ class ComplexityValidator(object):
.format(self.upper))
condition = (
self.specials > 0 and
sum([1 for char in password if char in special_characters]) <
sum(1 for char in password if char in special_characters) <
self.specials)
if condition:
raise ValidationError(
+2
View File
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
"""Modoboa core signals."""
from __future__ import unicode_literals
-1
View File
@@ -1 +0,0 @@
from __future__ import unicode_literals

Some files were not shown because too many files have changed in this diff Show More