diff --git a/actions/ejabberd b/actions/ejabberd index d3abbf3b1..568d18b82 100755 --- a/actions/ejabberd +++ b/actions/ejabberd @@ -31,7 +31,7 @@ import sys import ruamel.yaml from plinth import action_utils -from plinth.modules.config import config +from plinth.modules import config from plinth.modules.letsencrypt import LIVE_DIRECTORY as LE_LIVE_DIRECTORY diff --git a/actions/letsencrypt b/actions/letsencrypt index b7f0e464f..7991fef51 100755 --- a/actions/letsencrypt +++ b/actions/letsencrypt @@ -31,7 +31,7 @@ import configobj from plinth import action_utils from plinth.errors import ActionError -from plinth.modules.config import config +from plinth.modules import config from plinth.modules import letsencrypt as le diff --git a/debian/changelog b/debian/changelog index 2456e742b..371213247 100644 --- a/debian/changelog +++ b/debian/changelog @@ -3,6 +3,9 @@ plinth (0.17.0) UNRELEASED; urgency=medium [ Joseph Nuthalapati ] * transmission: Enable Single Sign On. + [ Ravi Bolla ] + * config: Refactor config.py into views and form. + -- James Valleroy Mon, 13 Nov 2017 06:53:34 -0500 plinth (0.16.0) unstable; urgency=medium diff --git a/plinth/modules/config/__init__.py b/plinth/modules/config/__init__.py index 84b281557..5736ee916 100644 --- a/plinth/modules/config/__init__.py +++ b/plinth/modules/config/__init__.py @@ -19,13 +19,50 @@ Plinth module for basic system configuration """ -from . import config -from .config import init +from django.utils.translation import ugettext_lazy + +from plinth.menu import main_menu +from plinth.modules import firewall +from plinth import actions +from plinth.signals import domain_added +from plinth.modules.names import SERVICES + +import socket -__all__ = ['config', 'init'] version = 1 is_essential = True depends = ['firewall', 'names'] + + +def get_domainname(): + """Return the domainname""" + fqdn = socket.getfqdn() + return '.'.join(fqdn.split('.')[1:]) + + +def init(): + """Initialize the module""" + menu = main_menu.get('system') + menu.add_urlname(ugettext_lazy('Configure'), 'glyphicon-cog', + 'config:index') + + # Register domain with Name Services module. + domainname = get_domainname() + if domainname: + try: + domainname_services = firewall.get_enabled_services( + zone='external') + except actions.ActionError: + # This happens when firewalld is not installed. + # TODO: Are these services actually enabled? + domainname_services = [service[0] for service in SERVICES] + else: + domainname_services = None + + domain_added.send_robust(sender='config', domain_type='domainname', + name=domainname, + description=ugettext_lazy('Domain Name'), + services=domainname_services) diff --git a/plinth/modules/config/forms.py b/plinth/modules/config/forms.py new file mode 100644 index 000000000..701075c88 --- /dev/null +++ b/plinth/modules/config/forms.py @@ -0,0 +1,114 @@ +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +""" +Forms for basic system configuration +""" + + +from django import forms +from django.utils.translation import ugettext as _, ugettext_lazy +from django.core import validators +from django.core.exceptions import ValidationError +from django.conf import settings +from django.utils import translation + +from plinth import cfg +from plinth.utils import format_lazy + +import plinth +import logging +import os +import re + +logger = logging.getLogger(__name__) + +HOSTNAME_REGEX = r'^[a-zA-Z0-9]([-a-zA-Z0-9]{,61}[a-zA-Z0-9])?$' + + +class TrimmedCharField(forms.CharField): + """Trim the contents of a CharField""" + def clean(self, value): + """Clean and validate the field value""" + if value: + value = value.strip() + + return super(TrimmedCharField, self).clean(value) + + +def domain_label_validator(domainname): + """Validate domain name labels.""" + for label in domainname.split('.'): + if not re.match(HOSTNAME_REGEX, label): + raise ValidationError(_('Invalid domain name')) + + +class ConfigurationForm(forms.Form): + """Main system configuration form""" + # See: + # https://tools.ietf.org/html/rfc952 + # https://tools.ietf.org/html/rfc1035#section-2.3.1 + # https://tools.ietf.org/html/rfc1123#section-2 + # https://tools.ietf.org/html/rfc2181#section-11 + hostname = TrimmedCharField( + label=ugettext_lazy('Hostname'), + help_text=format_lazy(ugettext_lazy( + 'Hostname is the local name by which other devices on the local ' + 'network can reach your {box_name}. It must start and end with ' + 'an alphabet or a digit and have as interior characters only ' + 'alphabets, digits and hyphens. Total length must be 63 ' + 'characters or less.'), box_name=ugettext_lazy(cfg.box_name)), + validators=[ + validators.RegexValidator( + HOSTNAME_REGEX, + ugettext_lazy('Invalid hostname'))]) + + domainname = TrimmedCharField( + label=ugettext_lazy('Domain Name'), + help_text=format_lazy(ugettext_lazy( + 'Domain name is the global name by which other devices on the ' + 'Internet can reach your {box_name}. It must consist of labels ' + 'separated by dots. Each label must start and end with an ' + 'alphabet or a digit and have as interior characters only ' + 'alphabets, digits and hyphens. Length of each label must be 63 ' + 'characters or less. Total length of domain name must be 253 ' + 'characters or less.'), box_name=ugettext_lazy(cfg.box_name)), + required=False, + validators=[ + validators.RegexValidator( + r'^[a-zA-Z0-9]([-a-zA-Z0-9.]{,251}[a-zA-Z0-9])?$', + ugettext_lazy('Invalid domain name')), + domain_label_validator]) + + language = forms.ChoiceField( + label=ugettext_lazy('Language'), + help_text=ugettext_lazy( + 'Language for this web administration interface'), + required=False) + + def __init__(self, *args, **kwargs): + """Set limited language choices.""" + super().__init__(*args, **kwargs) + languages = [] + for language_code, language_name in settings.LANGUAGES: + locale_code = translation.to_locale(language_code) + plinth_dir = os.path.dirname(plinth.__file__) + if language_code == 'en' or os.path.exists( + os.path.join(plinth_dir, 'locale', locale_code)): + languages.append((language_code, language_name)) + + self.fields['language'].choices = languages diff --git a/plinth/modules/config/tests/test_config.py b/plinth/modules/config/tests/test_config.py index 87bb95dbd..0d4e60fd9 100644 --- a/plinth/modules/config/tests/test_config.py +++ b/plinth/modules/config/tests/test_config.py @@ -24,7 +24,7 @@ import unittest from plinth import __main__ as plinth_main -from ..config import ConfigurationForm +from ..forms import ConfigurationForm class TestConfig(unittest.TestCase): diff --git a/plinth/modules/config/urls.py b/plinth/modules/config/urls.py index 846f6e19f..f059f5928 100644 --- a/plinth/modules/config/urls.py +++ b/plinth/modules/config/urls.py @@ -21,8 +21,7 @@ URLs for the Configuration module from django.conf.urls import url -from . import config as views - +from . import views urlpatterns = [ url(r'^sys/config/$', views.index, name='index'), diff --git a/plinth/modules/config/config.py b/plinth/modules/config/views.py similarity index 54% rename from plinth/modules/config/config.py rename to plinth/modules/config/views.py index 0bcb9d4ab..5ba083abb 100644 --- a/plinth/modules/config/config.py +++ b/plinth/modules/config/views.py @@ -16,35 +16,23 @@ # """ -Plinth module for configuring hostname and domainname. +Plinth views for basic system configuration """ - -from django import forms -from django.contrib import messages -from django.core import validators -from django.core.exceptions import ValidationError -from django.conf import settings -from django.template.response import TemplateResponse from django.utils import translation -from django.utils.translation import ugettext as _, ugettext_lazy -import logging -import os -import re -import socket +from django.utils.translation import ugettext as _ +from django.template.response import TemplateResponse +from django.contrib import messages -import plinth +from plinth.modules import config +from .forms import ConfigurationForm +from plinth.signals import pre_hostname_change, post_hostname_change from plinth import actions -from plinth import cfg -from plinth.menu import main_menu +from plinth.signals import domain_added, domain_removed, domainname_change from plinth.modules import firewall from plinth.modules.names import SERVICES -from plinth.signals import pre_hostname_change, post_hostname_change -from plinth.signals import domainname_change -from plinth.signals import domain_added, domain_removed -from plinth.utils import format_lazy - -HOSTNAME_REGEX = r'^[a-zA-Z0-9]([-a-zA-Z0-9]{,61}[a-zA-Z0-9])?$' +import logging +import socket LOGGER = logging.getLogger(__name__) @@ -54,12 +42,6 @@ def get_hostname(): return socket.gethostname() -def get_domainname(): - """Return the domainname""" - fqdn = socket.getfqdn() - return '.'.join(fqdn.split('.')[1:]) - - def get_language(request): """Return the current language setting""" # TODO: Store the language per user in kvstore, @@ -71,111 +53,10 @@ def get_language(request): request.LANGUAGE_CODE) -class TrimmedCharField(forms.CharField): - """Trim the contents of a CharField""" - def clean(self, value): - """Clean and validate the field value""" - if value: - value = value.strip() - - return super(TrimmedCharField, self).clean(value) - - -def domain_label_validator(domainname): - """Validate domain name labels.""" - for label in domainname.split('.'): - if not re.match(HOSTNAME_REGEX, label): - raise ValidationError(_('Invalid domain name')) - - -class ConfigurationForm(forms.Form): - """Main system configuration form""" - # See: - # https://tools.ietf.org/html/rfc952 - # https://tools.ietf.org/html/rfc1035#section-2.3.1 - # https://tools.ietf.org/html/rfc1123#section-2 - # https://tools.ietf.org/html/rfc2181#section-11 - hostname = TrimmedCharField( - label=ugettext_lazy('Hostname'), - help_text=format_lazy(ugettext_lazy( - 'Hostname is the local name by which other devices on the local ' - 'network can reach your {box_name}. It must start and end with ' - 'an alphabet or a digit and have as interior characters only ' - 'alphabets, digits and hyphens. Total length must be 63 ' - 'characters or less.'), box_name=ugettext_lazy(cfg.box_name)), - validators=[ - validators.RegexValidator( - HOSTNAME_REGEX, - ugettext_lazy('Invalid hostname'))]) - - domainname = TrimmedCharField( - label=ugettext_lazy('Domain Name'), - help_text=format_lazy(ugettext_lazy( - 'Domain name is the global name by which other devices on the ' - 'Internet can reach your {box_name}. It must consist of labels ' - 'separated by dots. Each label must start and end with an ' - 'alphabet or a digit and have as interior characters only ' - 'alphabets, digits and hyphens. Length of each label must be 63 ' - 'characters or less. Total length of domain name must be 253 ' - 'characters or less.'), box_name=ugettext_lazy(cfg.box_name)), - required=False, - validators=[ - validators.RegexValidator( - r'^[a-zA-Z0-9]([-a-zA-Z0-9.]{,251}[a-zA-Z0-9])?$', - ugettext_lazy('Invalid domain name')), - domain_label_validator]) - - language = forms.ChoiceField( - label=ugettext_lazy('Language'), - help_text=ugettext_lazy( - 'Language for this web administration interface'), - required=False) - - def __init__(self, *args, **kwargs): - """Set limited language choices.""" - super().__init__(*args, **kwargs) - languages = [] - for language_code, language_name in settings.LANGUAGES: - locale_code = translation.to_locale(language_code) - plinth_dir = os.path.dirname(plinth.__file__) - if language_code == 'en' or os.path.exists( - os.path.join(plinth_dir, 'locale', locale_code)): - languages.append((language_code, language_name)) - - self.fields['language'].choices = languages - - -def init(): - """Initialize the module""" - menu = main_menu.get('system') - menu.add_urlname(ugettext_lazy('Configure'), 'glyphicon-cog', - 'config:index') - - # Register domain with Name Services module. - domainname = get_domainname() - if domainname: - try: - domainname_services = firewall.get_enabled_services( - zone='external') - except actions.ActionError: - # This happens when firewalld is not installed. - # TODO: Are these services actually enabled? - domainname_services = [service[0] for service in SERVICES] - else: - domainname_services = None - - domain_added.send_robust(sender='config', domain_type='domainname', - name=domainname, - description=ugettext_lazy('Domain Name'), - services=domainname_services) - - def index(request): """Serve the configuration form""" status = get_status(request) - form = None - if request.method == 'POST': form = ConfigurationForm(request.POST, initial=status, prefix='configuration') @@ -196,7 +77,7 @@ def index(request): def get_status(request): """Return the current status""" return {'hostname': get_hostname(), - 'domainname': get_domainname(), + 'domainname': config.get_domainname(), 'language': get_language(request)} @@ -235,7 +116,7 @@ def _apply_changes(request, old_status, new_status): def set_hostname(hostname): """Sets machine hostname to hostname""" old_hostname = get_hostname() - domainname = get_domainname() + domainname = config.get_domainname() # Hostname should be ASCII. If it's unicode but passed our # valid_hostname check, convert to ASCII. @@ -258,7 +139,7 @@ def set_hostname(hostname): def set_domainname(domainname): """Sets machine domain name to domainname""" - old_domainname = get_domainname() + old_domainname = config.get_domainname() # Domain name should be ASCII. If it's unicode, convert to ASCII. domainname = str(domainname) diff --git a/plinth/modules/letsencrypt/__init__.py b/plinth/modules/letsencrypt/__init__.py index f12c8b281..32eaac75a 100644 --- a/plinth/modules/letsencrypt/__init__.py +++ b/plinth/modules/letsencrypt/__init__.py @@ -29,7 +29,7 @@ from plinth import cfg from plinth.errors import ActionError from plinth.menu import main_menu from plinth.modules import names -from plinth.modules.config import config +from plinth.modules import config from plinth.utils import format_lazy from plinth import module_loader from plinth.signals import domainname_change, domain_added, domain_removed diff --git a/plinth/modules/letsencrypt/views.py b/plinth/modules/letsencrypt/views.py index 6f06aadf3..0968fa81c 100644 --- a/plinth/modules/letsencrypt/views.py +++ b/plinth/modules/letsencrypt/views.py @@ -29,7 +29,7 @@ from django.views.decorators.http import require_POST from plinth import actions from plinth.errors import ActionError -from plinth.modules.config import config +from plinth.modules import config from plinth.modules import letsencrypt logger = logging.getLogger(__name__) diff --git a/plinth/modules/openvpn/views.py b/plinth/modules/openvpn/views.py index be901cfc6..79e3d1372 100644 --- a/plinth/modules/openvpn/views.py +++ b/plinth/modules/openvpn/views.py @@ -30,7 +30,7 @@ import logging from .forms import OpenVpnForm from plinth import actions from plinth.modules import openvpn -from plinth.modules.config import config +from plinth.modules import config logger = logging.getLogger(__name__)