Skip to content

Commit

Permalink
add additional email creation (#874)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikkonie committed Apr 26, 2024
1 parent d89b3a2 commit f55221a
Show file tree
Hide file tree
Showing 10 changed files with 379 additions and 78 deletions.
6 changes: 4 additions & 2 deletions projectroles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +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'
ADD_EMAIL_ALREADY_SET_MSG = 'Email already set as {email_type} email for user'


# Project ----------------------------------------------------------------------
Expand Down Expand Up @@ -1453,7 +1453,9 @@ 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)
raise ValidationError(
ADD_EMAIL_ALREADY_SET_MSG.format(email_type='primary')
)

def save(self, *args, **kwargs):
self._validate_email_unique()
Expand Down
49 changes: 44 additions & 5 deletions userprofile/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,19 @@
from projectroles.app_settings import AppSettingAPI
from projectroles.forms import (
SODARForm,
SODARModelForm,
SETTING_CUSTOM_VALIDATE_MSG,
SETTING_DISABLE_LABEL,
SETTING_SOURCE_ONLY_MSG,
)
from projectroles.models import APP_SETTING_VAL_MAXLENGTH, SODAR_CONSTANTS
from projectroles.models import (
SODARUserAdditionalEmail,
SODAR_CONSTANTS,
APP_SETTING_VAL_MAXLENGTH,
ADD_EMAIL_ALREADY_SET_MSG,
)
from projectroles.plugins import get_active_plugins
from projectroles.utils import build_secret


app_settings = AppSettingAPI()
Expand All @@ -24,11 +31,8 @@
APP_SETTING_SCOPE_USER = SODAR_CONSTANTS['APP_SETTING_SCOPE_USER']


# User Settings Form -----------------------------------------------------------


class UserSettingsForm(SODARForm):
"""The form for configuring user settings."""
"""Form for configuring user settings"""

def __init__(self, *args, **kwargs):
self.user = kwargs.pop('current_user')
Expand Down Expand Up @@ -203,3 +207,38 @@ def clean(self):
),
)
return self.cleaned_data


class UserEmailForm(SODARModelForm):
"""Form for creating additional email addresses for user"""

class Meta:
model = SODARUserAdditionalEmail
fields = ['email', 'user', 'secret']

def __init__(self, current_user=None, *args, **kwargs):
super().__init__(*args, **kwargs)
if current_user:
self.current_user = current_user
self.fields['user'].widget = forms.HiddenInput()
self.initial['user'] = current_user
self.fields['secret'].widget = forms.HiddenInput()
self.initial['secret'] = build_secret(32)

def clean(self):
if self.cleaned_data['email'] == self.current_user.email:
self.add_error(
'email', ADD_EMAIL_ALREADY_SET_MSG.format(email_type='primary')
)
return self.cleaned_data
if (
SODARUserAdditionalEmail.objects.filter(
user=self.current_user, email=self.cleaned_data['email']
).count()
> 0
):
self.add_error(
'email',
ADD_EMAIL_ALREADY_SET_MSG.format(email_type='additional'),
)
return self.cleaned_data
3 changes: 3 additions & 0 deletions userprofile/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@

# Allow viewing user detail
rules.add_perm('userprofile.view_detail', rules.is_authenticated)

# Allow adding additional email
rules.add_perm('userprofile.add_email', rules.is_authenticated)
155 changes: 89 additions & 66 deletions userprofile/templates/userprofile/detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,27 @@

{% block css %}
{{ block.super }}
{# TODO: Add styles for email table #}
<style type="text/css">
.table#sodar-user-email-table tr td:nth-child(1) {
width: 100%;
}
.table#sodar-user-email-table tr td:nth-child(5) {
width: 80px;
}
/* Responsive modifications */
@media screen and (max-width: 900px) {
.table#sodar-user-email-table tr th:nth-child(3),
.table#sodar-user-email-table tr td:nth-child(3) {
display: none;
}
}
@media screen and (max-width: 700px) {
.table#sodar-user-email-table tr th:nth-child(4),
.table#sodar-user-email-table tr td:nth-child(4) {
display: none;
}
}
</style>
{% endblock %}

{% block projectroles %}
Expand Down Expand Up @@ -97,74 +117,77 @@ <h4>
</div>
</div>

<div class="card" id="sodar-user-email-card">
<div class="card-header">
<h4>
<i class="iconify" data-icon="mdi:email-multiple"></i> Additional Email Addresses
{% if local_user %}
<span class="sodar-header-input-group pull-right">
<a role="button"
class="btn btn-primary"
id="sodar-user-btn-email-add"
href="#">
<i class="iconify" data-icon="mdi:plus-thick"></i> Add Email
</a>
</span>
{% endif %}
</h4>
</div>
<div class="card-body p-0">
<table class="table table-striped sodar-card-table"
id="sodar-user-email-table">
<thead>
<tr>
<th>Address</th>
<th>Status</th>
<th>Created</th>
<th>Modified</th>
<th></th>
</tr>
</thead>
<tbody>
{% if add_emails.count > 0 %}
{% for email in add_emails %}
<tr class="sodar-user-email-table-row">
<td><a href="mailto:{{ email.email }}">{{ email.email }}</a></td>
<td class="text-{% if email.verified %}success{% else %}danger{% endif %}">
{% if email.verified %}Verified{% else %}Unverified{% endif %}
</td>
<td>{{ email.date_created | date:'Y-m-d H:i'}}</td>
<td>{{ email.date_modified | date:'Y-m-d H:i'}}</td>
<td class="text-right">
<button class="btn btn-secondary dropdown-toggle sodar-list-dropdown"
type="button" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
<i class="iconify" data-icon="mdi:gear"></i>
</button>
<div class="dropdown-menu dropdown-menu-right sodar-user-email-dropdown">
<a class="dropdown-item sodar-user-email-resend {% if email.verified %}disabled{% endif %}"
href="#">
<i class="iconify" data-icon="mdi:send"></i> Resend Verification Mail
</a>
<a class="dropdown-item sodar-user-email-delete text-danger"
href="#">
<i class="iconify" data-icon="mdi:close-thick"></i> Delete Address
</a>
</div>
{% get_django_setting 'PROJECTROLES_SEND_EMAIL' as send_email %}
{% if send_email %}
<div class="card" id="sodar-user-email-card">
<div class="card-header">
<h4>
<i class="iconify" data-icon="mdi:email-multiple"></i> Additional Email Addresses
{% if local_user %}
<span class="sodar-header-input-group pull-right">
<a role="button"
class="btn btn-primary"
id="sodar-user-btn-email-add"
href="{% url 'userprofile:email_create' %}">
<i class="iconify" data-icon="mdi:plus-thick"></i> Add Email
</a>
</span>
{% endif %}
</h4>
</div>
<div class="card-body p-0">
<table class="table table-striped sodar-card-table"
id="sodar-user-email-table">
<thead>
<tr>
<th>Address</th>
<th>Status</th>
<th>Created</th>
<th>Modified</th>
<th></th>
</tr>
</thead>
<tbody>
{% if add_emails.count > 0 %}
{% for email in add_emails %}
<tr class="sodar-user-email-table-row">
<td><a href="mailto:{{ email.email }}">{{ email.email }}</a></td>
<td class="text-nowrap text-{% if email.verified %}success{% else %}danger{% endif %}">
{% if email.verified %}Verified{% else %}Unverified{% endif %}
</td>
<td class="text-nowrap">{{ email.date_created | date:'Y-m-d H:i'}}</td>
<td class="text-nowrap">{{ email.date_modified | date:'Y-m-d H:i'}}</td>
<td class="text-right">
<button class="btn btn-secondary dropdown-toggle sodar-list-dropdown"
type="button" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
<i class="iconify" data-icon="mdi:gear"></i>
</button>
<div class="dropdown-menu dropdown-menu-right sodar-user-email-dropdown">
<a class="dropdown-item sodar-user-email-resend {% if email.verified %}disabled{% endif %}"
href="#">
<i class="iconify" data-icon="mdi:send"></i> Resend Verification Mail
</a>
<a class="dropdown-item sodar-user-email-delete text-danger"
href="#">
<i class="iconify" data-icon="mdi:close-thick"></i> Delete Address
</a>
</div>
</td>
</tr>
{% endfor %}
{% else %}
<tr id="sodar-user-email-table-not-found">
<td class="bg-faded font-italic text-center" colspan="5">
No additional email addresses set.
</td>
</tr>
{% endfor %}
{% else %}
<tr id="sodar-user-email-table-not-found">
<td class="bg-faded font-italic text-center" colspan="5">
No additional email addresses set.
</td>
</tr>
{% endif %}
</tbody>
</table>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
</div>

{% endblock projectroles %}
Expand Down
45 changes: 45 additions & 0 deletions userprofile/templates/userprofile/email_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{% extends 'projectroles/base.html' %}

{% load rules %}
{% load crispy_forms_filters %}
{% load projectroles_tags %}
{% load projectroles_common_tags %}

{% block title %}
Add Email Address
{% endblock title %}

{% block head_extend %}
{{ form.media }}
{% endblock head_extend %}

{% block projectroles %}

<div class="row sodar-pr-content-title pt-2">
<h2 class="sodar-pr-content-title">{{ request.user.get_full_name }}</h2>
<div class="sodar-pr-content-title-secondary text-muted">User Profile</div>
</div>

<div class="row sodar-subtitle-container">
<h3>Add Email Address</h3>
</div>

<div class="container-fluid sodar-page-container">
<form method="post">
{% csrf_token %}
{{ form | crispy }}
<div class="row">
<div class="btn-group ml-auto" role="group">
<a role="button" class="btn btn-secondary"
href="{% url 'userprofile:detail' %}">
<i class="iconify" data-icon="mdi:arrow-left-circle"></i> Cancel
</a>
<button type="submit" class="btn btn-primary">
<i class="iconify" data-icon="mdi:plus-thick"></i> Add
</button>
</div>
</div>
</form>
</div>

{% endblock projectroles %}
13 changes: 13 additions & 0 deletions userprofile/tests/test_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,16 @@ def test_get_settings_update_anon(self):
url = reverse('userprofile:settings_update')
self.assert_response(url, [self.superuser, self.regular_user], 200)
self.assert_response(url, self.anonymous, 302)

def test_get_email_create(self):
"""Test UserEmailCreateView GET"""
url = reverse('userprofile:email_create')
self.assert_response(url, [self.superuser, self.regular_user], 200)
self.assert_response(url, self.anonymous, 302)

@override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True)
def test_get_email_create_anon(self):
"""Test UserEmailCreateView GET with anonymous access"""
url = reverse('userprofile:email_create')
self.assert_response(url, [self.superuser, self.regular_user], 200)
self.assert_response(url, self.anonymous, 302)
14 changes: 12 additions & 2 deletions userprofile/tests/test_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def test_update_button(self):
expected = [(self.local_user, 1), (self.ldap_user, 0)]
self.assert_element_count(expected, self.url, 'sodar-user-btn-update')

def test_add_email_unset(self):
def test_additional_email_unset(self):
"""Test existence of additional email elements without email"""
self.assert_element_count(
[(self.local_user, 0)],
Expand All @@ -48,7 +48,7 @@ def test_add_email_unset(self):
True,
)

def test_add_email_set(self):
def test_additional_email_set(self):
"""Test existence of additional email elements with email"""
self.make_email(self.local_user, '[email protected]')
self.make_email(self.local_user, '[email protected]', verified=False)
Expand All @@ -67,6 +67,16 @@ def test_add_email_set(self):
False,
)

@override_settings(PROJECTROLES_SEND_EMAIL=False)
def test_additional_email_disabled(self):
"""Test existence of email card with PROJECTROLES_SEND_EMAIL=False"""
self.assert_element_exists(
[self.local_user],
self.url,
'sodar-user-email-card',
False,
)


class TestUserSettings(UITestBase):
"""Tests for user settings page"""
Expand Down
Loading

0 comments on commit f55221a

Please sign in to comment.