Skip to content
Snippets Groups Projects
Commit 2f6ba440 authored by Tom Teichler's avatar Tom Teichler :beers:
Browse files

Merge branch '1-initialise-app-and-move-mailaddress-stuff-form-tic-desk' into 'master'

Resolve "Initialise app and move mailaddress stuff from TIC-Desk"

Closes #1

See merge request !7
parents cdb7da47 d6375d54
No related branches found
No related tags found
1 merge request!7Resolve "Initialise app and move mailaddress stuff from TIC-Desk"
Pipeline #64904 passed with warnings
Showing
with 434 additions and 17 deletions
......@@ -64,6 +64,8 @@ docs/_build/
# Generated files
aleksis/node_modules/
aleksis/static/
aleksis/whoosh_index/
poetry.lock
.coverage
.mypy_cache/
......@@ -72,3 +74,8 @@ htmlcov/
maintenance_mode_state.txt
media/
package-lock.json
# VSCode
.vscode/
.history/
*.code-workspace
include:
- project: "AlekSIS/official/AlekSIS"
file: /ci/general.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/prepare/lock.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/test/lint.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/test/security.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/build/dist.yml
- project: "AlekSIS/official/AlekSIS"
file: "/ci/deploy/trigger_dist.yml"
- project: "AlekSIS/official/AlekSIS"
file: "/ci/docker/image.yml"
- project: "AlekSIS/official/AlekSIS"
file: /ci/publish/pypi.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/deploy/review.yml
file: /ci/docker/image.yml
ARG APPS="AlekSIS-App-Postbuero"
FROM registry.edugit.org/aleksis/official/aleksis-core:master
......@@ -4,3 +4,9 @@ from aleksis.core.util.apps import AppConfig
class PostBueroConfig(AppConfig):
name = "aleksis.apps.postbuero"
verbose_name = "AlekSIS — Postbuero (Mail server management)"
urls = {
"Repository": "https://edugit.org/AlekSIS/Onboarding/AlekSIS-App-Postbuero",
}
licence = "EUPL-1.2+"
copyright_info = (([2021], "Tom Teichler", "tom.teichler@teckids.org"),)
from django import forms
from django.contrib.auth import get_user_model
from django.core.validators import validate_email
from django.utils.translation import gettext_lazy as _
from material import Layout, Row
from aleksis.core.mixins import ExtensibleForm
from aleksis.core.util.core_helpers import get_site_preferences
from .models import MailAddress, MailDomain
User = get_user_model()
class MailAddForm(ExtensibleForm):
layout = Layout(
Row("local_part", "domain"),
)
class Meta:
model = MailAddress
exclude = ["person"]
def __init__(self, request, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["domain"].queryset = MailDomain.objects.filter(is_public=True)
def clean_local_part(self):
local_part = self.cleaned_data["local_part"]
disallowed_local_parts = get_site_preferences()["postbuero__disallowed_local_parts"].split(
","
)
if local_part in disallowed_local_parts:
raise forms.ValidationError(_("Local part not allowed."))
address = f"{self.cleaned_data['local_part']}@{self.cleaned_data['domain']}"
validate_email(address)
return local_part
def save(self, person, *args, **kwargs):
self.instance.person = person
super().save(*args, **kwargs)
class MailDomainForm(ExtensibleForm):
"""Form for managing mail domains."""
layout = Layout(Row("domain", "is_public"))
class Meta:
model = MailDomain
exclude = []
......@@ -3,15 +3,34 @@ from django.utils.translation import ugettext_lazy as _
MENUS = {
"NAV_MENU_CORE": [
{
"name": _("Postbuero"),
"url": "empty",
"name": _("Mail"),
"url": "#",
"icon": "email",
"root": True,
"validators": [
"menu_generator.validators.is_authenticated",
"aleksis.core.util.core_helpers.has_person",
],
"submenu": [
{
"name": _("Mail addresses"),
"url": "manage_mail",
"icon": "email",
"validators": [
"menu_generator.validators.is_authenticated",
],
},
{
"name": _("Mail domains"),
"url": "mail_domains",
"icon": "domain_add",
"validators": [
(
"aleksis.core.util.predicates.permission_validator",
"core.view_schoolterm_rule",
),
],
},
],
}
},
]
}
# Generated by Django 3.2.11 on 2022-01-19 21:25
import django.contrib.sites.managers
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('core', '0035_preference_model_unique'),
('sites', '0002_alter_domain_unique'),
]
operations = [
migrations.CreateModel(
name='MailAddress',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('extended_data', models.JSONField(default=dict, editable=False)),
('domain', models.CharField(max_length=255, verbose_name='Domain')),
('local_part', models.CharField(max_length=64, verbose_name='Local part')),
('person', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='local_mail_addresses', to='core.person', verbose_name='Person')),
('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.site')),
],
options={
'verbose_name': 'Mail address',
'verbose_name_plural': 'Mail addresses',
},
managers=[
('objects', django.contrib.sites.managers.CurrentSiteManager()),
],
),
migrations.AddConstraint(
model_name='mailaddress',
constraint=models.UniqueConstraint(fields=('local_part', 'domain'), name='unique_local_part_per_domain'),
),
]
# Generated by Django 3.2.12 on 2022-02-01 18:47
import django.contrib.sites.managers
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('sites', '0002_alter_domain_unique'),
('core', '0035_preference_model_unique'),
('postbuero', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='MailDomain',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('extended_data', models.JSONField(default=dict, editable=False)),
('domain', models.CharField(max_length=255, verbose_name='Domain')),
('allowed_groups', models.ManyToManyField(blank=True, related_name='mail_domains', to='core.Group', verbose_name='Allowed groups')),
('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.site')),
],
options={
'abstract': False,
},
managers=[
('objects', django.contrib.sites.managers.CurrentSiteManager()),
],
),
migrations.AlterField(
model_name='mailaddress',
name='domain',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='postbuero.maildomain', verbose_name='Domain'),
),
]
# Generated by Django 3.2.12 on 2022-02-01 20:12
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('postbuero', '0002_maildomain'),
]
operations = [
migrations.AlterModelOptions(
name='maildomain',
options={'permissions': (('can_use_domain', 'Can use domain'),)},
),
]
# Generated by Django 3.2.12 on 2022-02-01 20:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('postbuero', '0003_alter_maildomain_options'),
]
operations = [
migrations.RemoveField(
model_name='maildomain',
name='allowed_groups',
),
migrations.AddField(
model_name='maildomain',
name='is_public',
field=models.BooleanField(default=True, verbose_name='Public usable'),
),
]
from django.db import models
from django.utils.translation import gettext_lazy as _
from aleksis.core.mixins import ExtensibleModel
from aleksis.core.models import Person
class MailDomain(ExtensibleModel):
domain = models.CharField(verbose_name=_("Domain"), max_length=255)
is_public = models.BooleanField(verbose_name=_("Public usable"), default=True)
def __str__(self) -> str:
return self.domain
class Meta:
permissions = (("can_use_domain", _("Can use domain")),)
class MailAddress(ExtensibleModel):
domain = models.ForeignKey(MailDomain, verbose_name=_("Domain"), on_delete=models.CASCADE)
local_part = models.CharField(verbose_name=_("Local part"), max_length=64)
person = models.ForeignKey(
Person,
verbose_name=_("Person"),
null=True,
on_delete=models.SET_NULL,
related_name="local_mail_addresses",
)
def __str__(self) -> str:
return f"{self.local_part}@{self.domain}"
class Meta:
verbose_name = _("Mail address")
verbose_name_plural = _("Mail addresses")
constraints = [
models.UniqueConstraint(
fields=["local_part", "domain"], name="unique_local_part_per_domain"
)
]
from django.utils.translation import gettext_lazy as _
from dynamic_preferences.preferences import Section
from dynamic_preferences.types import BooleanPreference, LongStringPreference
from aleksis.core.registries import site_preferences_registry
postbuero = Section("postbuero", verbose_name=_("Postbuero"))
@site_preferences_registry.register
class DisallowedLocalParts(LongStringPreference):
section = postbuero
name = "disallowed_local_parts"
required = False
default = (
"bin,daemon,Debian-exim,freerad,games,gnats,irc,list,lp,mail,man,messagebus,news,"
"nslcd,ntp,openldap,postfix,postgres,proxy,root,sshd,sssd,statd,sync,sys,systemd-bus-proxy,"
"systemd-network,systemd-resolve,systemd-timesync,uucp,www-data,"
"webmaster,hostmaster,postmaster"
)
verbose_name = _("Comma-seperated list of disallowed local parts")
@site_preferences_registry.register
class SendAdminMail(BooleanPreference):
section = postbuero
name = "admin_mail"
required = False
default = False
verbose_name = _("Sent notification to admins if a new email address was registered.")
import rules
from aleksis.core.util.predicates import has_global_perm, has_object_perm, has_person
from .util.predicates import is_domain_public
can_use_domain_predicate = has_person & (
is_domain_public | has_object_perm("postbuero.can_use_domain")
)
rules.add_perm("postbuero.can_use_domain", can_use_domain_predicate)
view_mail_domain_predicate = has_person & has_global_perm("postbuero.view_maildomain")
rules.add_perm("postbuero.view_maildomain_rule", view_mail_domain_predicate)
create_mail_domain_predicate = has_person & has_global_perm("postbuero.add_maildomain")
rules.add_perm("postbuero.create_maildomain_rule", create_mail_domain_predicate)
edit_mail_domain_predicate = has_person & has_global_perm("postbuero.change_maildomain")
rules.add_perm("postbuero.edit_maildomain_rule", edit_mail_domain_predicate)
from rest_framework import serializers
from .models import MailAddress
class MailAddressSerializer(serializers.ModelSerializer):
class Meta:
model = MailAddress
fields = ["username", "address"]
address = serializers.SerializerMethodField()
username = serializers.SerializerMethodField()
def get_address(self, obj):
return str(obj)
def get_username(self, obj):
return obj.person.user.username
from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
from django_tables2.utils import A
class MailDomainTable(tables.Table):
"""Table to list mail domains."""
class Meta:
attrs = {"class": "highlight"}
domain = tables.LinkColumn("edit_mail_domain", args=[A("id")])
edit = tables.LinkColumn(
"edit_mail_domain",
args=[A("id")],
text=_("Edit"),
attrs={"a": {"class": "btn-flat waves-effect waves-orange orange-text"}},
verbose_name=_("Actions"),
)
{% extends 'core/base.html' %}
{% load i18n %}
{% block content %}
<p class="flow-text">
{% blocktrans %}Postbuero (Mail server management){% endblocktrans %}
</p>
{% endblock %}
{% extends "core/base.html" %}
{% load i18n material_form %}
{% block page_title %}{% blocktrans %}Manage email addresses{% endblocktrans %}{% endblock %}
{% block browser_title %}{% blocktrans %}Manage email addresses{% endblocktrans %}{% endblock %}
{% block content %}
<h5>{% blocktrans %}Create new email address{% endblocktrans %}</h5>
<form method="post">
{% csrf_token %}
{% form form=mail_add_form %}{% endform %}
{% include "core/partials/save_button.html" %}
</form>
<div class="col s12">
<h5>{% blocktrans %}My email addresses{% endblocktrans %}</h5>
<p>
{% blocktrans %}
You currently own the following addresses:
{% endblocktrans %}
</p>
<ul class="collection">
{% for mail in mails %}
<li class="collection-item">{{ mail }}</li>
{% empty %}
<li class="collection-item">{% blocktrans %}You currently not own any addresses.{% endblocktrans %}</li>
{% endfor %}
</ul>
</div>
{% endblock %}
{# -*- engine:django -*- #}
{% extends "core/base.html" %}
{% load material_form i18n %}
{% block browser_title %}{% blocktrans %}Create mail domain{% endblocktrans %}{% endblock %}
{% block page_title %}{% blocktrans %}Create mail domain{% endblocktrans %}{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
{% form form=form %}{% endform %}
{% include "core/partials/save_button.html" %}
</form>
{% endblock %}
{# -*- engine:django -*- #}
{% extends "core/base.html" %}
{% load material_form i18n %}
{% block browser_title %}{% blocktrans %}Edit mail domain{% endblocktrans %}{% endblock %}
{% block page_title %}{% blocktrans %}Edit mail domain{% endblocktrans %}{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
{% form form=form %}{% endform %}
{% include "core/partials/save_button.html" %}
</form>
{% endblock %}
{# -*- engine:django -*- #}
{% extends "core/base.html" %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block browser_title %}{% blocktrans %}Mail domains{% endblocktrans %}{% endblock %}
{% block page_title %}{% blocktrans %}Mail domains{% endblocktrans %}{% endblock %}
{% block content %}
<a class="btn green waves-effect waves-light" href="{% url 'create_mail_domain' %}">
<i class="material-icons left">add</i>
{% trans "Create mail domain" %}
</a>
{% render_table table %}
{% endblock %}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment