mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Merge branch 'master' into feature/codacy
This commit is contained in:
@@ -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
|
||||
@@ -11,6 +11,7 @@ DEV
|
||||
.ropeproject
|
||||
modoboa.iml
|
||||
.tox
|
||||
.eggs/
|
||||
modoboa_test
|
||||
.coverage
|
||||
build/
|
||||
|
||||
@@ -37,6 +37,8 @@ install:
|
||||
- pip install -r requirements.txt
|
||||
- pip install -r ldap-requirements.txt
|
||||
- pip install -r test-requirements.txt
|
||||
- if [ $DB = POSTGRESQL ]; then pip install -r postgresql-requirements.txt ; fi
|
||||
- if [ $DB = MYSQL ]; then pip install -r mysql-requirements.txt ; fi
|
||||
- python setup.py develop
|
||||
|
||||
before_script:
|
||||
|
||||
+1
-1
@@ -30,11 +30,11 @@ Main features
|
||||
* Reputation protection: `DNSBL <https://en.wikipedia.org/wiki/DNSBL>`_ checks, `DMARC <https://dmarc.org/>`_ `reports <https://github.com/modoboa/modoboa-dmarc>`_ and more
|
||||
* `Amavis <http://www.amavis.org>`_ `frontend <https://github.com/modoboa/modoboa-amavis>`_
|
||||
* `Webmail <https://github.com/modoboa/modoboa-webmail>`_
|
||||
* `Calendar <https://github.com/modoboa/modoboa-radicale>`_
|
||||
* `Address book <https://github.com/modoboa/modoboa-contacts>`_
|
||||
* `Per-user Sieve filters <https://github.com/modoboa/modoboa-sievefilters>`_
|
||||
* `Autoreply messages for Postfix <https://github.com/modoboa/modoboa-postfix-autoreply>`_
|
||||
* `Graphical statistics about email traffic <https://github.com/modoboa/modoboa-stats>`_
|
||||
* `Radicale <http://radicale.org/>`_ `management frontend <https://github.com/modoboa/modoboa-radicale>`_
|
||||
|
||||
*************
|
||||
Documentation
|
||||
|
||||
@@ -7,4 +7,3 @@ from modoboa.core.commands import handle_command_line
|
||||
|
||||
if __name__ == "__main__":
|
||||
handle_command_line()
|
||||
|
||||
|
||||
+63
-59
@@ -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)
|
||||
}
|
||||
|
||||
@@ -254,6 +254,25 @@ This parameter is optional but you must ensure it is set to ``False``
|
||||
The default configuration file provided by the ``modoboa-admin.py``
|
||||
command is properly configured.
|
||||
|
||||
**********************
|
||||
Logging authentication
|
||||
**********************
|
||||
|
||||
To trace login attempts to the web interface, Modoboa uses python
|
||||
`SysLogHandler <https://docs.python.org/3/library/logging.handlers.html#logging.handlers.SysLogHandler>`_
|
||||
so you can see them in your syslog authentication log file
|
||||
(`/var/log/auth.log` in most cases).
|
||||
|
||||
Depending on your configuration, you may have to edit the :file:`settings.py` file
|
||||
and add `'address': '/dev/log'` to the logging section::
|
||||
|
||||
'syslog-auth': {
|
||||
'class': 'logging.handlers.SysLogHandler',
|
||||
'facility': SysLogHandler.LOG_AUTH,
|
||||
'address': '/dev/log',
|
||||
'formatter': 'syslog'
|
||||
},
|
||||
|
||||
***********************
|
||||
External authentication
|
||||
***********************
|
||||
|
||||
@@ -32,15 +32,25 @@ Modoboa depends on external tools and some of them require compilation
|
||||
so you need a compiler and a few C libraries. Make sure to install the
|
||||
following system packages according to your distribution:
|
||||
|
||||
+------------------------------+------------------------------+
|
||||
|Debian/Ubuntu |CentOS |
|
||||
+==============================+==============================+
|
||||
|build-essential, python-dev, |gcc, gcc-c++, python-devel, |
|
||||
|libxml2-dev, libxslt-dev, |libxml2-devel, libxslt-devel, |
|
||||
|libjpeg-dev, librrd-dev, |libjpeg-turbo-devel, |
|
||||
|rrdtool, libffi-dev |rrdtool-devel, rrdtool, |
|
||||
| |libffi-devel |
|
||||
+------------------------------+------------------------------+
|
||||
+------------------------------+
|
||||
|Debian / Ubuntu |
|
||||
+==============================+
|
||||
|build-essential python-dev |
|
||||
|libxml2-dev libxslt-dev |
|
||||
|libjpeg-dev librrd-dev |
|
||||
|rrdtool libffi-dev |
|
||||
| |
|
||||
+------------------------------+
|
||||
|
||||
+------------------------------+
|
||||
|CentOS |
|
||||
+==============================+
|
||||
|gcc gcc-c++ python-devel |
|
||||
|libxml2-devel libxslt-devel |
|
||||
|libjpeg-turbo-devel |
|
||||
|rrdtool-devel rrdtool |
|
||||
|libffi-devel |
|
||||
+------------------------------+
|
||||
|
||||
Then, install Modoboa:
|
||||
|
||||
|
||||
@@ -168,15 +168,8 @@ uwsgi:
|
||||
|
||||
.. warning::
|
||||
|
||||
Some distributions, including Debian and Ubuntu, ship an outdated
|
||||
version of uWSGI that does not conform to the WSGI
|
||||
specification. Versions prior to 1.2.6 do not call close on the
|
||||
response object after handling a request. In those cases the
|
||||
request_finished signal isn’t sent. This can result in idle
|
||||
connections to database and memcache servers.
|
||||
|
||||
Use uwsgi 1.2.6 or newer. If you do not, you *will* run into
|
||||
problems. Modoboa will fail in obscure ways.
|
||||
Use uwsgi 1.2.6 or newer. If you do not, you *will* run into
|
||||
problems. Modoboa will fail in obscure ways.
|
||||
|
||||
To use this setup, first `download and install uwsgi
|
||||
<http://uwsgi-docs.readthedocs.org/en/latest/WSGIquickstart.html>`_.
|
||||
|
||||
@@ -185,6 +185,12 @@ Replace ``<driver>`` and ``<path>`` by your values.
|
||||
|
||||
If ``transport_maps`` contains ``sql-relaydomains-transport.cf``, remove it.
|
||||
|
||||
.. warning::
|
||||
|
||||
If you make use of postfix's `proxymap server
|
||||
<http://www.postfix.org/proxymap.8.html>`_, you must also update
|
||||
the ``proxy_read_maps`` setting.
|
||||
|
||||
Reload postfix.
|
||||
|
||||
Add the following cron job in order to generate DKIM keys::
|
||||
|
||||
+1
-2
@@ -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
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
default_app_config = "modoboa.admin.apps.AdminConfig"
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django import forms
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Forms related to forwards management."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Forms related to import operations."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -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
@@ -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 +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
|
||||
|
||||
|
||||
@@ -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,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Django management command to export admin objects."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from backports import csv
|
||||
import sys
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import six
|
||||
|
||||
from modoboa.core.models import User
|
||||
from modoboa.core.extensions import exts_pool
|
||||
from modoboa.core.models import User
|
||||
from .... import models
|
||||
|
||||
if six.PY2:
|
||||
from backports import csv
|
||||
else:
|
||||
import csv
|
||||
|
||||
|
||||
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 +53,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,58 +1,69 @@
|
||||
# -*- 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 chardet.universaldetector import UniversalDetector
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
from django.utils import six
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from modoboa.core import models as core_models
|
||||
from modoboa.core.extensions import exts_pool
|
||||
from modoboa.lib.exceptions import Conflict
|
||||
|
||||
from .... import signals
|
||||
|
||||
if six.PY2:
|
||||
from backports import csv
|
||||
else:
|
||||
import csv
|
||||
|
||||
|
||||
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."""
|
||||
parser.add_argument(
|
||||
"--sepchar", type=str, default=";",
|
||||
"--sepchar", type=six.text_type, default=";",
|
||||
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",
|
||||
default=False, help="Encrypt provided passwords.")
|
||||
parser.add_argument(
|
||||
"files", type=str, nargs="+", help="CSV files to import.")
|
||||
"files", type=six.text_type, nargs="+", help="CSV files to import.")
|
||||
|
||||
def _import(self, filename, options):
|
||||
def _import(self, filename, options, encoding="utf-8"):
|
||||
"""Import domains or identities."""
|
||||
superadmin = (
|
||||
core_models.User.objects.filter(is_superuser=True).first()
|
||||
)
|
||||
if not os.path.exists(filename):
|
||||
if not os.path.isfile(filename):
|
||||
raise CommandError("File not found")
|
||||
|
||||
num_lines = sum(1 for line in open(filename) if line)
|
||||
num_lines = sum(
|
||||
1 for line in io.open(filename, encoding=encoding) if line
|
||||
)
|
||||
pbar = progressbar.ProgressBar(
|
||||
widgets=[
|
||||
progressbar.Percentage(), progressbar.Bar(), progressbar.ETA()
|
||||
], maxval=num_lines
|
||||
).start()
|
||||
with io.open(filename, encoding="utf8") as f:
|
||||
with io.open(filename, encoding=encoding, newline="") as f:
|
||||
reader = csv.reader(f, delimiter=options["sepchar"])
|
||||
i = 0
|
||||
for row in reader:
|
||||
@@ -81,4 +92,39 @@ class ImportCommand(BaseCommand):
|
||||
"""Command entry point."""
|
||||
exts_pool.load_all()
|
||||
for filename in options["files"]:
|
||||
self._import(filename, options)
|
||||
try:
|
||||
with transaction.atomic():
|
||||
self._import(filename, options)
|
||||
except CommandError as exc:
|
||||
raise exc
|
||||
except UnicodeDecodeError:
|
||||
self.stdout.write(self.style.NOTICE(
|
||||
_("CSV file is not encoded in UTF-8, attempting to guess "
|
||||
"encoding")
|
||||
))
|
||||
detector = UniversalDetector()
|
||||
with io.open(filename, "rb") as fp:
|
||||
for line in fp:
|
||||
detector.feed(line)
|
||||
if detector.done:
|
||||
break
|
||||
detector.close()
|
||||
|
||||
self.stdout.write(self.style.NOTICE(
|
||||
_("Reading CSV file using %(encoding)s encoding") %
|
||||
detector.result
|
||||
))
|
||||
try:
|
||||
with transaction.atomic():
|
||||
self._import(
|
||||
filename, options,
|
||||
encoding=detector.result["encoding"]
|
||||
)
|
||||
except UnicodeDecodeError as exc:
|
||||
six.raise_from(
|
||||
CommandError(
|
||||
_("Unable to decode CSV file using %(encoding)s "
|
||||
"encoding") % detector.result
|
||||
),
|
||||
exc
|
||||
)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Management command to create DKIM keys."""
|
||||
|
||||
from __future__ import print_function, unicode_literals
|
||||
@@ -11,6 +13,7 @@ from modoboa.lib import sysutils
|
||||
from modoboa.parameters import tools as param_tools
|
||||
|
||||
from .... import models
|
||||
from .... import signals
|
||||
|
||||
|
||||
class ManageDKIMKeys(BaseCommand):
|
||||
@@ -50,3 +53,5 @@ class ManageDKIMKeys(BaseCommand):
|
||||
enable_dkim=True, dkim_private_key_path="")
|
||||
for domain in qset:
|
||||
self.create_new_dkim_key(domain)
|
||||
if qset.exists():
|
||||
signals.new_dkim_keys.send(sender=self.__class__, domains=qset)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
@@ -205,8 +206,21 @@ class Domain(AdminObject):
|
||||
@property
|
||||
def bind_format_dkim_public_key(self):
|
||||
"""Return DKIM public key using Bind format."""
|
||||
return "{}._domainkey 10800 IN TXT \"v=DKIM1; k=rsa; p={}\"".format(
|
||||
self.dkim_key_selector, self.dkim_public_key)
|
||||
record = "v=DKIM1;k=rsa;p=%s" % self.dkim_public_key
|
||||
# TXT records longer than 255 characters need split into chunks
|
||||
# split record into 74 character chunks (+2 for indent, +2 for quotes(")
|
||||
# == 78 characters) to make more readable in text editors.
|
||||
split_record = []
|
||||
while record:
|
||||
if len(record) > 74:
|
||||
split_record.append(" \"%s\"" % record[:74])
|
||||
record = record[74:]
|
||||
else:
|
||||
split_record.append(" \"%s\"" % record)
|
||||
break
|
||||
record = "\n".join(split_record)
|
||||
return "{}._domainkey.{}. 10800 IN TXT (\n{})".format(
|
||||
self.dkim_key_selector, self.name, record)
|
||||
|
||||
def add_admin(self, account):
|
||||
"""Add a new administrator to this domain.
|
||||
@@ -245,9 +259,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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""MX records storage."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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):
|
||||
@@ -32,7 +33,10 @@ class DomainSerializer(serializers.ModelSerializer):
|
||||
model = models.Domain
|
||||
fields = (
|
||||
"pk", "name", "quota", "default_mailbox_quota", "enabled", "type",
|
||||
"enable_dkim", "dkim_key_selector", "dkim_key_length",
|
||||
"dkim_public_key"
|
||||
)
|
||||
read_only_fields = ("pk", "dkim_public_key", )
|
||||
|
||||
def validate_name(self, value):
|
||||
"""Check name constraints."""
|
||||
@@ -62,8 +66,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
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Modoboa admin signals."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
@@ -30,3 +32,4 @@ get_domain_form_instances = django.dispatch.Signal(
|
||||
# get_domain_tags = django.dispatch.Signal(providing_args=["domain"])
|
||||
import_object = django.dispatch.Signal(providing_args=["objtype"])
|
||||
use_external_recipients = django.dispatch.Signal(providing_args=["recipients"])
|
||||
new_dkim_keys = django.dispatch.Signal(providing_args=["domains"])
|
||||
|
||||
@@ -161,6 +161,24 @@ Domains.prototype = {
|
||||
$(document).trigger('domain_listing_refresh');
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a type to let the server know what kind of object we expect.
|
||||
*
|
||||
* @this Listing
|
||||
*/
|
||||
get_load_page_args: function() {
|
||||
var args = Admin.prototype.get_load_page_args.call(this);
|
||||
|
||||
if (this.navobj.getbaseurl() === "list") {
|
||||
args.objtype = "domain";
|
||||
this.options.eor_message = gettext("No more domain to show");
|
||||
} else {
|
||||
args.objtype = "quota";
|
||||
this.options.eor_message = gettext("No more quota to show");
|
||||
}
|
||||
return args;
|
||||
},
|
||||
|
||||
/**
|
||||
* Navigation callback: default.
|
||||
*
|
||||
@@ -305,6 +323,7 @@ Domains.prototype = {
|
||||
},
|
||||
success_callback: $.proxy(this.reload_listing, this)
|
||||
});
|
||||
$(document).trigger('domwizard_init');
|
||||
},
|
||||
|
||||
domainform_cb: function() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="modal-dialog {% block modal_css_classes %}{% endblock %}">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">{% trans "Close" %}</span></button>
|
||||
@@ -13,7 +13,7 @@
|
||||
</div>
|
||||
<div class="alert alert-info" style="overflow-wrap: break-word;">
|
||||
<strong>{% trans "Bind/named format" %}</strong><br>
|
||||
{{ domain.bind_format_dkim_public_key }}
|
||||
<span style="white-space: pre">{{ domain.bind_format_dkim_public_key }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
admin = new Domains({
|
||||
load_page_url: "{% url 'admin:_domain_list' %}"
|
||||
load_page_url: "{% url 'admin:domain_page' %}"
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
|
||||
echo "2.12"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,7 +8,6 @@ import copy
|
||||
import json
|
||||
|
||||
import dns.resolver
|
||||
from mock import patch
|
||||
|
||||
from django.test import override_settings
|
||||
from django.urls import reverse
|
||||
@@ -16,20 +15,24 @@ 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
|
||||
|
||||
try:
|
||||
# mock is part of the Python (>= 3.3) standard library
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
# fall back to the mock backport
|
||||
import mock
|
||||
|
||||
|
||||
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,13 +72,13 @@ 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)
|
||||
|
||||
@patch.object(dns.resolver.Resolver, "query")
|
||||
@patch("socket.getaddrinfo")
|
||||
@mock.patch.object(dns.resolver.Resolver, "query")
|
||||
@mock.patch("socket.getaddrinfo")
|
||||
def test_create_domain_with_mx_check(self, mock_getaddrinfo, mock_query):
|
||||
"""Check domain creation when MX check is activated."""
|
||||
self.set_global_parameter("enable_admin_limits", False, app="limits")
|
||||
@@ -84,7 +87,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 +143,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 +172,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 +194,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 +237,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 +392,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 +569,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 +607,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 +629,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 +692,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 +726,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)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
domain;domain1.com;1000;100;True
|
||||
domain;dómain2.com;2000;200;True
|
||||
|
@@ -0,0 +1,3 @@
|
||||
domain;domain1.com;1000;100;True
|
||||
domain;dómain2.com;2000;200;True
|
||||
domain;dómain2.com;2000;200;True
|
||||
|
@@ -0,0 +1,2 @@
|
||||
domain;domain1.com;1000;100;True
|
||||
domain;dómain2.com;2000;200;True
|
||||
|
@@ -9,7 +9,6 @@ import shutil
|
||||
import tempfile
|
||||
|
||||
import dns.resolver
|
||||
from mock import patch
|
||||
from testfixtures import compare
|
||||
|
||||
from django.core.management import call_command
|
||||
@@ -20,17 +19,23 @@ 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
|
||||
|
||||
try:
|
||||
# mock is part of the Python (>= 3.3) standard library
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
# fall back to the mock backport
|
||||
import mock
|
||||
|
||||
|
||||
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 +181,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"),
|
||||
@@ -206,8 +211,8 @@ class DomainTestCase(ModoTestCase):
|
||||
self.assertContains(response, "value=\"500\"")
|
||||
self.assertContains(response, "value=\"50\"")
|
||||
|
||||
@patch.object(dns.resolver.Resolver, "query")
|
||||
@patch("socket.getaddrinfo")
|
||||
@mock.patch.object(dns.resolver.Resolver, "query")
|
||||
@mock.patch("socket.getaddrinfo")
|
||||
def test_create_and_check_mx(self, mock_getaddrinfo, mock_query):
|
||||
"""Check for authorized MX record."""
|
||||
reseller = core_factories.UserFactory(
|
||||
@@ -347,22 +352,30 @@ 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)
|
||||
|
||||
def test_page_loader(self):
|
||||
"""Test page loader view."""
|
||||
url = reverse("admin:domain_page")
|
||||
response = self.ajax_get(url)
|
||||
self.assertIn("handle_mailboxes", response)
|
||||
response = self.ajax_get("{}?objtype=quota".format(url))
|
||||
self.assertIn("progress-bar", response["rows"])
|
||||
|
||||
|
||||
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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -2,25 +2,34 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
import dns.resolver
|
||||
from mock import patch
|
||||
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import CommandError
|
||||
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
|
||||
|
||||
try:
|
||||
# mock is part of the Python (>= 3.3) standard library
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
# fall back to the mock backport
|
||||
import mock
|
||||
|
||||
|
||||
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(
|
||||
@@ -79,8 +88,8 @@ domainalias; domalias1.com; domain1.com; True
|
||||
response,
|
||||
"Default mailbox quota cannot be greater than domain quota")
|
||||
|
||||
@patch.object(dns.resolver.Resolver, "query")
|
||||
@patch("socket.getaddrinfo")
|
||||
@mock.patch.object(dns.resolver.Resolver, "query")
|
||||
@mock.patch("socket.getaddrinfo")
|
||||
def test_domain_import_with_mx_check(self, mock_getaddrinfo, mock_query):
|
||||
"""Check domain import when MX check is enabled."""
|
||||
reseller = core_factories.UserFactory(
|
||||
@@ -135,7 +144,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 +210,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 +221,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 +239,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 +250,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 +283,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 +304,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,
|
||||
@@ -345,3 +357,72 @@ dómain; dómªin2.com; 1000; 100; True
|
||||
self.assertEqual(dom.default_mailbox_quota, 100)
|
||||
self.assertTrue(dom.enabled)
|
||||
self.assertTrue(admin.is_owner(dom))
|
||||
|
||||
def test_import_command_missing_file(self):
|
||||
with mock.patch("os.path.isfile") as mock_isfile:
|
||||
mock_isfile.return_value = False
|
||||
with self.assertRaises(CommandError) as cm:
|
||||
call_command("modo", "import", "test.csv")
|
||||
ex_message = cm.exception.messages
|
||||
self.assertEqual(ex_message, "File not found")
|
||||
|
||||
def test_import_command(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"test_data/import_domains.csv"
|
||||
)
|
||||
call_command("modo", "import", test_file)
|
||||
|
||||
admin = User.objects.get(username="admin")
|
||||
|
||||
dom = Domain.objects.get(name="domain1.com")
|
||||
self.assertEqual(dom.quota, 1000)
|
||||
self.assertEqual(dom.default_mailbox_quota, 100)
|
||||
self.assertTrue(dom.enabled)
|
||||
self.assertTrue(admin.is_owner(dom))
|
||||
|
||||
dom = Domain.objects.get(name="dómain2.com")
|
||||
self.assertEqual(dom.quota, 2000)
|
||||
self.assertEqual(dom.default_mailbox_quota, 200)
|
||||
self.assertTrue(dom.enabled)
|
||||
self.assertTrue(admin.is_owner(dom))
|
||||
|
||||
def test_import_command_non_utf8(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"test_data/import_domains_iso8859.csv"
|
||||
)
|
||||
call_command("modo", "import", test_file)
|
||||
dom = Domain.objects.get(name="dómain2.com")
|
||||
self.assertEqual(dom.quota, 2000)
|
||||
|
||||
def test_import_command_duplicates(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"test_data/import_domains_duplicates.csv"
|
||||
)
|
||||
with self.assertRaises(CommandError) as cm:
|
||||
call_command("modo", "import", test_file)
|
||||
ex_message = cm.exception.messages
|
||||
self.assertTrue(ex_message.startswith("Object already exists: "))
|
||||
|
||||
def test_import_command_duplicates_continue(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"test_data/import_domains_duplicates.csv"
|
||||
)
|
||||
call_command("modo", "import", "--continue-if-exists", test_file)
|
||||
|
||||
admin = User.objects.get(username="admin")
|
||||
|
||||
dom = Domain.objects.get(name="domain1.com")
|
||||
self.assertEqual(dom.quota, 1000)
|
||||
self.assertEqual(dom.default_mailbox_quota, 100)
|
||||
self.assertTrue(dom.enabled)
|
||||
self.assertTrue(admin.is_owner(dom))
|
||||
|
||||
dom = Domain.objects.get(name="dómain2.com")
|
||||
self.assertEqual(dom.quota, 2000)
|
||||
self.assertEqual(dom.default_mailbox_quota, 200)
|
||||
self.assertTrue(dom.enabled)
|
||||
self.assertTrue(admin.is_owner(dom))
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Management command tests."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
@@ -6,16 +8,19 @@ import os
|
||||
import shutil
|
||||
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, models
|
||||
|
||||
from .. import factories
|
||||
from .. import models
|
||||
try:
|
||||
# mock is part of the Python (>= 3.3) standard library
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
# fall back to the mock backport
|
||||
import mock
|
||||
|
||||
|
||||
@override_settings(
|
||||
@@ -24,7 +29,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()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Test map files generation."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -5,29 +5,32 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
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
|
||||
|
||||
try:
|
||||
# mock is part of the Python (>= 3.3) standard library
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
# fall back to the mock backport
|
||||
import mock
|
||||
|
||||
|
||||
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")
|
||||
@@ -52,11 +55,11 @@ class MXTestCase(ModoTestCase):
|
||||
cls.localconfig.save()
|
||||
models.MXRecord.objects.all().delete()
|
||||
|
||||
@patch("gevent.socket.gethostbyname")
|
||||
@patch("socket.getaddrinfo")
|
||||
@patch.object(dns.resolver.Resolver, "query")
|
||||
@mock.patch("gevent.socket.gethostbyname")
|
||||
@mock.patch("socket.getaddrinfo")
|
||||
@mock.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
|
||||
@@ -80,11 +83,11 @@ class MXTestCase(ModoTestCase):
|
||||
qs = models.MXRecord.objects.filter(domain=self.domain)
|
||||
self.assertEqual(id_, qs[0].id)
|
||||
|
||||
@patch("gevent.socket.gethostbyname")
|
||||
@patch("socket.getaddrinfo")
|
||||
@patch.object(dns.resolver.Resolver, "query")
|
||||
@mock.patch("gevent.socket.gethostbyname")
|
||||
@mock.patch("socket.getaddrinfo")
|
||||
@mock.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
|
||||
@@ -105,8 +108,8 @@ class MXTestCase(ModoTestCase):
|
||||
management.call_command(
|
||||
"modo", "check_mx", "--domain", "toto.com")
|
||||
|
||||
@patch("socket.getaddrinfo")
|
||||
@patch.object(dns.resolver.Resolver, "query")
|
||||
@mock.patch("socket.getaddrinfo")
|
||||
@mock.patch.object(dns.resolver.Resolver, "query")
|
||||
def test_get_domain_mx_list_logging(self, mock_query, mock_getaddrinfo):
|
||||
mock_query.side_effect = utils.mock_dns_query_result
|
||||
mock_getaddrinfo.side_effect = utils.mock_ip_query_result
|
||||
@@ -133,7 +136,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 +144,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")
|
||||
@@ -152,11 +155,11 @@ class DNSBLTestCase(ModoTestCase):
|
||||
name="modoboa.com", enable_dns_checks=False)
|
||||
models.DNSBLResult.objects.all().delete()
|
||||
|
||||
@patch("gevent.socket.gethostbyname")
|
||||
@patch("socket.getaddrinfo")
|
||||
@patch.object(dns.resolver.Resolver, "query")
|
||||
@mock.patch("gevent.socket.gethostbyname")
|
||||
@mock.patch("socket.getaddrinfo")
|
||||
@mock.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
|
||||
@@ -173,11 +176,11 @@ class DNSBLTestCase(ModoTestCase):
|
||||
self.assertFalse(self.domain.uses_a_reserved_tld)
|
||||
self.assertTrue(self.domain2.uses_a_reserved_tld)
|
||||
|
||||
@patch("gevent.socket.gethostbyname")
|
||||
@patch("socket.getaddrinfo")
|
||||
@patch.object(dns.resolver.Resolver, "query")
|
||||
@mock.patch("gevent.socket.gethostbyname")
|
||||
@mock.patch("socket.getaddrinfo")
|
||||
@mock.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
|
||||
@@ -189,11 +192,11 @@ class DNSBLTestCase(ModoTestCase):
|
||||
print(message.subject)
|
||||
self.assertEqual(len(mail.outbox), 2)
|
||||
|
||||
@patch("gevent.socket.gethostbyname")
|
||||
@patch("socket.getaddrinfo")
|
||||
@patch.object(dns.resolver.Resolver, "query")
|
||||
@mock.patch("gevent.socket.gethostbyname")
|
||||
@mock.patch("socket.getaddrinfo")
|
||||
@mock.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
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,11 +26,13 @@ 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,
|
||||
name="domain_delete"),
|
||||
url(r'^domains/page/$', domain_views.get_next_page,
|
||||
name="domain_page"),
|
||||
]
|
||||
|
||||
urlpatterns += [
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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),
|
||||
@@ -108,6 +108,19 @@ def domains(request, tplname="admin/domains.html"):
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(
|
||||
lambda u: u.has_perm("admin.view_domains") or
|
||||
u.has_perm("admin.view_mailboxes") or
|
||||
u.has_perm("admin.add_domain")
|
||||
)
|
||||
def get_next_page(request):
|
||||
"""Return the next page of the domain or quota list."""
|
||||
if request.GET.get("objtype", "domain") == "domain":
|
||||
return _domains(request)
|
||||
return list_quotas(request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("core.add_user")
|
||||
def domains_list(request):
|
||||
@@ -118,8 +131,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 +175,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 +228,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"])
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
# -*- 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
|
||||
|
||||
if six.PY2:
|
||||
from backports import csv
|
||||
else:
|
||||
import csv
|
||||
|
||||
|
||||
def _export(content, filename):
|
||||
"""Export a csv file's content
|
||||
@@ -52,7 +57,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 +85,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()
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Import related views."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from backports import csv
|
||||
|
||||
import io
|
||||
|
||||
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 import six
|
||||
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
|
||||
|
||||
if six.PY2:
|
||||
from backports import csv
|
||||
else:
|
||||
import csv
|
||||
|
||||
|
||||
@reversion.create_revision()
|
||||
@@ -39,8 +44,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 +68,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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""App config for core."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,19 @@ import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from os.path import isfile
|
||||
|
||||
try:
|
||||
import pip
|
||||
except ImportError:
|
||||
sys.stderr.write("Error: pip is required to install extensions.\n")
|
||||
sys.exit(2)
|
||||
import dj_database_url
|
||||
|
||||
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 django.utils.six.moves import input
|
||||
|
||||
from modoboa.core.commands import Command
|
||||
from modoboa.lib.api_client import ModoAPIClient
|
||||
|
||||
if sys.version_info < (3, 0, 0):
|
||||
input = raw_input
|
||||
from modoboa.lib.sysutils import exec_cmd
|
||||
|
||||
DBCONN_TPL = """
|
||||
'{{ conn_name }}': {
|
||||
@@ -51,47 +44,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 +94,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 +113,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 +123,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 +181,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 +199,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 +209,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 = []
|
||||
@@ -227,9 +220,12 @@ class DeployCommand(Command):
|
||||
extensions = [(extension, extension.replace("-", "_"))
|
||||
for extension in extensions]
|
||||
if not parsed_args.dont_install_extensions:
|
||||
pip_args = (
|
||||
["install"] + [extension[0] for extension in extensions])
|
||||
pip.main(pip_args)
|
||||
cmd = (
|
||||
sys.executable +
|
||||
" -m pip install " +
|
||||
" ".join([extension[0] for extension in extensions])
|
||||
)
|
||||
exec_cmd(cmd, capture_output=False)
|
||||
extra_settings = self.find_extra_settings(extensions)
|
||||
extensions = [extension[1] for extension in extensions]
|
||||
|
||||
@@ -238,20 +234,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 +260,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 +268,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
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
# coding: utf-8
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Core application constants."""
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Custom context processors."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Development settings
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Factories for core application."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
@@ -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")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user