Skip to content

Commit

Permalink
add SODARUserAdditionalEmail model (#874)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikkonie committed Apr 25, 2024
1 parent 4819bb7 commit d726fde
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Added
- Optional pagination for REST API list views (#1313)
- Email notification opt-out settings (#1417)
- CC and BCC field support in sending generic emails (#415)
- ``SODARUserAdditionalEmail`` model (#874)
- **Timeline**
- ``sodar_uuid`` field in ``TimelineEventObjectRef`` model (#1415)
- REST API views (#1350)
Expand Down
79 changes: 79 additions & 0 deletions projectroles/migrations/0029_sodaruseradditionalemail.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Generated by Django 4.2.11 on 2024-04-25 11:48

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("projectroles", "0028_populate_finder_role"),
]

operations = [
migrations.CreateModel(
name="SODARUserAdditionalEmail",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("email", models.EmailField(help_text="Email address", max_length=254)),
(
"verified",
models.BooleanField(
default=False, help_text="Email verification status"
),
),
(
"secret",
models.CharField(
help_text="Secret token for email verification",
max_length=255,
unique=True,
),
),
(
"date_created",
models.DateTimeField(
auto_now_add=True, help_text="DateTime of creation"
),
),
(
"date_modified",
models.DateTimeField(
auto_now=True, help_text="DateTime of last modification"
),
),
(
"sodar_uuid",
models.UUIDField(
default=uuid.uuid4,
help_text="SODARUserAdditionalEmail SODAR UUID",
unique=True,
),
),
(
"user",
models.ForeignKey(
help_text="User for whom the email is assigned",
on_delete=django.db.models.deletion.CASCADE,
related_name="additional_emails",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"ordering": ["user__username", "email"],
"unique_together": {("user", "email")},
},
),
]
87 changes: 86 additions & 1 deletion projectroles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
'Invalid project type "{project_type}" for ' 'role "{role_name}"'
)
CAT_PUBLIC_ACCESS_MSG = 'Public guest access is not allowed for categories'
ADD_EMAIL_ALREADY_SET_MSG = 'Email already set as primary email for user'


# Project ----------------------------------------------------------------------
Expand Down Expand Up @@ -1271,7 +1272,7 @@ def get_project(self):
)


# Abstract User Model ----------------------------------------------------------
# User Models ------------------------------------------------------------------


class SODARUser(AbstractUser):
Expand Down Expand Up @@ -1373,3 +1374,87 @@ def update_ldap_username(self):
self.username = u_split[0] + '@' + u_split[1].upper()
self.save()
return self.username


class SODARUserAdditionalEmail(models.Model):
"""
Model representing an additional email address for a user. Stores
information for email verification.
"""

#: User for whom the email is assigned
user = models.ForeignKey(
AUTH_USER_MODEL,
related_name='additional_emails',
help_text='User for whom the email is assigned',
on_delete=models.CASCADE,
)

#: Email address
email = models.EmailField(
unique=False,
null=False,
blank=False,
help_text='Email address',
)

#: Email verification status
verified = models.BooleanField(
default=False, help_text='Email verification status'
)

#: Secret token for email verification
secret = models.CharField(
max_length=255,
unique=True,
blank=False,
null=False,
help_text='Secret token for email verification',
)

#: DateTime of creation
date_created = models.DateTimeField(
auto_now_add=True, help_text='DateTime of creation'
)

#: DateTime of last modification
date_modified = models.DateTimeField(
auto_now=True, help_text='DateTime of last modification'
)

#: SODARUserAdditionalEmail SODAR UUID
sodar_uuid = models.UUIDField(
default=uuid.uuid4,
unique=True,
help_text='SODARUserAdditionalEmail SODAR UUID',
)

class Meta:
ordering = ['user__username', 'email']
unique_together = ['user', 'email']

def __str__(self):
return '{}: {}'.format(self.user.username, self.email)

def __repr__(self):
values = (
self.user.username,
self.email,
str(self.verified),
self.secret,
str(self.sodar_uuid),
)
return 'SODARUserAdditionalEmail({})'.format(
', '.join(repr(v) for v in values)
)

def _validate_email_unique(self):
"""
Assert the same email has not yet been set for the user.
"""
if self.email == self.user.email:
raise ValidationError(ADD_EMAIL_ALREADY_SET_MSG)

def save(self, *args, **kwargs):
self._validate_email_unique()
super().save(*args, **kwargs)
62 changes: 62 additions & 0 deletions projectroles/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
AppSetting,
RemoteSite,
RemoteProject,
SODARUserAdditionalEmail,
SODAR_CONSTANTS,
ROLE_RANKING,
CAT_DELIMITER,
Expand All @@ -45,6 +46,8 @@
REMOTE_SITE_URL = 'https://sodar.example.org'
REMOTE_SITE_SECRET = build_secret()
REMOTE_SITE_USER_DISPLAY = True
ADD_EMAIL = '[email protected]'
ADD_EMAIL_SECRET = build_secret()


class ProjectMixin:
Expand Down Expand Up @@ -297,6 +300,21 @@ def make_sodar_user(
return user


class SODARUserAdditionalEmailMixin:
"""Helper mixin for SODARUserAdditionalEmail creation"""

def make_email(self, user, email, verified, secret=build_secret()):
values = {
'user': user,
'email': email,
'verified': verified,
'secret': secret,
}
email = SODARUserAdditionalEmail(**values)
email.save()
return email


class TestProject(ProjectMixin, RoleMixin, RoleAssignmentMixin, TestCase):
"""Tests for model.Project"""

Expand Down Expand Up @@ -1547,3 +1565,47 @@ def test_update_ldap_username(self):
self.user.username = 'user@example'
self.user.update_ldap_username()
self.assertEqual(self.user.username, 'user@EXAMPLE')


class TestSODARUserAdditionalEmail(SODARUserAdditionalEmailMixin, TestCase):
"""Tests for SODARUserAdditionalEmail"""

def setUp(self):
self.user = self.make_user('user')
self.email = self.make_email(
user=self.user,
email=ADD_EMAIL,
verified=True,
secret=ADD_EMAIL_SECRET,
)

def test_initialization(self):
"""Test SODARUserAdditionalEmail initialization"""
expected = {
'id': self.email.pk,
'user': self.user.pk,
'email': ADD_EMAIL,
'verified': True,
'secret': ADD_EMAIL_SECRET,
'sodar_uuid': self.email.sodar_uuid,
}
self.assertEqual(model_to_dict(self.email), expected)

def test__str__(self):
"""Test SODARUserAdditionalEmail __str__()"""
self.assertEqual(self.email.__str__(), 'user: [email protected]')

def test__repr__(self):
"""Test SODARUserAdditionalEmail __repr__()"""
expected = 'SODARUserAdditionalEmail(\'{}\')'.format(
'\', \''.join(
[
self.email.user.username,
self.email.email,
str(self.email.verified),
self.email.secret,
str(self.email.sodar_uuid),
]
)
)
self.assertEqual(self.email.__repr__(), expected)

0 comments on commit d726fde

Please sign in to comment.