From d1b59cc31278376444799b7b2ed91509011fab76 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 27 Oct 2016 18:53:17 +0200 Subject: [PATCH] WIP see #951 --- .travis.yml | 2 +- modoboa/core/app_settings.py | 9 +- modoboa/core/static/core/js/settings.js | 4 +- modoboa/core/templates/core/parameters.html | 5 +- .../core/templates/core/user_preferences.html | 4 +- modoboa/core/tests/test_core.py | 38 ---- modoboa/core/tests/test_views.py | 162 ++++++++++++++++++ modoboa/core/urls.py | 6 +- modoboa/core/views/admin.py | 36 ++-- modoboa/lib/form_utils.py | 3 +- .../templates/parameters/base_form.html | 2 +- modoboa/parameters/tools.py | 1 + modoboa/static/js/twocols_nav.js | 14 ++ test_project/test_project/settings.py | 2 + 14 files changed, 208 insertions(+), 80 deletions(-) create mode 100644 modoboa/core/tests/test_views.py 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 %}

{% trans "Parameters" %} {% trans "Configure Modoboa" %}


-
{% csrf_token %} +{% csrf_token %} {% include "parameters/base_form.html" %}
- +
- diff --git a/modoboa/core/templates/core/user_preferences.html b/modoboa/core/templates/core/user_preferences.html index 7d599207e..f38ba518f 100644 --- a/modoboa/core/templates/core/user_preferences.html +++ b/modoboa/core/templates/core/user_preferences.html @@ -3,11 +3,11 @@
{% if forms %}
+ id="parameters_form"> {% csrf_token %} {% include "parameters/base_form.html" %}
- +
{% else %}
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',