From a2a0b8a5b7f55a74958c9a07166e43cce9cf402d Mon Sep 17 00:00:00 2001 From: "J.-S. Caux" <J.S.Caux@uva.nl> Date: Sat, 17 Oct 2020 10:23:50 +0200 Subject: [PATCH] Add apimail.models.Domain --- apimail/admin.py | 4 +++ apimail/migrations/0021_auto_20201017_1016.py | 30 +++++++++++++++++++ apimail/migrations/0022_auto_20201017_1018.py | 19 ++++++++++++ apimail/models/__init__.py | 2 ++ apimail/models/account.py | 10 +++++++ apimail/models/domain.py | 24 +++++++++++++++ apimail/validators.py | 16 ++++++++++ 7 files changed, 105 insertions(+) create mode 100644 apimail/migrations/0021_auto_20201017_1016.py create mode 100644 apimail/migrations/0022_auto_20201017_1018.py create mode 100644 apimail/models/domain.py diff --git a/apimail/admin.py b/apimail/admin.py index c9db40e95..6ea48c9d3 100644 --- a/apimail/admin.py +++ b/apimail/admin.py @@ -5,6 +5,7 @@ __license__ = "AGPL v3" from django.contrib import admin from .models import ( + Domain, EmailAccount, EmailAccountAccess, AttachmentFile, ComposedMessage, ComposedMessageAPIResponse, @@ -13,6 +14,9 @@ from .models import ( UserTag) +admin.site.register(Domain) + + class EmailAccountAccessInline(admin.StackedInline): model = EmailAccountAccess extra = 0 diff --git a/apimail/migrations/0021_auto_20201017_1016.py b/apimail/migrations/0021_auto_20201017_1016.py new file mode 100644 index 000000000..01efa3ea1 --- /dev/null +++ b/apimail/migrations/0021_auto_20201017_1016.py @@ -0,0 +1,30 @@ +# Generated by Django 2.2.16 on 2020-10-17 08:16 + +import apimail.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('apimail', '0020_auto_20200214_1159'), + ] + + operations = [ + migrations.CreateModel( + name='Domain', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, unique=True, validators=[apimail.validators._simple_domain_name_validator])), + ], + options={ + 'ordering': ('name',), + }, + ), + migrations.AddField( + model_name='emailaccount', + name='domain', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='email_accounts', to='apimail.Domain'), + ), + ] diff --git a/apimail/migrations/0022_auto_20201017_1018.py b/apimail/migrations/0022_auto_20201017_1018.py new file mode 100644 index 000000000..3230748f8 --- /dev/null +++ b/apimail/migrations/0022_auto_20201017_1018.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.16 on 2020-10-17 08:18 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('apimail', '0021_auto_20201017_1016'), + ] + + operations = [ + migrations.AlterField( + model_name='emailaccount', + name='domain', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='email_accounts', to='apimail.Domain'), + ), + ] diff --git a/apimail/models/__init__.py b/apimail/models/__init__.py index 4ef781dbb..4f8395d53 100644 --- a/apimail/models/__init__.py +++ b/apimail/models/__init__.py @@ -8,6 +8,8 @@ from .attachment import AttachmentFile from .composed_message import ComposedMessage, ComposedMessageAPIResponse +from .domain import Domain + from .event import Event from .stored_message import StoredMessage diff --git a/apimail/models/account.py b/apimail/models/account.py index c0537002d..fb59da3d1 100644 --- a/apimail/models/account.py +++ b/apimail/models/account.py @@ -3,6 +3,7 @@ __license__ = "AGPL v3" from django.conf import settings +from django.core.exceptions import ValidationError from django.db import models from ..managers import EmailAccountAccessQuerySet @@ -14,6 +15,11 @@ class EmailAccount(models.Model): Access is specified on a per-user basis through the related EmailAccountAccess model. """ + domain = models.ForeignKey( + 'apimail.Domain', + related_name='email_accounts', + on_delete=models.CASCADE + ) name = models.CharField(max_length=256) email = models.EmailField(unique=True) description = models.TextField() @@ -24,6 +30,10 @@ class EmailAccount(models.Model): def __str__(self): return('%s <%s>' % (self.name, self.email)) + def clean(self): + if self.email.rpartition('@')[2] != self.domain.name: + raise ValidationError("Email domain does not match domain name.") + class EmailAccountAccess(models.Model): """ diff --git a/apimail/models/domain.py b/apimail/models/domain.py new file mode 100644 index 000000000..92184bd46 --- /dev/null +++ b/apimail/models/domain.py @@ -0,0 +1,24 @@ +_copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from django.db import models + +from ..validators import _simple_domain_name_validator + + +class Domain(models.Model): + """ + Domain name information. + """ + name = models.CharField( + max_length=100, + validators=[_simple_domain_name_validator], + unique=True, + ) + + class Meta: + ordering = ('name',) + + def __str__(self): + return self.name diff --git a/apimail/validators.py b/apimail/validators.py index 2c5581249..8bd1602f0 100644 --- a/apimail/validators.py +++ b/apimail/validators.py @@ -2,11 +2,27 @@ __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)" __license__ = "AGPL v3" +import string + from django.conf import settings from django.core.exceptions import ValidationError from django.template.defaultfilters import filesizeformat +def _simple_domain_name_validator(value): + """ + Validate that the given value contains no whitespaces to prevent common typos. + + Taken from django.contrib.sites.models + """ + checks = ((s in value) for s in string.whitespace) + if any(checks): + raise ValidationError( + "The domain name cannot contain any spaces or tabs.", + code='invalid', + ) + + def validate_max_email_attachment_file_size(value): if value.size > int(settings.MAX_EMAIL_ATTACHMENT_FILE_SIZE): raise ValidationError( -- GitLab