diff --git a/.travis.yml b/.travis.yml
index 40e25fa3b..92f9b63fb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -38,7 +38,7 @@ before_script:
script:
- python ./tests.py
- cd test_project
- - coverage run --source ../modoboa manage.py test modoboa.core modoboa.lib modoboa.admin.tests modoboa.limits modoboa.relaydomains
+ - coverage run --source ../modoboa manage.py test modoboa.core modoboa.admin.tests modoboa.limits modoboa.relaydomains
after_success:
- codecov
diff --git a/modoboa/core/app_settings.py b/modoboa/core/app_settings.py
index 697fcfb7c..471b36478 100644
--- a/modoboa/core/app_settings.py
+++ b/modoboa/core/app_settings.py
@@ -15,14 +15,7 @@ from . import constants
def enabled_applications():
- """Return the list of installed extensions.
-
- We check if the table exists before trying to fetch activated
- extensions because the admin module is always imported by Django,
- even before the database exists (example: the first ``syncdb``).
-
- :return: a list
- """
+ """Return the list of installed extensions."""
from modoboa.core.extensions import exts_pool
result = [("user", "user")]
diff --git a/modoboa/core/static/core/js/settings.js b/modoboa/core/static/core/js/settings.js
index a20ebac58..0e926fbcc 100644
--- a/modoboa/core/static/core/js/settings.js
+++ b/modoboa/core/static/core/js/settings.js
@@ -4,6 +4,8 @@
* @constructor
* @param {Object} options - instance options
*/
+/* global TwocolsNav */
+
var Settings = function(options) {
TwocolsNav.call(this, options);
};
@@ -40,7 +42,7 @@ Settings.prototype = {
* Navigation callback: extensions.
*/
extensions_callback: function(data) {
- this.select_left_menu();
+ this.select_left_menu();
if (data.content) {
$('#' + this.options.divid).html(data.content);
}
diff --git a/modoboa/core/templates/core/parameters.html b/modoboa/core/templates/core/parameters.html
index a533a9a5b..b0efc3da8 100644
--- a/modoboa/core/templates/core/parameters.html
+++ b/modoboa/core/templates/core/parameters.html
@@ -1,8 +1,7 @@
{% load i18n lib_tags %}
diff --git a/modoboa/core/tests/test_core.py b/modoboa/core/tests/test_core.py
index 4fc047042..f659116fd 100644
--- a/modoboa/core/tests/test_core.py
+++ b/modoboa/core/tests/test_core.py
@@ -57,44 +57,6 @@ class ChangeDefaultAdminTestCase(TestCase):
self.client.login(username="modoadmin", password="password"))
-class DashboardTestCase(ModoTestCase):
- """Dashboard tests."""
-
- @classmethod
- def setUpTestData(cls):
- """Create some data."""
- super(DashboardTestCase, cls).setUpTestData()
- cls.dadmin = factories.UserFactory(
- username="admin@test.com", groups=("DomainAdmins",)
- )
- cls.user = factories.UserFactory(
- username="user@test.com", groups=("SimpleUsers",)
- )
-
- def test_access(self):
- """Load dashboard."""
- url = reverse("core:dashboard")
- response = self.client.get(url)
- self.assertEqual(response.status_code, 200)
- self.assertIn("Latest news", response.content)
- self.client.logout()
- self.client.login(username=self.dadmin.username, password="toto")
- response = self.client.get(url)
- self.assertEqual(response.status_code, 200)
- self.assertIn("Latest news", response.content)
- self.client.logout()
- self.client.login(username=self.user.username, password="toto")
- response = self.client.get(url)
- self.assertEqual(response.status_code, 302)
-
- def test_root_dispatch(self):
- """Check root dispatching."""
- url = reverse("core:root")
- response = self.client.get(url)
- self.assertEqual(response.status_code, 302)
- self.assertTrue(response.url.endswith(reverse("core:dashboard")))
-
-
@skipIf(NO_LDAP, 'No ldap module installed')
class LDAPTestCaseMixin(object):
"""Set of methods used to test LDAP features."""
diff --git a/modoboa/core/tests/test_views.py b/modoboa/core/tests/test_views.py
new file mode 100644
index 000000000..e50dbe12c
--- /dev/null
+++ b/modoboa/core/tests/test_views.py
@@ -0,0 +1,162 @@
+"""Core views test cases."""
+
+from testfixtures import compare
+
+from django import forms
+from django.core.urlresolvers import reverse
+
+from modoboa.lib.tests import ModoTestCase
+from modoboa.parameters import forms as param_forms
+from modoboa.parameters import tools as param_tools
+
+from .. import factories
+from .. import models
+
+
+class DashboardTestCase(ModoTestCase):
+ """Dashboard tests."""
+
+ @classmethod
+ def setUpTestData(cls):
+ """Create some data."""
+ super(DashboardTestCase, cls).setUpTestData()
+ cls.dadmin = factories.UserFactory(
+ username="admin@test.com", groups=("DomainAdmins",)
+ )
+ cls.user = factories.UserFactory(
+ username="user@test.com", groups=("SimpleUsers",)
+ )
+
+ def test_access(self):
+ """Load dashboard."""
+ url = reverse("core:dashboard")
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("Latest news", response.content)
+ self.client.logout()
+ self.client.login(username=self.dadmin.username, password="toto")
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("Latest news", response.content)
+ self.client.logout()
+ self.client.login(username=self.user.username, password="toto")
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 302)
+
+ def test_root_dispatch(self):
+ """Check root dispatching."""
+ url = reverse("core:root")
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 302)
+ self.assertTrue(response.url.endswith(reverse("core:dashboard")))
+
+
+class SettingsTestCase(ModoTestCase):
+ """Settings tests."""
+
+ settings_sample = {
+ "core-check_new_versions": "True",
+ "core-log_maximum_age": "365",
+ "core-ldap_auth_method": "searchbind",
+ "limits-deflt_domain_mailbox_aliases_limit": "0",
+ "core-send_statistics": "True",
+ "core-default_top_redirection": "user",
+ "core-sender_address": "noreply@modoboa.org",
+ "admin-auto_account_removal": "False",
+ "core-ldap_server_port": "389",
+ "core-rounds_number": "70000",
+ "core-ldap_bind_password": "",
+ "limits-deflt_domain_domain_aliases_limit": "0",
+ "core-secret_key": ":?j3]QPWo!.'_c4n",
+ "relaydomains-master_cf_path": "/etc/postfix/master.cf",
+ "limits-deflt_domain_domain_admins_limit": "0",
+ "limits-enable_admin_limits": "True",
+ "core-ldap_bind_dn": "",
+ "core-enable_api_communication": "True",
+ "limits-deflt_user_domain_admins_limit": "0",
+ "limits-enable_domain_limits": "False",
+ "csrfmiddlewaretoken": "SGgMVZsA4TPqoiV786TMST6xgOlhAf4F",
+ "limits-deflt_user_mailboxes_limit": "0",
+ "core-password_scheme": "sha512crypt",
+ "core-items_per_page": "30",
+ "limits-deflt_user_mailbox_aliases_limit": "0",
+ "limits-deflt_domain_mailboxes_limit": "0",
+ "core-ldap_is_active_directory": "False",
+ "core-ldap_group_type": "posixgroup",
+ "limits-deflt_user_domain_aliases_limit": "0",
+ "core-top_notifications_check_interval": "30",
+ "core-ldap_password_attribute": "userPassword",
+ "admin-auto_create_domain_and_mailbox": "True",
+ "admin-enable_dnsbl_checks": "True",
+ "admin-default_domain_quota": "0",
+ "core-ldap_server_address": "localhost",
+ "core-authentication_type": "local",
+ "core-ldap_admin_groups": "",
+ "core-ldap_groups_search_base": "",
+ "admin-enable_mx_checks": "True",
+ "core-ldap_search_base": "",
+ "admin-valid_mxs": "",
+ "limits-deflt_user_domains_limit": "0",
+ "core-ldap_search_filter": "(mail=%(user)s)",
+ "core-ldap_secured": "False",
+ "core-ldap_user_dn_template": ""
+ }
+
+ def test_get_settings(self):
+ """Test settings display."""
+ url = reverse("core:parameters")
+ response = self.ajax_get(url)
+ for app in ["core", "admin", "limits", "relaydomains"]:
+ self.assertIn('data-app="{}"'.format(app), response["content"])
+
+ def test_save_settings(self):
+ """Test settings save."""
+ url = reverse("core:parameters")
+ response = self.client.post(url, self.settings_sample, format="json")
+ self.assertEqual(response.status_code, 200)
+ self.settings_sample["core-rounds_number"] = ""
+ response = self.client.post(url, self.settings_sample, format="json")
+ self.assertEqual(response.status_code, 400)
+ compare(response.json(), {
+ "form_errors": {"rounds_number": ["This field is required."]},
+ "prefix": "core"
+ })
+
+
+class UserSettings(param_forms.UserParametersForm):
+ """Stupid user settings form."""
+
+ app = "core"
+
+ test = forms.CharField()
+
+
+class PreferencesTestCase(ModoTestCase):
+ """Test user preferences."""
+
+ @classmethod
+ def setUpClass(cls):
+ """Register user form."""
+ super(PreferencesTestCase, cls).setUpClass()
+ param_tools.registry.add("user", UserSettings, "Test")
+
+ @classmethod
+ def tearDownClass(cls):
+ """Remove user class."""
+ super(PreferencesTestCase, cls).tearDownClass()
+ del param_tools.registry._registry["user"]["core"]
+
+ def test_get_preferences(self):
+ """Test preferences display."""
+ url = reverse("core:user_preferences")
+ self.ajax_get(url)
+
+ def test_save_preferences(self):
+ """Test preferences save."""
+ url = reverse("core:user_preferences")
+ response = self.client.post(url, {"core-test": "toto"}, format="json")
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.json(), "Preferences saved")
+
+ admin = models.User.objects.get(username="admin")
+ self.assertEqual(admin.parameters.get_value("test"), "toto")
diff --git a/modoboa/core/urls.py b/modoboa/core/urls.py
index f2050f9f4..39f5b53b3 100644
--- a/modoboa/core/urls.py
+++ b/modoboa/core/urls.py
@@ -17,10 +17,8 @@ urlpatterns = patterns(
url(r'^core/$', 'modoboa.core.views.admin.viewsettings',
name="index"),
- url(r'^core/parameters/$', 'modoboa.core.views.admin.viewparameters',
- name="parameter_listl"),
- url(r'^core/parameters/save/$', 'modoboa.core.views.admin.saveparameters',
- name="parameter_save"),
+ url(r'^core/parameters/$', 'modoboa.core.views.admin.parameters',
+ name="parameters"),
url(r'^core/info/$', 'modoboa.core.views.admin.information',
name="information"),
url(r'^core/logs/$', 'modoboa.core.views.admin.logs', name="log_list"),
diff --git a/modoboa/core/views/admin.py b/modoboa/core/views/admin.py
index 288c4465b..aab2ce6b6 100644
--- a/modoboa/core/views/admin.py
+++ b/modoboa/core/views/admin.py
@@ -28,7 +28,22 @@ def viewsettings(request, tplname='core/settings_header.html'):
@login_required
@user_passes_test(lambda u: u.is_superuser)
-def viewparameters(request, tplname='core/parameters.html'):
+def parameters(request, tplname="core/parameters.html"):
+ """View to display and save global parameters."""
+ if request.method == "POST":
+ forms = param_tools.registry.get_forms(
+ "global", request.POST, localconfig=request.localconfig)
+ for formdef in forms:
+ form = formdef["form"]
+ if form.is_valid():
+ form.save()
+ form.to_django_settings()
+ continue
+ return render_to_json_response(
+ {"form_errors": form.errors, "prefix": form.app}, status=400
+ )
+ request.localconfig.save()
+ return render_to_json_response(_("Parameters saved"))
return render_to_json_response({
"left_selection": "parameters",
"content": _render_to_string(request, tplname, {
@@ -38,25 +53,6 @@ def viewparameters(request, tplname='core/parameters.html'):
})
-@login_required
-@user_passes_test(lambda u: u.is_superuser)
-def saveparameters(request):
- """Save all parameters."""
- forms = param_tools.registry.get_forms(
- "global", request.POST, localconfig=request.localconfig)
- for formdef in forms:
- form = formdef["form"]
- if form.is_valid():
- form.save()
- form.to_django_settings()
- continue
- return render_to_json_response(
- {'form_errors': form.errors, 'prefix': form.app}, status=400
- )
- request.localconfig.save()
- return render_to_json_response(_("Parameters saved"))
-
-
@login_required
@user_passes_test(lambda u: u.is_superuser)
def information(request, tplname="core/information.html"):
diff --git a/modoboa/lib/form_utils.py b/modoboa/lib/form_utils.py
index b1cd0e54b..d8914c4d4 100644
--- a/modoboa/lib/form_utils.py
+++ b/modoboa/lib/form_utils.py
@@ -232,9 +232,8 @@ class DynamicForm(object):
class TabForms(object):
-
"""
- Simple forms container
+ Simple forms container.
This class tries to encapsulate multiple forms that will be
displayed using tabs. It is different from a classical formset
diff --git a/modoboa/parameters/templates/parameters/base_form.html b/modoboa/parameters/templates/parameters/base_form.html
index 040ec24f6..381151c24 100644
--- a/modoboa/parameters/templates/parameters/base_form.html
+++ b/modoboa/parameters/templates/parameters/base_form.html
@@ -1,7 +1,7 @@
{% load form_tags %}
{% for form in forms %}
diff --git a/modoboa/parameters/tools.py b/modoboa/parameters/tools.py
index b0202c913..9addff441 100644
--- a/modoboa/parameters/tools.py
+++ b/modoboa/parameters/tools.py
@@ -63,6 +63,7 @@ class Registry(object):
if not data["formclass"].has_access(**kwargs):
continue
result.append({
+ "app": app,
"label": data["label"],
"form": data["formclass"](*args, **kwargs)
})
diff --git a/modoboa/static/js/twocols_nav.js b/modoboa/static/js/twocols_nav.js
index c7dda2be5..85d164401 100644
--- a/modoboa/static/js/twocols_nav.js
+++ b/modoboa/static/js/twocols_nav.js
@@ -3,6 +3,9 @@
*
* @constructor
*/
+
+/* global Listing */
+
var TwocolsNav = function(options) {
Listing.call(this, options);
};
@@ -33,6 +36,17 @@ TwocolsNav.prototype = {
success_cb: $.proxy(this.save_cb, this)
});
}, this));
+ $(document).on('submit', '#parameters_form', function(evt) {
+ simple_ajax_form_post(evt, {
+ formid: $(this).attr('id'),
+ modal: false,
+ reload_on_success: false,
+ success_cb: $.proxy(this.save_cb, this),
+ error_cb: function(data) {
+ $('a[data-app="' + data.prefix + '"]').tab('show');
+ }
+ });
+ });
},
update_content: function(data) {
diff --git a/test_project/test_project/settings.py b/test_project/test_project/settings.py
index e4ca61d1d..f0e525d0b 100644
--- a/test_project/test_project/settings.py
+++ b/test_project/test_project/settings.py
@@ -60,6 +60,7 @@ MODOBOA_APPS = (
'modoboa.admin',
'modoboa.limits',
'modoboa.relaydomains',
+ 'modoboa.parameters',
)
INSTALLED_APPS += MODOBOA_APPS
@@ -75,6 +76,7 @@ MIDDLEWARE_CLASSES = (
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ 'modoboa.core.middleware.LocalConfigMiddleware',
'modoboa.lib.middleware.AjaxLoginRedirect',
'modoboa.lib.middleware.CommonExceptionCatcher',
'modoboa.lib.middleware.RequestCatcherMiddleware',