email_server: aliases: Using Django forms instead of custom forms

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2021-10-13 16:28:14 -07:00 committed by James Valleroy
parent b0e460b433
commit f5d1cb474f
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
3 changed files with 134 additions and 129 deletions

View File

@ -1,7 +1,14 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
"""
Forms for the email app.
"""
from django import forms from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from . import aliases as aliases_module
class EmailServerForm(forms.Form): class EmailServerForm(forms.Form):
domain = forms.CharField(label=_('domain'), max_length=256) domain = forms.CharField(label=_('domain'), max_length=256)
@ -10,6 +17,48 @@ class EmailServerForm(forms.Form):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
class AliasCreationForm(forms.Form): class AliasCreateForm(forms.Form):
email_name = forms.CharField(label=_('New alias (without @domain)'), """Form to create a new alias."""
alias = forms.CharField(label=_('New alias (without @domain)'),
max_length=50) 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

View File

@ -15,16 +15,13 @@
{% else %} {% else %}
<form action="{{ request.path }}" method="post"> <form action="{{ request.path }}" method="post">
{% csrf_token %} {% csrf_token %}
{{ alias_boxes|safe }} {{ list_form|bootstrap }}
<input type="hidden" name="form" value="Checkboxes">
<input type="hidden" name="form" value="list">
<input class="btn btn-secondary" type="submit" name="btn_disable" <input class="btn btn-secondary" type="submit" name="btn_disable"
value="{% trans 'Disable selected' %}"> value="{% trans 'Disable selected' %}">
<input class="btn btn-secondary" type="submit" name="btn_enable" <input class="btn btn-secondary" type="submit" name="btn_enable"
value="{% trans 'Enable selected' %}"> value="{% trans 'Enable selected' %}">
<input class="btn btn-danger" type="submit" name="btn_delete" <input class="btn btn-danger" type="submit" name="btn_delete"
value="{% trans 'Delete selected' %}"> value="{% trans 'Delete selected' %}">
</form> </form>
@ -34,10 +31,9 @@
<form action="{{ request.path }}" method="post"> <form action="{{ request.path }}" method="post">
{% csrf_token %} {% csrf_token %}
{{ form|bootstrap }} {{ create_form|bootstrap }}
<input type="hidden" name="form" value="AliasCreationForm"> <input type="hidden" name="form" value="create">
<input class="btn btn-primary" type="submit" name="btn_add" <input class="btn btn-primary" type="submit" value="{% trans 'Add' %}">
value="{% trans 'Add' %}">
</form> </form>
{% endblock %} {% endblock %}

View File

@ -1,20 +1,23 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
import io """
import itertools Views for the email app.
"""
import pwd import pwd
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.http import HttpResponseBadRequest from django.http import HttpResponseBadRequest
from django.shortcuts import redirect 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.utils.translation import gettext_lazy as _
from django.views.generic.base import TemplateView, View from django.views.generic.base import TemplateView, View
from django.views.generic.edit import FormView
import plinth.actions import plinth.actions
import plinth.utils import plinth.utils
from plinth.views import AppView, render_tabs 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): class TabMixin(View):
@ -125,131 +128,88 @@ class MyMailView(TemplateView):
return self.render_to_response(self.get_context_data()) return self.render_to_response(self.get_context_data())
class AliasView(TemplateView): class AliasView(FormView):
"""View to create, list, enable, disable and delete aliases.
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('<fieldset class="form-group">')
self.sb.write('<legend>%s</legend>' % escape(legend))
self._render_boxes(email_names)
self.sb.write('</fieldset>')
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('<div class="form-check">')
self.sb.write('<input type="checkbox" name="alias" ')
self.sb.write('class="form-check-input" ')
self.sb.write('id="%s" value="%s">' % (input_id, value))
self.sb.write('<label class="form-check-label" ')
self.sb.write('for="%s">%s</label>' % (input_id, value))
self.sb.write('</div>')
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
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' 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): def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs) context = super().get_context_data(*args, **kwargs)
context['form'] = forms.AliasCreationForm() if isinstance(context['form'], forms.AliasCreateForm):
context['create_form'] = context['form']
uid = pwd.getpwnam(self.request.user.username).pw_uid aliases = self._get_current_aliases()
models = aliases.get(uid) context['list_form'] = forms.AliasListForm(aliases)
if len(models) > 0:
form = AliasView.Checkboxes(initial=models)
context['alias_boxes'] = form.render()
else: else:
context['no_alias'] = True context['create_form'] = forms.AliasCreateForm()
context['list_form'] = context['form']
return context return context
def _find_form(self, post): def post(self, request, *args, **kwargs):
form_name = post.get('form') """Find which form was submitted before proceeding."""
for cls in self.form_classes: self.posted_form = request.POST.get('form')
if cls.__name__ == form_name: return super().post(request, *args, **kwargs)
return cls(post)
raise ValidationError('Form was unspecified')
def _find_button(self, post): def form_valid(self, form):
key_filter = (k for k in post.keys() if k.startswith('btn_')) """Handle a valid submission."""
lst = list(itertools.islice(key_filter, 2)) if isinstance(form, forms.AliasListForm):
if len(lst) != 1: self._list_form_valid(form)
raise ValidationError('Bad post data') elif isinstance(form, forms.AliasCreateForm):
if not isinstance(lst[0], str): self._create_form_valid(form)
raise ValidationError('Bad post data')
return lst[0][len('btn_'):]
def post(self, request): return super().form_valid(form)
form = self._find_form(request.POST)
button = self._find_button(request.POST)
if not form.is_valid():
raise ValidationError('Form invalid')
if isinstance(form, AliasView.Checkboxes): def _list_form_valid(self, form):
if button not in ('delete', 'disable', 'enable'): """Handle a valid alias list form operation."""
raise ValidationError('Bad button') alias_list = form.cleaned_data['aliases']
return self.alias_operation_form_valid(form, button) 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): def _create_form_valid(self, form):
if button != 'add': """Handle a valid create alias form operation."""
raise ValidationError('Bad button') aliases_module.put(self._get_uid(), form.cleaned_data['alias'])
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())
class TLSView(TabMixin, TemplateView): class TLSView(TabMixin, TemplateView):