diff --git a/api/serializers.py b/api/serializers.py
index c8cdffd9..398f2b19 100644
--- a/api/serializers.py
+++ b/api/serializers.py
@@ -495,7 +495,8 @@ class UserSerializer(NamespacedHMSerializer):
class Meta:
model = users.User
- fields = ('name', 'pseudo', 'email', 'school', 'shell', 'comment',
+ fields = ('surname', 'pseudo', 'email', 'local_email_redirect',
+ 'local_email_enabled', 'school', 'shell', 'comment',
'state', 'registered', 'telephone', 'solde', 'access',
'end_access', 'uid', 'class_name', 'api_url')
extra_kwargs = {
@@ -512,7 +513,8 @@ class ClubSerializer(NamespacedHMSerializer):
class Meta:
model = users.Club
- fields = ('name', 'pseudo', 'email', 'school', 'shell', 'comment',
+ fields = ('name', 'pseudo', 'email', 'local_email_redirect',
+ 'local_email_enabled', 'school', 'shell', 'comment',
'state', 'registered', 'telephone', 'solde', 'room',
'access', 'end_access', 'administrators', 'members',
'mailing', 'uid', 'api_url')
@@ -529,9 +531,10 @@ class AdherentSerializer(NamespacedHMSerializer):
class Meta:
model = users.Adherent
- fields = ('name', 'surname', 'pseudo', 'email', 'school', 'shell',
- 'comment', 'state', 'registered', 'telephone', 'room',
- 'solde', 'access', 'end_access', 'uid', 'api_url')
+ fields = ('name', 'surname', 'pseudo', 'email', 'local_email_redirect',
+ 'local_email_enabled', 'school', 'shell', 'comment',
+ 'state', 'registered', 'telephone', 'room', 'solde',
+ 'access', 'end_access', 'uid', 'api_url')
extra_kwargs = {
'shell': {'view_name': 'shell-detail'}
}
@@ -593,6 +596,15 @@ class WhitelistSerializer(NamespacedHMSerializer):
fields = ('user', 'raison', 'date_start', 'date_end', 'active', 'api_url')
+class EMailAddressSerializer(NamespacedHMSerializer):
+ """Serialize `users.models.EMailAddress` objects.
+ """
+
+ class Meta:
+ model = users.EMailAddress
+ fields = ('user', 'local_part', 'complete_email_address', 'api_url')
+
+
# SERVICE REGEN
@@ -611,6 +623,21 @@ class ServiceRegenSerializer(NamespacedHMSerializer):
}
+# LOCAL EMAILS
+
+
+class LocalEmailUsersSerializer(NamespacedHMSerializer):
+ email_address = EMailAddressSerializer(
+ read_only=True,
+ many=True
+ )
+
+ class Meta:
+ model = users.User
+ fields = ('local_email_enabled', 'local_email_redirect',
+ 'email_address')
+
+
# DHCP
diff --git a/api/urls.py b/api/urls.py
index 67302789..7ee36073 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -93,10 +93,13 @@ router.register_viewset(r'users/listright', views.ListRightViewSet)
router.register_viewset(r'users/shell', views.ShellViewSet, base_name='shell')
router.register_viewset(r'users/ban', views.BanViewSet)
router.register_viewset(r'users/whitelist', views.WhitelistViewSet)
+router.register_viewset(r'users/emailaddress', views.EMailAddressViewSet)
# SERVICE REGEN
router.register_viewset(r'services/regen', views.ServiceRegenViewSet, base_name='serviceregen')
# DHCP
router.register_view(r'dhcp/hostmacip', views.HostMacIpView),
+# LOCAL EMAILS
+router.register_view(r'localemail/users', views.LocalEmailUsersView),
# DNS
router.register_view(r'dns/zones', views.DNSZonesView),
# MAILING
diff --git a/api/views.py b/api/views.py
index 7b01b0c3..ef083edf 100644
--- a/api/views.py
+++ b/api/views.py
@@ -469,6 +469,21 @@ class WhitelistViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = serializers.WhitelistSerializer
+class EMailAddressViewSet(viewsets.ReadOnlyModelViewSet):
+ """Exposes list and details of `users.models.EMailAddress` objects.
+ """
+ serializer_class = serializers.EMailAddressSerializer
+ queryset = users.EMailAddress.objects.none()
+
+ def get_queryset(self):
+ if preferences.OptionalUser.get_cached_value(
+ 'local_email_accounts_enabled'):
+ return (users.EMailAddress.objects
+ .filter(user__local_email_enabled=True))
+ else:
+ return users.EMailAddress.objects.none()
+
+
# SERVICE REGEN
@@ -489,8 +504,26 @@ class ServiceRegenViewSet(viewsets.ModelViewSet):
return queryset
+# LOCAL EMAILS
+
+
+class LocalEmailUsersView(generics.ListAPIView):
+ """Exposes all the aliases of the users that activated the internal address
+ """
+ serializer_class = serializers.LocalEmailUsersSerializer
+
+ def get_queryset(self):
+ if preferences.OptionalUser.get_cached_value(
+ 'local_email_accounts_enabled'):
+ return (users.User.objects
+ .filter(local_email_enabled=True))
+ else:
+ return users.User.objects.none()
+
+
# DHCP
+
class HostMacIpView(generics.ListAPIView):
"""Exposes the associations between hostname, mac address and IPv4 in
order to build the DHCP lease files.
@@ -501,6 +534,7 @@ class HostMacIpView(generics.ListAPIView):
# DNS
+
class DNSZonesView(generics.ListAPIView):
"""Exposes the detailed information about each extension (hostnames,
IPs, DNS records, etc.) in order to build the DNS zone files.
diff --git a/preferences/migrations/0046_optionaluser_mail_extension.py b/preferences/migrations/0046_optionaluser_mail_extension.py
new file mode 100644
index 00000000..9de1608a
--- /dev/null
+++ b/preferences/migrations/0046_optionaluser_mail_extension.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-06-26 19:31
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('preferences', '0045_remove_unused_payment_fields'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='optionaluser',
+ name='local_email_accounts_enabled',
+ field=models.BooleanField(default=False, help_text='Enable local email accounts for users'),
+ ),
+ migrations.AddField(
+ model_name='optionaluser',
+ name='local_email_domain',
+ field=models.CharField(default='@example.org', help_text='Domain to use for local email accounts', max_length=32),
+ ),
+ migrations.AddField(
+ model_name='optionaluser',
+ name='max_email_address',
+ field=models.IntegerField(default=15, help_text='Maximum number of local email address for a standard user'),
+ ),
+ ]
diff --git a/preferences/models.py b/preferences/models.py
index d39b9f8e..c2d6aa74 100644
--- a/preferences/models.py
+++ b/preferences/models.py
@@ -30,6 +30,7 @@ from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.cache import cache
+from django.forms import ValidationError
import machines.models
from re2o.mixins import AclMixin
@@ -83,12 +84,32 @@ class OptionalUser(AclMixin, PreferencesModel):
blank=True,
null=True
)
+ local_email_accounts_enabled = models.BooleanField(
+ default=False,
+ help_text="Enable local email accounts for users"
+ )
+ local_email_domain = models.CharField(
+ max_length = 32,
+ default = "@example.org",
+ help_text="Domain to use for local email accounts",
+ )
+ max_email_address = models.IntegerField(
+ default = 15,
+ help_text = "Maximum number of local email address for a standard user"
+ )
class Meta:
permissions = (
("view_optionaluser", "Peut voir les options de l'user"),
)
+ def clean(self):
+ """Clean model:
+ Check the mail_extension
+ """
+ if self.local_email_domain[0] != "@":
+ raise ValidationError("Mail domain must begin with @")
+
@receiver(post_save, sender=OptionalUser)
def optionaluser_post_save(**kwargs):
diff --git a/preferences/templates/preferences/display_preferences.html b/preferences/templates/preferences/display_preferences.html
old mode 100755
new mode 100644
index 34417695..a3f2dbc3
--- a/preferences/templates/preferences/display_preferences.html
+++ b/preferences/templates/preferences/display_preferences.html
@@ -32,194 +32,204 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% block content %}
Préférences utilisateur
-
- Editer
+ Editer
-
-
+
+Généralités
-
- Téléphone obligatoirement requis |
- {{ useroptions.is_tel_mandatory|tick }} |
- Auto inscription |
- {{ useroptions.self_adhesion|tick }} |
-
-
- Champ gpg fingerprint |
- {{ useroptions.gpg_fingerprint|tick }} |
- Shell par défaut des utilisateurs |
- {{ useroptions.shell_default }} |
-
-
- Creations d'adhérents par tous |
- {{ useroptions.all_can_create_adherent|tick }} |
- Creations de clubs par tous |
- {{ useroptions.all_can_create_club|tick }} |
-
+
+ Téléphone obligatoirement requis |
+ {{ useroptions.is_tel_mandatory|tick }} |
+ Auto inscription |
+ {{ useroptions.self_adhesion|tick }} |
+
+
+ Champ gpg fingerprint |
+ {{ useroptions.gpg_fingerprint|tick }} |
+ Shell par défaut des utilisateurs |
+ {{ useroptions.shell_default }} |
+
+
+ Creations d'adhérents par tous |
+ {{ useroptions.all_can_create_adherent|tick }} |
+ Creations de clubs par tous |
+ {{ useroptions.all_can_create_club|tick }} |
+
+
+
+Comptes mails
+
+
+ Gestion des comptes mails |
+ {{ useroptions.local_email_accounts_enabled | tick }} |
+ Extension mail interne |
+ {{ useroptions.local_email_domain }} |
+
+
+ Nombre d'alias mail max |
+ {{ useroptions.max_email_address }} |
+
Préférences machines
-
- Editer
+ Editer
-
-
-
- Mot de passe par machine |
- {{ machineoptions.password_machine|tick }} |
- Machines/interfaces autorisées par utilisateurs |
- {{ machineoptions.max_lambdauser_interfaces }} |
-
-
- Alias dns autorisé par utilisateur |
- {{ machineoptions.max_lambdauser_aliases }} |
- Support de l'ipv6 |
- {{ machineoptions.ipv6_mode }} |
-
-
- Creation de machines |
- {{ machineoptions.create_machine|tick }} |
-
+
+ Mot de passe par machine |
+ {{ machineoptions.password_machine|tick }} |
+ Machines/interfaces autorisées par utilisateurs |
+ {{ machineoptions.max_lambdauser_interfaces }} |
+
+
+ Alias dns autorisé par utilisateur |
+ {{ machineoptions.max_lambdauser_aliases }} |
+ Support de l'ipv6 |
+ {{ machineoptions.ipv6_mode }} |
+
+
+ Creation de machines |
+ {{ machineoptions.create_machine|tick }} |
+
+
+
Préférences topologie
-
- Editer
+ Editer
-
-
-
- Politique générale de placement de vlan |
- {{ topologieoptions.radius_general_policy }} |
- Ce réglage défini la politique vlan après acceptation radius : soit sur le vlan de la plage d'ip de la machine, soit sur un vlan prédéfini dans "Vlan où placer les machines après acceptation RADIUS" |
- |
-
-
- Vlan où placer les machines après acceptation RADIUS |
- {{ topologieoptions.vlan_decision_ok }} |
- Vlan où placer les machines après rejet RADIUS |
- {{ topologieoptions.vlan_decision_nok }} |
-
+
+ Politique générale de placement de vlan |
+ {{ topologieoptions.radius_general_policy }} |
+
+ Ce réglage défini la politique vlan après acceptation radius :
+ soit sur le vlan de la plage d'ip de la machine, soit sur un
+ vlan prédéfini dans "Vlan où placer les machines après acceptation
+ RADIUS"
+ |
+ |
+
+
+ Vlan où placer les machines après acceptation RADIUS |
+ {{ topologieoptions.vlan_decision_ok }} |
+ Vlan où placer les machines après rejet RADIUS |
+ {{ topologieoptions.vlan_decision_nok }} |
+
+
+
Préférences generales
-
- Editer
+ Editer
-
-
-
- Nom du site web |
- {{ generaloptions.site_name }} |
- Adresse mail d'expedition automatique |
- {{ generaloptions.email_from }} |
-
-
- Affichage de résultats dans le champ de recherche |
- {{ generaloptions.search_display_page }} |
- Nombre d'items affichés en liste (taille normale) |
- {{ generaloptions.pagination_number }} |
-
-
- Nombre d'items affichés en liste (taille élevée) |
- {{ generaloptions.pagination_large_number }} |
- Temps avant expiration du lien de reinitialisation de mot de passe (en heures) |
- {{ generaloptions.req_expire_hrs }} |
-
-
- Message global affiché sur le site |
- {{ generaloptions.general_message }} |
- Résumé des CGU |
- {{ generaloptions.GTU_sum_up }} |
-
-
- CGU |
- {{generaloptions.GTU}}
- |
+
+ Nom du site web |
+ {{ generaloptions.site_name }} |
+ Adresse mail d'expedition automatique |
+ {{ generaloptions.email_from }} |
+
+
+ Affichage de résultats dans le champ de recherche |
+ {{ generaloptions.search_display_page }} |
+ Nombre d'items affichés en liste (taille normale) |
+ {{ generaloptions.pagination_number }} |
+
+
+ Nombre d'items affichés en liste (taille élevée) |
+ {{ generaloptions.pagination_large_number }} |
+ Temps avant expiration du lien de reinitialisation de mot de passe (en heures) |
+ {{ generaloptions.req_expire_hrs }} |
+
+
+ Message global affiché sur le site |
+ {{ generaloptions.general_message }} |
+ Résumé des CGU |
+ {{ generaloptions.GTU_sum_up }} |
+
+
+ CGU |
+ {{generaloptions.GTU}}
+ |
+
+
Données de l'association
-
- Editer
+ Editer
-
-
-
- Nom |
- {{ assooptions.name }} |
- SIRET |
- {{ assooptions.siret }} |
-
-
- Adresse |
- {{ assooptions.adresse1 }}
- {{ assooptions.adresse2 }} |
- Contact mail |
- {{ assooptions.contact }} |
-
-
- Telephone |
- {{ assooptions.telephone }} |
- Pseudo d'usage |
- {{ assooptions.pseudo }} |
-
-
- Objet utilisateur de l'association |
- {{ assooptions.utilisateur_asso }} |
- Description de l'association |
- {{ assooptions.description | safe }} |
-
-
+
+ Nom |
+ {{ assooptions.name }} |
+ SIRET |
+ {{ assooptions.siret }} |
+
+
+ Adresse |
+ {{ assooptions.adresse1 }}
+ {{ assooptions.adresse2 }} |
+ Contact mail |
+ {{ assooptions.contact }} |
+
+
+ Telephone |
+ {{ assooptions.telephone }} |
+ Pseudo d'usage |
+ {{ assooptions.pseudo }} |
+
+
+ Objet utilisateur de l'association |
+ {{ assooptions.utilisateur_asso }} |
+ Description de l'association |
+ {{ assooptions.description | safe }} |
+
+
+
Messages personalisé dans les mails
-
- Editer
+ Editer
-
-
-
- Mail de bienvenue (Français) |
- {{ mailmessageoptions.welcome_mail_fr | safe }} |
-
-
- Mail de bienvenue (Anglais) |
- {{ mailmessageoptions.welcome_mail_en | safe }} |
-
+
+ Mail de bienvenue (Français) |
+ {{ mailmessageoptions.welcome_mail_fr | safe }} |
+
+
+ Mail de bienvenue (Anglais) |
+ {{ mailmessageoptions.welcome_mail_en | safe }} |
+
-Liste des services et préférences page d'accueil
+
+
+Liste des services et préférences page d'accueil
{% can_create preferences.Service%}
- Ajouter un service
+
+ Ajouter un service
+
{% acl_end %}
- Supprimer un ou plusieurs service
+
+ Supprimer un ou plusieurs service
+
{% include "preferences/aff_service.html" with service_list=service_list %}
-
- Editer
+ Editer
-
-
-
- Url du compte twitter |
- {{ homeoptions.twitter_url }} |
- Nom utilisé pour afficher le compte |
- {{ homeoptions.twitter_account_name }} |
-
-
- Url du compte facebook |
- {{ homeoptions.facebook_url }} |
-
+
+ Url du compte twitter |
+ {{ homeoptions.twitter_url }} |
+ Nom utilisé pour afficher le compte |
+ {{ homeoptions.twitter_account_name }} |
+
+
+ Url du compte facebook |
+ {{ homeoptions.facebook_url }} |
+
-
-
-
{% endblock %}
diff --git a/users/admin.py b/users/admin.py
index a902d2e2..e7dd3240 100644
--- a/users/admin.py
+++ b/users/admin.py
@@ -34,6 +34,7 @@ from reversion.admin import VersionAdmin
from .models import (
User,
+ EMailAddress,
ServiceUser,
School,
ListRight,
@@ -108,6 +109,11 @@ class BanAdmin(VersionAdmin):
pass
+class EMailAddressAdmin(VersionAdmin):
+ """Gestion des alias mail"""
+ pass
+
+
class WhitelistAdmin(VersionAdmin):
"""Gestion des whitelist"""
pass
@@ -126,6 +132,8 @@ class UserAdmin(VersionAdmin, BaseUserAdmin):
'pseudo',
'surname',
'email',
+ 'local_email_redirect',
+ 'local_email_enabled',
'school',
'is_admin',
'shell'
@@ -211,6 +219,7 @@ admin.site.register(School, SchoolAdmin)
admin.site.register(ListRight, ListRightAdmin)
admin.site.register(ListShell, ListShellAdmin)
admin.site.register(Ban, BanAdmin)
+admin.site.register(EMailAddress, EMailAddressAdmin)
admin.site.register(Whitelist, WhitelistAdmin)
admin.site.register(Request, RequestAdmin)
# Now register the new UserAdmin...
diff --git a/users/forms.py b/users/forms.py
index 670fbb98..cbffd961 100644
--- a/users/forms.py
+++ b/users/forms.py
@@ -53,6 +53,7 @@ from .models import (
School,
ListRight,
Whitelist,
+ EMailAddress,
ListShell,
Ban,
Adherent,
@@ -219,7 +220,7 @@ class UserChangeForm(FormRevMixin, forms.ModelForm):
class Meta:
model = Adherent
- fields = ('pseudo', 'password', 'surname', 'email')
+ fields = ('pseudo', 'password', 'surname')
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
@@ -312,7 +313,6 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
'name',
'surname',
'pseudo',
- 'email',
'school',
'comment',
'room',
@@ -364,7 +364,6 @@ class ClubForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
fields = [
'surname',
'pseudo',
- 'email',
'school',
'comment',
'room',
@@ -590,3 +589,39 @@ class WhitelistForm(FormRevMixin, ModelForm):
model = Whitelist
exclude = ['user']
widgets = {'date_end':DateTimePicker}
+
+
+class EMailAddressForm(FormRevMixin, ModelForm):
+ """Create and edit a local email address"""
+ def __init__(self, *args, **kwargs):
+ prefix = kwargs.pop('prefix', self.Meta.model.__name__)
+ super(EMailAddressForm, self).__init__(*args, prefix=prefix, **kwargs)
+ self.fields['local_part'].label = "Local part of the email"
+ self.fields['local_part'].help_text = "Can't contain @"
+
+ class Meta:
+ model = EMailAddress
+ exclude = ['user']
+
+
+class EmailSettingsForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
+ """Edit email-related settings"""
+ def __init__(self, *args, **kwargs):
+ prefix = kwargs.pop('prefix', self.Meta.model.__name__)
+ super(EmailSettingsForm, self).__init__(*args, prefix=prefix, **kwargs)
+ self.fields['email'].label = "Contact email address"
+ if 'local_email_redirect' in self.fields:
+ self.fields['local_email_redirect'].label = "Redirect local emails"
+ self.fields['local_email_redirect'].help_text = (
+ "Enable the automated redirection of the local email address "
+ "to the contact email address"
+ )
+ if 'local_email_enabled' in self.fields:
+ self.fields['local_email_enabled'].label = "Use local emails"
+ self.fields['local_email_enabled'].help_text = (
+ "Enable the use of the local email account"
+ )
+
+ class Meta:
+ model = User
+ fields = ['email', 'local_email_redirect', 'local_email_enabled']
diff --git a/users/migrations/0073_auto_20180629_1614.py b/users/migrations/0073_auto_20180629_1614.py
new file mode 100644
index 00000000..72ebffe6
--- /dev/null
+++ b/users/migrations/0073_auto_20180629_1614.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-06-29 14:14
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import re2o.mixins
+
+
+class Migration(migrations.Migration):
+
+ def create_initial_email_address(apps, schema_editor):
+ db_alias = schema_editor.connection.alias
+ User = apps.get_model("users", "User")
+ EMailAddress = apps.get_model("users", "EMailAddress")
+ users = User.objects.using(db_alias).all()
+ for user in users:
+ EMailAddress.objects.using(db_alias).create(
+ local_part=user.pseudo,
+ user=user
+ )
+
+ def delete_all_email_address(apps, schema_editor):
+ db_alias = schema_editor.connection.alias
+ EMailAddress = apps.get_model("users", "EMailAddress")
+ EMailAddress.objects.using(db_alias).delete()
+
+ dependencies = [
+ ('users', '0072_auto_20180426_2021'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='EMailAddress',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('local_part', models.CharField(help_text="Local part of the email address", max_length=128, unique=True)),
+ ('user', models.ForeignKey(help_text='User of the local email', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ bases=(re2o.mixins.RevMixin, re2o.mixins.AclMixin, models.Model),
+ options={'permissions': (('view_emailaddress', 'Can see a local email account object'),), 'verbose_name': 'Local email account', 'verbose_name_plural': 'Local email accounts'},
+ ),
+ migrations.AddField(
+ model_name='user',
+ name='local_email_enabled',
+ field=models.BooleanField(default=False, help_text="Wether or not to enable the local email account."),
+ ),
+ migrations.AddField(
+ model_name='user',
+ name='local_email_redirect',
+ field=models.BooleanField(default=False, help_text='Whether or not to redirect the local email messages to the main email.'),
+ ),
+ migrations.RunPython(create_initial_email_address,
+ delete_all_email_address),
+ ]
+
diff --git a/users/models.py b/users/models.py
old mode 100644
new mode 100755
index 067a0346..bdc37142
--- a/users/models.py
+++ b/users/models.py
@@ -148,7 +148,7 @@ class UserManager(BaseUserManager):
pseudo=pseudo,
surname=surname,
name=surname,
- email=self.normalize_email(email),
+ email=self.normalize_email(mail),
)
user.set_password(password)
@@ -195,6 +195,14 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
validators=[linux_user_validator]
)
email = models.EmailField()
+ local_email_redirect = models.BooleanField(
+ default=False,
+ help_text="Whether or not to redirect the local email messages to the main email."
+ )
+ local_email_enabled = models.BooleanField(
+ default=False,
+ help_text="Wether or not to enable the local email account."
+ )
school = models.ForeignKey(
'School',
on_delete=models.PROTECT,
@@ -674,6 +682,13 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
self.pwd_ntlm = hashNT(password)
return
+ @cached_property
+ def email_address(self):
+ if (OptionalUser.get_cached_value('local_email_accounts_enabled')
+ and self.local_email_enabled):
+ return self.emailaddress_set.all()
+ return EMailAddress.objects.none()
+
def get_next_domain_name(self):
"""Look for an available name for a new interface for
this user by trying "pseudo0", "pseudo1", "pseudo2", ...
@@ -792,6 +807,32 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
"Droit requis pour changer le shell"
)
+ @staticmethod
+ def can_change_local_email_redirect(user_request, *_args, **_kwargs):
+ """ Check if a user can change local_email_redirect.
+
+ :param user_request: The user who request
+ :returns: a message and a boolean which is True if the user has
+ the right to change a redirection
+ """
+ return (
+ OptionalUser.get_cached_value('local_email_accounts_enabled'),
+ "La gestion des comptes mails doit être activée"
+ )
+
+ @staticmethod
+ def can_change_local_email_enabled(user_request, *_args, **_kwargs):
+ """ Check if a user can change internal address .
+
+ :param user_request: The user who request
+ :returns: a message and a boolean which is True if the user has
+ the right to change internal address
+ """
+ return (
+ OptionalUser.get_cached_value('local_email_accounts_enabled'),
+ "La gestion des comptes mails doit être activée"
+ )
+
@staticmethod
def can_change_force(user_request, *_args, **_kwargs):
""" Check if a user can change a force
@@ -886,9 +927,19 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
'shell': self.can_change_shell,
'force': self.can_change_force,
'selfpasswd': self.check_selfpasswd,
+ 'local_email_redirect': self.can_change_local_email_redirect,
+ 'local_email_enabled' : self.can_change_local_email_enabled,
}
self.__original_state = self.state
+ def clean(self, *args, **kwargs):
+ """Check if this pseudo is already used by any mailalias.
+ Better than raising an error in post-save and catching it"""
+ if (EMailAddress.objects
+ .filter(local_part=self.pseudo)
+ .exclude(user=self)):
+ raise ValidationError("This pseudo is already in use.")
+
def __str__(self):
return self.pseudo
@@ -1011,9 +1062,11 @@ class Club(User):
@receiver(post_save, sender=User)
def user_post_save(**kwargs):
""" Synchronisation post_save : envoie le mail de bienvenue si creation
+ Synchronise le pseudo, en créant un alias mail correspondant
Synchronise le ldap"""
is_created = kwargs['created']
user = kwargs['instance']
+ EMailAddress.objects.get_or_create(local_part=user.pseudo, user=user)
if is_created:
user.notif_inscription()
user.state_sync()
@@ -1593,3 +1646,124 @@ class LdapServiceUserGroup(ldapdb.models.Model):
def __str__(self):
return self.name
+
+
+class EMailAddress(RevMixin, AclMixin, models.Model):
+ """Defines a local email account for a user
+ """
+ user = models.ForeignKey(
+ User,
+ on_delete=models.CASCADE,
+ help_text="User of the local email",
+ )
+ local_part = models.CharField(
+ unique=True,
+ max_length=128,
+ help_text="Local part of the email address"
+ )
+
+ class Meta:
+ permissions = (
+ ("view_emailaddress", "Can see a local email account object"),
+ )
+ verbose_name = "Local email account"
+ verbose_name_plural = "Local email accounts"
+
+ def __str__(self):
+ return self.local_part + OptionalUser.get_cached_value('local_email_domain')
+
+ @cached_property
+ def complete_email_address(self):
+ return self.local_part + OptionalUser.get_cached_value('local_email_domain')
+
+ @staticmethod
+ def can_create(user_request, userid, *_args, **_kwargs):
+ """Check if a user can create a `EMailAddress` object.
+
+ Args:
+ user_request: The user who wants to create the object.
+ userid: The id of the user to whom the account is to be created
+
+ Returns:
+ a message and a boolean which is True if the user can create
+ a local email account.
+ """
+ if user_request.has_perm('users.add_emailaddress'):
+ return True, None
+ if not OptionalUser.get_cached_value('local_email_accounts_enabled'):
+ return False, "The local email accounts are not enabled."
+ if int(user_request.id) != int(userid):
+ return False, "You don't have the right to add a local email account to another user."
+ elif user_request.email_address.count() >= OptionalUser.get_cached_value('max_email_address'):
+ return False, "You have reached the limit of {} local email account.".format(
+ OptionalUser.get_cached_value('max_email_address')
+ )
+ return True, None
+
+ def can_view(self, user_request, *_args, **_kwargs):
+ """Check if a user can view the local email account
+
+ Args:
+ user_request: The user who wants to view the object.
+
+ Returns:
+ a message and a boolean which is True if the user can see
+ the local email account.
+ """
+ if user_request.has_perm('users.view_emailaddress'):
+ return True, None
+ if not OptionalUser.get_cached_value('local_email_accounts_enabled'):
+ return False, "The local email accounts are not enabled."
+ if user_request == self.user:
+ return True, None
+ return False, "You don't have the right to edit someone else's local email account."
+
+ def can_delete(self, user_request, *_args, **_kwargs):
+ """Check if a user can delete the alias
+
+ Args:
+ user_request: The user who wants to delete the object.
+
+ Returns:
+ a message and a boolean which is True if the user can delete
+ the local email account.
+ """
+ if self.local_part == self.user.pseudo:
+ return False, ("You cannot delete a local email account whose "
+ "local part is the same as the username.")
+ if user_request.has_perm('users.delete_emailaddress'):
+ return True, None
+ if not OptionalUser.get_cached_value('local_email_accounts_enabled'):
+ return False, "The local email accounts are not enabled."
+ if user_request == self.user:
+ return True, None
+ return False, ("You don't have the right to delete someone else's "
+ "local email account")
+
+ def can_edit(self, user_request, *_args, **_kwargs):
+ """Check if a user can edit the alias
+
+ Args:
+ user_request: The user who wants to edit the object.
+
+ Returns:
+ a message and a boolean which is True if the user can edit
+ the local email account.
+ """
+ if self.local_part == self.user.pseudo:
+ return False, ("You cannot edit a local email account whose "
+ "local part is the same as the username.")
+ if user_request.has_perm('users.change_emailaddress'):
+ return True, None
+ if not OptionalUser.get_cached_value('local_email_accounts_enabled'):
+ return False, "The local email accounts are not enabled."
+ if user_request == self.user:
+ return True, None
+ return False, ("You don't have the right to edit someone else's "
+ "local email account")
+
+ def clean(self, *args, **kwargs):
+ if "@" in self.local_part:
+ raise ValidationError("The local part cannot contain a @")
+ super(EMailAddress, self).clean(*args, **kwargs)
+
diff --git a/users/templates/users/aff_emailaddress.html b/users/templates/users/aff_emailaddress.html
new file mode 100644
index 00000000..14156ac9
--- /dev/null
+++ b/users/templates/users/aff_emailaddress.html
@@ -0,0 +1,56 @@
+{% comment %}
+Re2o est un logiciel d'administration développé initiallement au rezometz. Il
+se veut agnostique au réseau considéré, de manière à être installable en
+quelques clics.
+
+Copyright © 2017 Gabriel Détraz
+Copyright © 2017 Goulven Kermarec
+Copyright © 2017 Augustin Lemesle
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+{% endcomment %}
+
+{% load acl %}
+{% load logs_extra %}
+
+{% if emailaddress_list.paginator %}
+{% include "pagination.html" with list=emailaddress_list %}
+{% endif %}
+
+
+
+
+ Email address |
+ |
+
+
+ {% for emailaddress in emailaddress_list %}
+ {{ emailaddress.complete_email_address }} |
+
+ {% can_delete emailaddress %}
+ {% include 'buttons/suppr.html' with href='users:del-emailaddress' id=emailaddress.id %}
+ {% acl_end %}
+ {% can_edit emailaddress %}
+ {% include 'buttons/edit.html' with href='users:edit-emailaddress' id=emailaddress.id %}
+ {% acl_end %}
+ {% history_button emailaddress %}
+ |
+
+ {% endfor %}
+
+
+{% if emailaddress_list.paginator %}
+{% include "pagination.html" with list=emailaddress_list %}
+{% endif %}
diff --git a/users/templates/users/index_emailaddress.html b/users/templates/users/index_emailaddress.html
new file mode 100644
index 00000000..6976e168
--- /dev/null
+++ b/users/templates/users/index_emailaddress.html
@@ -0,0 +1,34 @@
+{% extends "users/sidebar.html" %}
+{% comment %}
+Re2o est un logiciel d'administration développé initiallement au rezometz. Il
+se veut agnostique au réseau considéré, de manière à être installable en
+quelques clics.
+
+Copyright © 2017 Gabriel Détraz
+Copyright © 2017 Goulven Kermarec
+Copyright © 2017 Augustin Lemesle
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+{% endcomment %}
+
+{% load bootstrap3 %}
+
+{% block title %}Local email accounts{% endblock %}
+
+{% block content %}
+ Local email accounts
+ {% include "users/aff_emailaddress.html" with emailaddress_list=emailaddress_list %}
+{% endblock %}
+
diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html
index 6e960fc2..1e1926cc 100644
--- a/users/templates/users/profil.html
+++ b/users/templates/users/profil.html
@@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load bootstrap3 %}
{% load acl %}
{% load logs_extra %}
+{% load design %}
{% block title %}Profil{% endblock %}
@@ -406,8 +407,58 @@ with this program; if not, write to the Free Software Foundation, Inc.,
+
+
+
+ Email settings
+
+
+
+
+ {% can_edit users %}
+
+ Edit email settings
+
+ {% acl_end %}
+
+
+ {% if local_email_accounts_enabled %}
+
+
+
+ Contact email address |
+ {{ users.email }} |
+
+
+ Enable the local email account |
+ {{ users.local_email_enabled | tick }} |
+ Enable the local email redirection |
+ {{ users.local_email_redirect | tick }} |
+
+
+
+ {% if users.local_email_enabled %}
+ {% can_create EMailAddress users.id %}
+
+ Add an email address
+
+ {% acl_end %}
+ {% if emailaddress_list %}
+ {% include "users/aff_emailaddress.html" with emailaddress_list=emailaddress_list %}
+ {% endif %}
+ {% endif %}
+ {% else %}
+
+
+
+ Contact email address |
+ {{ users.email }} |
+
+
+
+ {% endif %}
+
+
+
-
-
-
{% endblock %}
diff --git a/users/urls.py b/users/urls.py
index 6bb374c3..f5114600 100644
--- a/users/urls.py
+++ b/users/urls.py
@@ -64,6 +64,18 @@ urlpatterns = [
url(r'^del_whitelist/(?P[0-9]+)$',
views.del_whitelist,
name='del-whitelist'),
+ url(r'^add_emailaddress/(?P[0-9]+)$',
+ views.add_emailaddress,
+ name='add-emailaddress'),
+ url(r'^edit_emailaddress/(?P[0-9]+)$',
+ views.edit_emailaddress,
+ name='edit-emailaddress'),
+ url(r'^del_emailaddress/(?P[0-9]+)$',
+ views.del_emailaddress,
+ name='del-emailaddress'),
+ url(r'^edit_email_settings/(?P[0-9]+)$',
+ views.edit_email_settings,
+ name='edit-email-settings'),
url(r'^add_school/$', views.add_school, name='add-school'),
url(r'^edit_school/(?P[0-9]+)$',
views.edit_school,
diff --git a/users/views.py b/users/views.py
index a6444e33..a4f4ea83 100644
--- a/users/views.py
+++ b/users/views.py
@@ -81,10 +81,13 @@ from .models import (
Adherent,
Club,
ListShell,
+ EMailAddress,
)
from .forms import (
BanForm,
WhitelistForm,
+ EMailAddressForm,
+ EmailSettingsForm,
DelSchoolForm,
DelListRightForm,
NewListRightForm,
@@ -492,6 +495,101 @@ def del_whitelist(request, whitelist, **_kwargs):
)
+@login_required
+@can_create(EMailAddress)
+@can_edit(User)
+def add_emailaddress(request, user, userid):
+ """ Create a new local email account"""
+ emailaddress_instance = EMailAddress(user=user)
+ emailaddress = EMailAddressForm(
+ request.POST or None,
+ instance=emailaddress_instance
+ )
+ if emailaddress.is_valid():
+ emailaddress.save()
+ messages.success(request, "Local email account created")
+ return redirect(reverse(
+ 'users:profil',
+ kwargs={'userid': str(userid)}
+ ))
+ return form(
+ {'userform': emailaddress,
+ 'showCGU': False,
+ 'action_name': 'Add a local email account'},
+ 'users/user.html',
+ request
+ )
+
+
+@login_required
+@can_edit(EMailAddress)
+def edit_emailaddress(request, emailaddress_instance, **_kwargs):
+ """ Edit a local email account"""
+ emailaddress = EMailAddressForm(
+ request.POST or None,
+ instance=emailaddress_instance
+ )
+ if emailaddress.is_valid():
+ if emailaddress.changed_data:
+ emailaddress.save()
+ messages.success(request, "Local email account modified")
+ return redirect(reverse(
+ 'users:profil',
+ kwargs={'userid': str(emailaddress_instance.user.id)}
+ ))
+ return form(
+ {'userform': emailaddress,
+ 'showCGU': False,
+ 'action_name': 'Edit a local email account'},
+ 'users/user.html',
+ request
+ )
+
+
+@login_required
+@can_delete(EMailAddress)
+def del_emailaddress(request, emailaddress, **_kwargs):
+ """Delete a local email account"""
+ if request.method == "POST":
+ emailaddress.delete()
+ messages.success(request, "Local email account deleted")
+ return redirect(reverse(
+ 'users:profil',
+ kwargs={'userid': str(emailaddress.user.id)}
+ ))
+ return form(
+ {'objet': emailaddress, 'objet_name': 'emailaddress'},
+ 'users/delete.html',
+ request
+ )
+
+
+@login_required
+@can_edit(User)
+def edit_email_settings(request, user_instance, **_kwargs):
+ """Edit the email settings of a user"""
+ email_settings = EmailSettingsForm(
+ request.POST or None,
+ instance=user_instance,
+ user=request.user
+ )
+ if email_settings.is_valid():
+ if email_settings.changed_data:
+ email_settings.save()
+ messages.success(request, "Email settings updated")
+ return redirect(reverse(
+ 'users:profil',
+ kwargs={'userid': str(user_instance.id)}
+ ))
+ return form(
+ {'userform': email_settings,
+ 'showCGU': False,
+ 'action_name': 'Edit the email settings'},
+ 'users/user.html',
+ request
+ )
+
+
@login_required
@can_create(School)
def add_school(request):
@@ -914,7 +1012,11 @@ def profil(request, users, **_kwargs):
'white_list': whitelists,
'user_solde': user_solde,
'solde_activated': Paiement.objects.filter(is_balance=True).exists(),
- 'asso_name': AssoOption.objects.first().name
+ 'asso_name': AssoOption.objects.first().name,
+ 'emailaddress_list': users.email_address,
+ 'local_email_accounts_enabled': (
+ OptionalUser.objects.first().local_email_accounts_enabled
+ )
}
)