diff --git a/plinth/modules/email_server/forms.py b/plinth/modules/email_server/forms.py
index 2108b91fb..1351aba0d 100644
--- a/plinth/modules/email_server/forms.py
+++ b/plinth/modules/email_server/forms.py
@@ -1,7 +1,14 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
+"""
+Forms for the email app.
+"""
+
from django import forms
+from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
+from . import aliases as aliases_module
+
class EmailServerForm(forms.Form):
domain = forms.CharField(label=_('domain'), max_length=256)
@@ -10,6 +17,48 @@ class EmailServerForm(forms.Form):
super().__init__(*args, **kwargs)
-class AliasCreationForm(forms.Form):
- email_name = forms.CharField(label=_('New alias (without @domain)'),
- max_length=50)
+class AliasCreateForm(forms.Form):
+ """Form to create a new alias."""
+ alias = forms.CharField(label=_('New alias (without @domain)'),
+ max_length=50)
+
+ def clean_alias(self):
+ """Return the checked value for alias."""
+ value = self.data['alias']
+ if aliases_module.exists(value):
+ raise ValidationError('Alias is already taken')
+
+ return value
+
+
+class AliasListForm(forms.Form):
+ """Form to list/enable/disable/delete current aliases."""
+ aliases = forms.MultipleChoiceField(label=_('Aliases'),
+ widget=forms.CheckboxSelectMultiple)
+
+ def __init__(self, aliases, *args, **kwargs):
+ """Populate the choices for aliases."""
+ super().__init__(*args, **kwargs)
+ enabled_aliases = [(alias.email_name, alias.email_name)
+ for alias in aliases if alias.enabled]
+ disabled_aliases = [(alias.email_name, alias.email_name)
+ for alias in aliases if not alias.enabled]
+ choices = []
+ if enabled_aliases:
+ choices.append((_('Enabled'), enabled_aliases))
+
+ if disabled_aliases:
+ choices.append((_('Disabled'), disabled_aliases))
+
+ self.fields['aliases'].choices = choices
+
+ def clean(self):
+ """Add the pressed button to cleaned data."""
+ cleaned_data = super().clean()
+ buttons = [key[4:] for key in self.data if key.startswith('btn_')]
+ if len(buttons) != 1 or buttons[0] not in ('enable', 'disable',
+ 'delete'):
+ raise ValidationError('Invalid button pressed')
+
+ cleaned_data['action'] = buttons[0]
+ return cleaned_data
diff --git a/plinth/modules/email_server/templates/email_alias.html b/plinth/modules/email_server/templates/email_alias.html
index 0923cf9c3..7cbed7efd 100644
--- a/plinth/modules/email_server/templates/email_alias.html
+++ b/plinth/modules/email_server/templates/email_alias.html
@@ -15,16 +15,13 @@
{% else %}
@@ -34,10 +31,9 @@
{% endblock %}
diff --git a/plinth/modules/email_server/views.py b/plinth/modules/email_server/views.py
index 1b7afb81f..0d47b18f9 100644
--- a/plinth/modules/email_server/views.py
+++ b/plinth/modules/email_server/views.py
@@ -1,20 +1,23 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
-import io
-import itertools
+"""
+Views for the email app.
+"""
import pwd
from django.core.exceptions import ValidationError
from django.http import HttpResponseBadRequest
from django.shortcuts import redirect
-from django.utils.html import escape
+from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic.base import TemplateView, View
+from django.views.generic.edit import FormView
import plinth.actions
import plinth.utils
from plinth.views import AppView, render_tabs
-from . import aliases, audit, forms
+from . import aliases as aliases_module
+from . import audit, forms
class TabMixin(View):
@@ -125,131 +128,88 @@ class MyMailView(TemplateView):
return self.render_to_response(self.get_context_data())
-class AliasView(TemplateView):
-
- class Checkboxes:
-
- def __init__(self, post=None, initial=None):
- self.models = initial
- self.post = post
- self.cleaned_data = {}
- # HTML rendering
- self.sb = io.StringIO()
- self.counter = 0
-
- def render(self):
- if self.models is None:
- raise RuntimeError('Uninitialized form')
- if self.sb.tell() > 0:
- raise RuntimeError('render has been called')
-
- enabled = [a.email_name for a in self.models if a.enabled]
- disabled = [a.email_name for a in self.models if not a.enabled]
-
- self._render_fieldset(enabled, _('Enabled aliases'))
- self._render_fieldset(disabled, _('Disabled aliases'))
-
- return self.sb.getvalue()
-
- def _render_fieldset(self, email_names, legend):
- if len(email_names) > 0:
- self.sb.write('')
-
- def _render_boxes(self, email_names):
- for email_name in email_names:
- input_id = 'cb_alias_%d' % self._count()
- value = escape(email_name)
- self.sb.write('')
-
- self.sb.write('' % (input_id, value))
-
- self.sb.write('' % (input_id, value))
-
- self.sb.write('
')
-
- def _count(self):
- self.counter += 1
- return self.counter
-
- def is_valid(self):
- lst = list(filter(None, self.post.getlist('alias')))
- if not lst:
- return False
- else:
- self.cleaned_data['alias'] = lst
- return True
+class AliasView(FormView):
+ """View to create, list, enable, disable and delete aliases.
+ This view has two forms. Form to list (and manage) existing aliases, and a
+ form to create a new aliases. When GET operation is used, both forms
+ created and template is rendered. When POST operation is used, the form
+ posted is detected using hidden form values and the appropriate form is
+ initialized for the FormView base class to work with.
+ """
template_name = 'email_alias.html'
- form_classes = (forms.AliasCreationForm, Checkboxes)
+ form_classes = (forms.AliasCreateForm, forms.AliasListForm)
+ success_url = reverse_lazy('email_server:aliases')
+
+ def __init__(self, *args, **kwargs):
+ """Initialize the view."""
+ super().__init__(*args, **kwargs)
+ self.posted_form = None
+
+ def get_form_class(self):
+ """Return form class to build."""
+ if self.posted_form == 'create':
+ return forms.AliasCreateForm
+
+ return forms.AliasListForm
+
+ def get_form_kwargs(self):
+ """Send aliases to list form."""
+ kwargs = super().get_form_kwargs()
+ if self.posted_form != 'create':
+ kwargs['aliases'] = self._get_current_aliases()
+
+ return kwargs
+
+ def _get_uid(self):
+ """Return the UID of the user that made the request."""
+ return pwd.getpwnam(self.request.user.username).pw_uid
+
+ def _get_current_aliases(self):
+ """Return current list of aliases."""
+ return aliases_module.get(self._get_uid())
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
- context['form'] = forms.AliasCreationForm()
-
- uid = pwd.getpwnam(self.request.user.username).pw_uid
- models = aliases.get(uid)
- if len(models) > 0:
- form = AliasView.Checkboxes(initial=models)
- context['alias_boxes'] = form.render()
+ if isinstance(context['form'], forms.AliasCreateForm):
+ context['create_form'] = context['form']
+ aliases = self._get_current_aliases()
+ context['list_form'] = forms.AliasListForm(aliases)
else:
- context['no_alias'] = True
+ context['create_form'] = forms.AliasCreateForm()
+ context['list_form'] = context['form']
+
return context
- def _find_form(self, post):
- form_name = post.get('form')
- for cls in self.form_classes:
- if cls.__name__ == form_name:
- return cls(post)
- raise ValidationError('Form was unspecified')
+ def post(self, request, *args, **kwargs):
+ """Find which form was submitted before proceeding."""
+ self.posted_form = request.POST.get('form')
+ return super().post(request, *args, **kwargs)
- def _find_button(self, post):
- key_filter = (k for k in post.keys() if k.startswith('btn_'))
- lst = list(itertools.islice(key_filter, 2))
- if len(lst) != 1:
- raise ValidationError('Bad post data')
- if not isinstance(lst[0], str):
- raise ValidationError('Bad post data')
- return lst[0][len('btn_'):]
+ def form_valid(self, form):
+ """Handle a valid submission."""
+ if isinstance(form, forms.AliasListForm):
+ self._list_form_valid(form)
+ elif isinstance(form, forms.AliasCreateForm):
+ self._create_form_valid(form)
- def post(self, request):
- form = self._find_form(request.POST)
- button = self._find_button(request.POST)
- if not form.is_valid():
- raise ValidationError('Form invalid')
+ return super().form_valid(form)
- if isinstance(form, AliasView.Checkboxes):
- if button not in ('delete', 'disable', 'enable'):
- raise ValidationError('Bad button')
- return self.alias_operation_form_valid(form, button)
+ def _list_form_valid(self, form):
+ """Handle a valid alias list form operation."""
+ alias_list = form.cleaned_data['aliases']
+ action = form.cleaned_data['action']
+ uid = self._get_uid()
+ if action == 'delete':
+ aliases_module.delete(uid, alias_list)
+ elif action == 'disable':
+ aliases_module.set_disabled(uid, alias_list)
+ elif action == 'enable':
+ aliases_module.set_enabled(uid, alias_list)
- if isinstance(form, forms.AliasCreationForm):
- if button != 'add':
- raise ValidationError('Bad button')
- return self.alias_creation_form_valid(form, button)
-
- raise RuntimeError('Unknown form')
-
- def alias_operation_form_valid(self, form, button):
- uid = pwd.getpwnam(self.request.user.username).pw_uid
- alias_list = form.cleaned_data['alias']
- if button == 'delete':
- aliases.delete(uid, alias_list)
- elif button == 'disable':
- aliases.set_disabled(uid, alias_list)
- elif button == 'enable':
- aliases.set_enabled(uid, alias_list)
- return self.render_to_response(self.get_context_data())
-
- def alias_creation_form_valid(self, form, button):
- uid = pwd.getpwnam(self.request.user.username).pw_uid
- aliases.put(uid, form.cleaned_data['email_name'])
- return self.render_to_response(self.get_context_data())
+ def _create_form_valid(self, form):
+ """Handle a valid create alias form operation."""
+ aliases_module.put(self._get_uid(), form.cleaned_data['alias'])
class TLSView(TabMixin, TemplateView):