mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-26 14:42:25 +00:00
Merge branch 'crans' of https://gitlab.federez.net/federez/re2o into crans
This commit is contained in:
commit
093050e245
73 changed files with 2153 additions and 187 deletions
|
@ -1,3 +1,4 @@
|
|||
# -*- mode: python; coding: utf-8 -*-
|
||||
# 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.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- mode: python; coding: utf-8 -*-
|
||||
# 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.
|
||||
|
@ -43,6 +44,7 @@ class ExpiringTokenAuthentication(TokenAuthentication):
|
|||
)
|
||||
utc_now = datetime.datetime.now(datetime.timezone.utc)
|
||||
if token.created < utc_now - token_duration:
|
||||
raise ValueError('boom')
|
||||
raise exceptions.AuthenticationFailed(_('Token has expired'))
|
||||
|
||||
return (token.user, token)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- mode: python; coding: utf-8 -*-
|
||||
# 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.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- mode: python; coding: utf-8 -*-
|
||||
# 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.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- mode: python; coding: utf-8 -*-
|
||||
# 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.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- mode: python; coding: utf-8 -*-
|
||||
# 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.
|
||||
|
@ -484,10 +485,12 @@ class UserSerializer(NamespacedHMSerializer):
|
|||
"""
|
||||
access = serializers.BooleanField(source='has_access')
|
||||
uid = serializers.IntegerField(source='uid_number')
|
||||
email = serializers.CharField(source='get_mail')
|
||||
|
||||
class Meta:
|
||||
model = users.User
|
||||
fields = ('name', 'pseudo', 'email', 'school', 'shell', 'comment',
|
||||
'external_mail', 'redirection', 'internal_address',
|
||||
'state', 'registered', 'telephone', 'solde', 'access',
|
||||
'end_access', 'uid', 'class_name', 'api_url')
|
||||
extra_kwargs = {
|
||||
|
@ -501,10 +504,12 @@ class ClubSerializer(NamespacedHMSerializer):
|
|||
name = serializers.CharField(source='surname')
|
||||
access = serializers.BooleanField(source='has_access')
|
||||
uid = serializers.IntegerField(source='uid_number')
|
||||
email = serializers.CharField(source='get_mail')
|
||||
|
||||
class Meta:
|
||||
model = users.Club
|
||||
fields = ('name', 'pseudo', 'email', 'school', 'shell', 'comment',
|
||||
'external_mail', 'redirection', 'internal_address',
|
||||
'state', 'registered', 'telephone', 'solde', 'room',
|
||||
'access', 'end_access', 'administrators', 'members',
|
||||
'mailing', 'uid', 'api_url')
|
||||
|
@ -518,10 +523,12 @@ class AdherentSerializer(NamespacedHMSerializer):
|
|||
"""
|
||||
access = serializers.BooleanField(source='has_access')
|
||||
uid = serializers.IntegerField(source='uid_number')
|
||||
email = serializers.CharField(source='get_mail')
|
||||
|
||||
class Meta:
|
||||
model = users.Adherent
|
||||
fields = ('name', 'surname', 'pseudo', 'email', 'school', 'shell',
|
||||
fields = ('name', 'surname', 'pseudo', 'email', 'redirection', 'internal_address',
|
||||
'external_mail', 'school', 'shell',
|
||||
'comment', 'state', 'registered', 'telephone', 'room',
|
||||
'solde', 'access', 'end_access', 'uid', 'api_url')
|
||||
extra_kwargs = {
|
||||
|
@ -585,6 +592,15 @@ class WhitelistSerializer(NamespacedHMSerializer):
|
|||
fields = ('user', 'raison', 'date_start', 'date_end', 'active', 'api_url')
|
||||
|
||||
|
||||
class MailAliasSerializer(NamespacedHMSerializer):
|
||||
"""Serialize `users.models.MailAlias` objects.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = users.MailAlias
|
||||
fields = ('user', 'valeur', 'complete_mail')
|
||||
|
||||
|
||||
# SERVICE REGEN
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- mode: python; coding: utf-8 -*-
|
||||
# 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.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- mode: python; coding: utf-8 -*-
|
||||
# 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.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- mode: python; coding: utf-8 -*-
|
||||
# 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.
|
||||
|
@ -91,6 +92,7 @@ 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/mailalias', views.MailAliasViewSet)
|
||||
# SERVICE REGEN
|
||||
router.register_viewset(r'services/regen', views.ServiceRegenViewSet, base_name='serviceregen')
|
||||
# DHCP
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- mode: python; coding: utf-8 -*-
|
||||
# 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.
|
||||
|
@ -461,6 +462,13 @@ class WhitelistViewSet(viewsets.ReadOnlyModelViewSet):
|
|||
serializer_class = serializers.WhitelistSerializer
|
||||
|
||||
|
||||
class MailAliasViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""Exposes list and details of `users.models.MailAlias` objects.
|
||||
"""
|
||||
queryset = users.MailAlias.objects.all()
|
||||
serializer_class = serializers.MailAliasSerializer
|
||||
|
||||
|
||||
# SERVICE REGEN
|
||||
|
||||
|
||||
|
|
|
@ -355,27 +355,47 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
|||
port=port_number
|
||||
)
|
||||
.first())
|
||||
|
||||
# Si le port est inconnu, on place sur le vlan defaut
|
||||
# Aucune information particulière ne permet de déterminer quelle
|
||||
# politique à appliquer sur ce port
|
||||
if not port:
|
||||
return (sw_name, "Chambre inconnue", u'Port inconnu', VLAN_OK)
|
||||
|
||||
# Si un vlan a été précisé, on l'utilise pour VLAN_OK
|
||||
if port.vlan_force:
|
||||
DECISION_VLAN = int(port.vlan_force.vlan_id)
|
||||
# On récupère le profil du port
|
||||
port_profil = port.get_port_profil
|
||||
|
||||
# Si un vlan a été précisé dans la config du port,
|
||||
# on l'utilise pour VLAN_OK
|
||||
if port_profil.vlan_untagged:
|
||||
DECISION_VLAN = int(port_profil.vlan_untagged.vlan_id)
|
||||
extra_log = u"Force sur vlan " + str(DECISION_VLAN)
|
||||
else:
|
||||
DECISION_VLAN = VLAN_OK
|
||||
|
||||
if port.radius == 'NO':
|
||||
# Si le port est désactivé, on rejette sur le vlan de déconnexion
|
||||
if not port.state:
|
||||
return (sw_name, port.room, u'Port desactivé', VLAN_NOK)
|
||||
|
||||
# Si radius est désactivé, on laisse passer
|
||||
if port_profil.radius_type == 'NO':
|
||||
return (sw_name,
|
||||
"",
|
||||
u"Pas d'authentification sur ce port" + extra_log,
|
||||
DECISION_VLAN)
|
||||
|
||||
if port.radius == 'BLOQ':
|
||||
return (sw_name, port.room, u'Port desactive', VLAN_NOK)
|
||||
# Si le 802.1X est activé sur ce port, cela veut dire que la personne a été accept précédemment
|
||||
# Par conséquent, on laisse passer sur le bon vlan
|
||||
if nas_type.port_access_mode == '802.1X' and port_profil.radius_type == '802.1X':
|
||||
room = port.room or "Chambre/local inconnu"
|
||||
return (sw_name, room, u'Acceptation authentification 802.1X', DECISION_VLAN)
|
||||
|
||||
if port.radius == 'STRICT':
|
||||
# Sinon, cela veut dire qu'on fait de l'auth radius par mac
|
||||
# Si le port est en mode strict, on vérifie que tous les users
|
||||
# rattachés à ce port sont bien à jour de cotisation. Sinon on rejette (anti squattage)
|
||||
# Il n'est pas possible de se connecter sur une prise strict sans adhérent à jour de cotis
|
||||
# dedans
|
||||
if port_profil.radius_mode == 'STRICT':
|
||||
room = port.room
|
||||
if not room:
|
||||
return (sw_name, "Inconnue", u'Chambre inconnue', VLAN_NOK)
|
||||
|
@ -390,7 +410,8 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
|||
return (sw_name, room, u'Chambre resident desactive', VLAN_NOK)
|
||||
# else: user OK, on passe à la verif MAC
|
||||
|
||||
if port.radius == 'COMMON' or port.radius == 'STRICT':
|
||||
# Si on fait de l'auth par mac, on cherche l'interface via sa mac dans la bdd
|
||||
if port_profil.radius_mode == 'COMMON' or port_profil.radius_mode == 'STRICT':
|
||||
# Authentification par mac
|
||||
interface = (Interface.objects
|
||||
.filter(mac_address=mac_address)
|
||||
|
@ -399,15 +420,19 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
|||
.first())
|
||||
if not interface:
|
||||
room = port.room
|
||||
# On essaye de register la mac
|
||||
# On essaye de register la mac, si l'autocapture a été activée
|
||||
# Sinon on rejette sur vlan_nok
|
||||
if not nas_type.autocapture_mac:
|
||||
return (sw_name, "", u'Machine inconnue', VLAN_NOK)
|
||||
# On ne peut autocapturer que si on connait la chambre et donc l'user correspondant
|
||||
elif not room:
|
||||
return (sw_name,
|
||||
"Inconnue",
|
||||
u'Chambre et machine inconnues',
|
||||
VLAN_NOK)
|
||||
else:
|
||||
# Si la chambre est vide (local club, prises en libre services)
|
||||
# Impossible d'autocapturer
|
||||
if not room_user:
|
||||
room_user = User.objects.filter(
|
||||
Q(club__room=port.room) | Q(adherent__room=port.room)
|
||||
|
@ -418,6 +443,8 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
|||
u'Machine et propriétaire de la chambre '
|
||||
'inconnus',
|
||||
VLAN_NOK)
|
||||
# Si il y a plus d'un user dans la chambre, impossible de savoir à qui
|
||||
# Ajouter la machine
|
||||
elif room_user.count() > 1:
|
||||
return (sw_name,
|
||||
room,
|
||||
|
@ -425,11 +452,13 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
|||
'dans la chambre/local -> ajout de mac '
|
||||
'automatique impossible',
|
||||
VLAN_NOK)
|
||||
# Si l'adhérent de la chambre n'est pas à jour de cotis, pas d'autocapture
|
||||
elif not room_user.first().has_access():
|
||||
return (sw_name,
|
||||
room,
|
||||
u'Machine inconnue et adhérent non cotisant',
|
||||
VLAN_NOK)
|
||||
# Sinon on capture et on laisse passer sur le bon vlan
|
||||
else:
|
||||
result, reason = (room_user
|
||||
.first()
|
||||
|
@ -449,6 +478,9 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
|||
reason + str(mac_address)
|
||||
),
|
||||
VLAN_NOK)
|
||||
# L'interface a été trouvée, on vérifie qu'elle est active, sinon on reject
|
||||
# Si elle n'a pas d'ipv4, on lui en met une
|
||||
# Enfin on laisse passer sur le vlan pertinent
|
||||
else:
|
||||
room = port.room
|
||||
if not interface.is_active:
|
||||
|
|
|
@ -266,7 +266,6 @@ class ExtensionForm(FormRevMixin, ModelForm):
|
|||
self.fields['origin'].label = 'Enregistrement A origin'
|
||||
self.fields['origin_v6'].label = 'Enregistrement AAAA origin'
|
||||
self.fields['soa'].label = 'En-tête SOA à utiliser'
|
||||
self.fielss['mail_extension'].label = 'Utilisable comme extension mail'
|
||||
|
||||
|
||||
class DelExtensionForm(FormRevMixin, Form):
|
||||
|
|
|
@ -641,10 +641,6 @@ class Extension(RevMixin, AclMixin, models.Model):
|
|||
'SOA',
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
mail_extension = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Determine si l'extension peut être utilisée comme extension mail interne"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
|
|
|
@ -34,6 +34,7 @@ from .models import (
|
|||
OptionalTopologie,
|
||||
GeneralOption,
|
||||
Service,
|
||||
MailContact,
|
||||
AssoOption,
|
||||
MailMessageOption,
|
||||
HomeOption
|
||||
|
@ -65,6 +66,11 @@ class ServiceAdmin(VersionAdmin):
|
|||
pass
|
||||
|
||||
|
||||
class MailContactAdmin(VersionAdmin):
|
||||
"""Class admin gestion des adresses mail de contact"""
|
||||
pass
|
||||
|
||||
|
||||
class AssoOptionAdmin(VersionAdmin):
|
||||
"""Class admin options de l'asso"""
|
||||
pass
|
||||
|
@ -86,5 +92,6 @@ admin.site.register(OptionalTopologie, OptionalTopologieAdmin)
|
|||
admin.site.register(GeneralOption, GeneralOptionAdmin)
|
||||
admin.site.register(HomeOption, HomeOptionAdmin)
|
||||
admin.site.register(Service, ServiceAdmin)
|
||||
admin.site.register(MailContact, MailContactAdmin)
|
||||
admin.site.register(AssoOption, AssoOptionAdmin)
|
||||
admin.site.register(MailMessageOption, MailMessageOptionAdmin)
|
||||
|
|
|
@ -35,7 +35,8 @@ from .models import (
|
|||
AssoOption,
|
||||
MailMessageOption,
|
||||
HomeOption,
|
||||
Service
|
||||
Service,
|
||||
MailContact
|
||||
)
|
||||
|
||||
class EditOptionalUserForm(ModelForm):
|
||||
|
@ -233,3 +234,30 @@ class DelServiceForm(Form):
|
|||
self.fields['services'].queryset = instances
|
||||
else:
|
||||
self.fields['services'].queryset = Service.objects.all()
|
||||
|
||||
class MailContactForm(ModelForm):
|
||||
"""Edition, ajout d'adresse de contact"""
|
||||
class Meta:
|
||||
model = MailContact
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(MailContactForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
|
||||
class DelMailContactForm(Form):
|
||||
"""Suppression d'adresse de contact"""
|
||||
mailcontacts = forms.ModelMultipleChoiceField(
|
||||
queryset=MailContact.objects.none(),
|
||||
label="Enregistrements adresses actuels",
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
instances = kwargs.pop('instances', None)
|
||||
super(DelMailContactForm, self).__init__(*args, **kwargs)
|
||||
if instances:
|
||||
self.fields['mailcontacts'].queryset = instances
|
||||
else:
|
||||
self.fields['mailcontacts'].queryset = MailContact.objects.all()
|
||||
|
|
20
preferences/migrations/0035_optionaluser_mail_extension.py
Normal file
20
preferences/migrations/0035_optionaluser_mail_extension.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# -*- 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', '0034_auto_20180416_1120'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='optionaluser',
|
||||
name='mail_extension',
|
||||
field=models.CharField(default='@example.org', help_text='Extension principale pour les mails internes', max_length=32),
|
||||
),
|
||||
]
|
20
preferences/migrations/0036_optionaluser_mail_accounts.py
Normal file
20
preferences/migrations/0036_optionaluser_mail_accounts.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-06-29 16:01
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('preferences', '0035_optionaluser_mail_extension'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='optionaluser',
|
||||
name='mail_accounts',
|
||||
field=models.BooleanField(default=False, help_text='Activation des comptes mails pour les utilisateurs'),
|
||||
),
|
||||
]
|
20
preferences/migrations/0037_optionaluser_max_mail_alias.py
Normal file
20
preferences/migrations/0037_optionaluser_max_mail_alias.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-06-30 12:32
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('preferences', '0036_optionaluser_mail_accounts'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='optionaluser',
|
||||
name='max_mail_alias',
|
||||
field=models.IntegerField(default=15, help_text="Nombre maximal d'alias pour un utilisateur lambda"),
|
||||
),
|
||||
]
|
28
preferences/migrations/0038_mailcontact.py
Normal file
28
preferences/migrations/0038_mailcontact.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-06-30 15:27
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import re2o.mixins
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('preferences', '0037_optionaluser_max_mail_alias'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='MailContact',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('address', models.EmailField(default='contact@example.org', help_text='Adresse mail de contact', max_length=254)),
|
||||
('commentary', models.CharField(blank=True, help_text="Description de l'utilisation de l'adresse mail associée", max_length=256, null=True)),
|
||||
],
|
||||
options={
|
||||
'permissions': (('view_mailcontact', 'Peut voir les mails de contact'),),
|
||||
},
|
||||
bases=(re2o.mixins.AclMixin, models.Model),
|
||||
),
|
||||
]
|
|
@ -31,6 +31,7 @@ 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 cotisations.models
|
||||
import machines.models
|
||||
from re2o.mixins import AclMixin
|
||||
|
@ -102,6 +103,19 @@ class OptionalUser(AclMixin, PreferencesModel):
|
|||
blank=True,
|
||||
null=True
|
||||
)
|
||||
mail_accounts = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Activation des comptes mails pour les utilisateurs"
|
||||
)
|
||||
mail_extension = models.CharField(
|
||||
max_length = 32,
|
||||
default = "@example.org",
|
||||
help_text="Extension principale pour les mails internes",
|
||||
)
|
||||
max_mail_alias = models.IntegerField(
|
||||
default = 15,
|
||||
help_text = "Nombre maximal d'alias pour un utilisateur lambda"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
|
@ -109,12 +123,17 @@ class OptionalUser(AclMixin, PreferencesModel):
|
|||
)
|
||||
|
||||
def clean(self):
|
||||
"""Creation du mode de paiement par solde"""
|
||||
"""Clean du model:
|
||||
Creation du mode de paiement par solde
|
||||
Vérifie que l'extension mail commence bien par @
|
||||
"""
|
||||
if self.user_solde:
|
||||
p = cotisations.models.Paiement.objects.filter(moyen="Solde")
|
||||
if not len(p):
|
||||
c = cotisations.models.Paiement(moyen="Solde")
|
||||
c.save()
|
||||
if self.mail_extension[0] != "@":
|
||||
raise ValidationError("L'extension mail doit commencer par un @")
|
||||
|
||||
|
||||
@receiver(post_save, sender=OptionalUser)
|
||||
|
@ -273,6 +292,33 @@ class Service(AclMixin, models.Model):
|
|||
def __str__(self):
|
||||
return str(self.name)
|
||||
|
||||
class MailContact(AclMixin, models.Model):
|
||||
"""Addresse mail de contact associée à un commentaire descriptif"""
|
||||
|
||||
address = models.EmailField(
|
||||
default = "contact@example.org",
|
||||
help_text = "Adresse mail de contact"
|
||||
)
|
||||
|
||||
commentary = models.CharField(
|
||||
blank = True,
|
||||
null = True,
|
||||
help_text = "Description de l'utilisation de l'adresse mail associée",
|
||||
max_length = 256
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def get_name(self):
|
||||
return self.address.split("@")[0]
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
("view_mailcontact", "Peut voir les mails de contact"),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return(self.address)
|
||||
|
||||
|
||||
class AssoOption(AclMixin, PreferencesModel):
|
||||
"""Options générales de l'asso : siret, addresse, nom, etc"""
|
||||
|
|
45
preferences/templates/preferences/aff_mailcontact.html
Normal file
45
preferences/templates/preferences/aff_mailcontact.html
Normal file
|
@ -0,0 +1,45 @@
|
|||
{% 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 %}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Adresse</th>
|
||||
<th>Commentaire</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for mailcontact in mailcontact_list %}
|
||||
<tr>
|
||||
<td>{{ mailcontact.address }}</td>
|
||||
<td>{{ mailcontact.commentary }}</td>
|
||||
<td class="text-right">
|
||||
{% can_edit mailcontact %}
|
||||
{% include 'buttons/edit.html' with href='preferences:edit-mailcontact' id=mailcontact.id %}
|
||||
{% acl_end %}
|
||||
{% include 'buttons/history.html' with href='preferences:history' name='mailcontact' id=mailcontact.id %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
|
@ -36,20 +36,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</a>
|
||||
<p>
|
||||
</p>
|
||||
<h5>Généralités</h5>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>Téléphone obligatoirement requis</th>
|
||||
<td>{{ useroptions.is_tel_mandatory }}</td>
|
||||
<th>Activation du solde pour les utilisateurs</th>
|
||||
<td>{{ useroptions.user_solde }}</td>
|
||||
<th>Auto inscription</th>
|
||||
<td>{{ useroptions.self_adhesion }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Champ gpg fingerprint</th>
|
||||
<td>{{ useroptions.gpg_fingerprint }}</td>
|
||||
{% if useroptions.user_solde %}
|
||||
<th>Solde négatif</th>
|
||||
<td>{{ useroptions.solde_negatif }}</td>
|
||||
{% endif %}
|
||||
<th>Shell par défaut des utilisateurs</th>
|
||||
<td>{{ useroptions.shell_default }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Creations d'adhérents par tous</th>
|
||||
|
@ -57,21 +56,37 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<th>Creations de clubs par tous</th>
|
||||
<td>{{ useroptions.all_can_create_club }}</td>
|
||||
</tr>
|
||||
{% if useroptions.user_solde %}
|
||||
</table>
|
||||
<h5>{% if useroptions.user_solde %}<span class="label label-success">Gestion du solde{% else %}<span class="label label-danger">Gesion du solde{% endif%}</span></h5>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>Activation du solde pour les utilisateurs</th>
|
||||
<td>{{ useroptions.user_solde }}</td>
|
||||
|
||||
<th>Solde négatif</th>
|
||||
<td>{{ useroptions.solde_negatif }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Solde maximum</th>
|
||||
<td>{{ useroptions.max_solde }}</td>
|
||||
<th>Montant minimal de rechargement en ligne</th>
|
||||
<td>{{ useroptions.min_online_payment }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
<h5>{% if useroptions.mail_accounts %}<span class="label label-success">Comptes mails{% else %}<span class="label label-danger">Comptes mails{% endif%}</span></h5>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>Auto inscription</th>
|
||||
<td>{{ useroptions.self_adhesion }}</td>
|
||||
<th>Shell par défaut des utilisateurs</th>
|
||||
<td>{{ useroptions.shell_default }}</td>
|
||||
<th>Gestion des comptes mails</th>
|
||||
<td>{{ useroptions.mail_accounts }}</td>
|
||||
<th>Extension mail interne</th>
|
||||
<td>{{ useroptions.mail_extension }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Nombre d'alias maximum</th>
|
||||
<td>{{ useroption.max_mail_alias }}<td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h4>Préférences machines</h4>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'OptionalMachine' %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
|
@ -215,9 +230,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% can_create preferences.Service%}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:add-service' %}"><i class="fa fa-plus"></i> Ajouter un service</a>
|
||||
{% acl_end %}
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'preferences:del-services' %}"><i class="fa fa-trash"></i> Supprimer un ou plusieurs service</a>
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'preferences:del-service' %}"><i class="fa fa-trash"></i> Supprimer un ou plusieurs services</a>
|
||||
{% include "preferences/aff_service.html" with service_list=service_list %}
|
||||
|
||||
<h2>Liste des adresses mail de contact</h2>
|
||||
{% can_create preferences.MailContact%}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:add-mailcontact' %}"><i class="fa fa-plus"></i>Ajouter une adresse</a>
|
||||
{% acl_end %}
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'preferences:del-mailcontact' %}"><i class="fa fa-trash"></i>Supprimer une ou plusieurs adresses</a>
|
||||
{% include "preferences/aff_mailcontact.html" with mailcontact_list=mailcontact_list %}
|
||||
|
||||
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'HomeOption' %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
Editer
|
||||
|
|
|
@ -73,7 +73,14 @@ urlpatterns = [
|
|||
views.edit_service,
|
||||
name='edit-service'
|
||||
),
|
||||
url(r'^del_services/$', views.del_services, name='del-services'),
|
||||
url(r'^del_service/$', views.del_service, name='del-service'),
|
||||
url(r'^add_mailcontact/$', views.add_mailcontact, name='add-mailcontact'),
|
||||
url(
|
||||
r'^edit_mailcontact/(?P<mailcontactid>[0-9]+)$',
|
||||
views.edit_mailcontact,
|
||||
name='edit-mailcontact'
|
||||
),
|
||||
url(r'^del_mailcontact/$', views.del_mailcontact, name='del-mailcontact'),
|
||||
url(
|
||||
r'^history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
|
||||
re2o.views.history,
|
||||
|
|
|
@ -42,9 +42,10 @@ from reversion import revisions as reversion
|
|||
from re2o.views import form
|
||||
from re2o.acl import can_create, can_edit, can_delete_set, can_view_all
|
||||
|
||||
from .forms import ServiceForm, DelServiceForm
|
||||
from .forms import ServiceForm, DelServiceForm, MailContactForm, DelMailContactForm
|
||||
from .models import (
|
||||
Service,
|
||||
MailContact,
|
||||
OptionalUser,
|
||||
OptionalMachine,
|
||||
AssoOption,
|
||||
|
@ -71,6 +72,7 @@ def display_options(request):
|
|||
homeoptions, _created = HomeOption.objects.get_or_create()
|
||||
mailmessageoptions, _created = MailMessageOption.objects.get_or_create()
|
||||
service_list = Service.objects.all()
|
||||
mailcontact_list = MailContact.objects.all()
|
||||
return form({
|
||||
'useroptions': useroptions,
|
||||
'machineoptions': machineoptions,
|
||||
|
@ -79,7 +81,8 @@ def display_options(request):
|
|||
'assooptions': assooptions,
|
||||
'homeoptions': homeoptions,
|
||||
'mailmessageoptions': mailmessageoptions,
|
||||
'service_list': service_list
|
||||
'service_list': service_list,
|
||||
'mailcontact_list': mailcontact_list
|
||||
}, 'preferences/display_preferences.html', request)
|
||||
|
||||
|
||||
|
@ -169,7 +172,7 @@ def edit_service(request, service_instance, **_kwargs):
|
|||
|
||||
@login_required
|
||||
@can_delete_set(Service)
|
||||
def del_services(request, instances):
|
||||
def del_service(request, instances):
|
||||
"""Suppression d'un service de la page d'accueil"""
|
||||
services = DelServiceForm(request.POST or None, instances=instances)
|
||||
if services.is_valid():
|
||||
|
@ -179,7 +182,7 @@ def del_services(request, instances):
|
|||
with transaction.atomic(), reversion.create_revision():
|
||||
services_del.delete()
|
||||
reversion.set_user(request.user)
|
||||
messages.success(request, "Le service a été supprimée")
|
||||
messages.success(request, "Le service a été supprimé")
|
||||
except ProtectedError:
|
||||
messages.error(request, "Erreur le service\
|
||||
suivant %s ne peut être supprimé" % services_del)
|
||||
|
@ -189,3 +192,75 @@ def del_services(request, instances):
|
|||
'preferences/preferences.html',
|
||||
request
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@can_create(MailContact)
|
||||
def add_mailcontact(request):
|
||||
"""Ajout d'une adresse de contact"""
|
||||
mailcontact = MailContactForm(
|
||||
request.POST or None,
|
||||
request.FILES or None
|
||||
)
|
||||
if mailcontact.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
mailcontact.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Création")
|
||||
messages.success(request, "Cette adresse a été ajoutée")
|
||||
return redirect(reverse('preferences:display-options'))
|
||||
return form(
|
||||
{'preferenceform': mailcontact, 'action_name': 'Ajouter'},
|
||||
'preferences/preferences.html',
|
||||
request
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@can_edit(MailContact)
|
||||
def edit_mailcontact(request, mailcontact_instance, **_kwargs):
|
||||
"""Edition des adresses de contacte affichées"""
|
||||
mailcontact = MailContactForm(
|
||||
request.POST or None,
|
||||
request.FILES or None,
|
||||
instance=mailcontact_instance
|
||||
)
|
||||
if mailcontact.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
mailcontact.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Modification")
|
||||
messages.success(request, "Adresse modifiée")
|
||||
return redirect(reverse('preferences:display-options'))
|
||||
return form(
|
||||
{'preferenceform': mailcontact, 'action_name': 'Editer'},
|
||||
'preferences/preferences.html',
|
||||
request
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@can_delete_set(MailContact)
|
||||
def del_mailcontact(request, instances):
|
||||
"""Suppression d'une adresse de contact"""
|
||||
mailcontacts = DelMailContactForm(
|
||||
request.POST or None,
|
||||
instances=instances
|
||||
)
|
||||
if mailcontacts.is_valid():
|
||||
mailcontacts_dels = mailcontacts.cleaned_data['mailcontacts']
|
||||
for mailcontacts_del in mailcontacts_dels:
|
||||
try:
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
mailcontacts_del.delete()
|
||||
reversion.set_user(request.user)
|
||||
messages.success(request, "L'adresse a été supprimée")
|
||||
except ProtectedError:
|
||||
messages.error(request, "Erreur le service\
|
||||
suivant %s ne peut être supprimé" % mailcontacts_del)
|
||||
return redirect(reverse('preferences:display-options'))
|
||||
return form(
|
||||
{'preferenceform': mailcontacts, 'action_name': 'Supprimer'},
|
||||
'preferences/preferences.html',
|
||||
request
|
||||
)
|
||||
|
|
0
printer/__init__.py
Normal file
0
printer/__init__.py
Normal file
3
printer/admin.py
Normal file
3
printer/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
5
printer/apps.py
Normal file
5
printer/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PrinterConfig(AppConfig):
|
||||
name = 'printer'
|
37
printer/forms.py
Normal file
37
printer/forms.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
# -*- mode: python; coding: utf-8 -*-
|
||||
|
||||
"""printer.forms
|
||||
Form to add, edit, cancel printer jobs.
|
||||
Author : Maxime Bombar <bombar@crans.org>.
|
||||
Date : 29/06/2018
|
||||
"""
|
||||
|
||||
from django import forms
|
||||
from django.forms import (
|
||||
Form,
|
||||
ModelForm,
|
||||
)
|
||||
|
||||
import itertools
|
||||
|
||||
from re2o.mixins import FormRevMixin
|
||||
|
||||
from .models import (
|
||||
JobWithOptions,
|
||||
)
|
||||
|
||||
|
||||
class JobWithOptionsForm(FormRevMixin, ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(JobWithOptionsForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
class Meta:
|
||||
model = JobWithOptions
|
||||
fields = [
|
||||
'file',
|
||||
'color',
|
||||
'disposition',
|
||||
'count',
|
||||
]
|
||||
|
26
printer/migrations/0001_initial.py
Normal file
26
printer/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-06-28 18:30
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Dummy',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
48
printer/migrations/0002_auto_20180628_2032.py
Normal file
48
printer/migrations/0002_auto_20180628_2032.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-06-28 18:32
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import printer.models
|
||||
import printer.validators
|
||||
import re2o.mixins
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('printer', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='JobWithOptions',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('file', models.FileField(upload_to=printer.models.user_printing_path, validators=[printer.validators.FileValidator(allowed_types=['application/pdf'], max_size=10485760)])),
|
||||
('starttime', models.DateTimeField(auto_now_add=True)),
|
||||
('endtime', models.DateTimeField(null=True)),
|
||||
('status', models.CharField(choices=[('Printable', 'Printable'), ('Running', 'Running'), ('Cancelled', 'Cancelled'), ('Finished', 'Finished')], max_length=255)),
|
||||
('price', models.IntegerField(default=0)),
|
||||
('format', models.CharField(choices=[('A4', 'A4'), ('A3', 'A4')], default='A4', max_length=255)),
|
||||
('color', models.CharField(choices=[('Greyscale', 'Greyscale'), ('Color', 'Color')], default='Greyscale', max_length=255)),
|
||||
('disposition', models.CharField(choices=[('TwoSided', 'Two sided'), ('OneSided', 'One sided'), ('Booklet', 'Booklet')], default='TwoSided', max_length=255)),
|
||||
('count', models.PositiveIntegerField(default=1)),
|
||||
('stapling', models.CharField(choices=[('None', 'None'), ('TopLeft', 'One top left'), ('TopRight', 'One top right'), ('LeftSided', 'Two left sided'), ('RightSided', 'Two right sided')], default='None', max_length=255)),
|
||||
('perforation', models.CharField(choices=[('None', 'None'), ('TwoLeftSidedHoles', 'Two left sided holes'), ('TwoRightSidedHoles', 'Two right sided holes'), ('TwoTopHoles', 'Two top holes'), ('TwoBottomHoles', 'Two bottom holes'), ('FourLeftSidedHoles', 'Four left sided holes'), ('FourRightSidedHoles', 'Four right sided holes')], default='None', max_length=255)),
|
||||
('printAs', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='print_as_user', to=settings.AUTH_USER_MODEL)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
bases=(re2o.mixins.RevMixin, models.Model),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='dummy',
|
||||
name='user',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Dummy',
|
||||
),
|
||||
]
|
0
printer/migrations/__init__.py
Normal file
0
printer/migrations/__init__.py
Normal file
116
printer/models.py
Normal file
116
printer/models.py
Normal file
|
@ -0,0 +1,116 @@
|
|||
# -*- mode: python; coding: utf-8 -*-
|
||||
|
||||
"""printer.models
|
||||
Models of the printer application
|
||||
Author : Maxime Bombar <bombar@crans.org>.
|
||||
Date : 29/06/2018
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
from django.forms import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.template.defaultfilters import filesizeformat
|
||||
|
||||
from re2o.mixins import RevMixin
|
||||
|
||||
import users.models
|
||||
|
||||
from .validators import (
|
||||
FileValidator,
|
||||
)
|
||||
|
||||
from .settings import (
|
||||
MAX_PRINTFILE_SIZE,
|
||||
ALLOWED_TYPES,
|
||||
)
|
||||
|
||||
|
||||
"""
|
||||
- ```user_printing_path``` is a function that returns the path of the uploaded file, used with the FileField.
|
||||
- ```Job``` is the main model of a printer job. His parent is the ```user``` model.
|
||||
"""
|
||||
|
||||
|
||||
def user_printing_path(instance, filename):
|
||||
# File will be uploaded to MEDIA_ROOT/printings/user_<id>/<filename>
|
||||
return 'printings/user_{0}/{1}'.format(instance.user.id, filename)
|
||||
|
||||
|
||||
class JobWithOptions(RevMixin, models.Model):
|
||||
"""
|
||||
This is the main model of printer application :
|
||||
|
||||
- ```user``` is a ForeignKey to the User Application
|
||||
- ```file``` is the file to print
|
||||
- ```starttime``` is the time when the job was launched
|
||||
- ```endtime``` is the time when the job was stopped.
|
||||
A job is stopped when it is either finished or cancelled.
|
||||
- ```status``` can be running, finished or cancelled.
|
||||
- ```club``` is blank in general. If the job was launched as a club then
|
||||
it is the id of the club.
|
||||
- ```price``` is the total price of this printing.
|
||||
|
||||
Printing Options :
|
||||
|
||||
- ```format``` is the paper format. Example: A4.
|
||||
- ```color``` is the colorization option. Either Color or Greyscale.
|
||||
- ```disposition``` is the paper disposition.
|
||||
- ```count``` is the number of copies to be printed.
|
||||
- ```stapling``` is the stapling options.
|
||||
- ```perforations``` is the perforation options.
|
||||
|
||||
|
||||
Parent class : User
|
||||
"""
|
||||
STATUS_AVAILABLE = (
|
||||
('Printable', 'Printable'),
|
||||
('Running', 'Running'),
|
||||
('Cancelled', 'Cancelled'),
|
||||
('Finished', 'Finished')
|
||||
)
|
||||
user = models.ForeignKey('users.User', on_delete=models.PROTECT)
|
||||
file = models.FileField(upload_to=user_printing_path, validators=[FileValidator(allowed_types=ALLOWED_TYPES, max_size=MAX_PRINTFILE_SIZE)])
|
||||
starttime = models.DateTimeField(auto_now_add=True)
|
||||
endtime = models.DateTimeField(null=True)
|
||||
status = models.CharField(max_length=255, choices=STATUS_AVAILABLE)
|
||||
printAs = models.ForeignKey('users.User', on_delete=models.PROTECT, related_name='print_as_user', null=True)
|
||||
price = models.IntegerField(default=0)
|
||||
|
||||
FORMAT_AVAILABLE = (
|
||||
('A4', 'A4'),
|
||||
('A3', 'A4'),
|
||||
)
|
||||
COLOR_CHOICES = (
|
||||
('Greyscale', 'Greyscale'),
|
||||
('Color', 'Color')
|
||||
)
|
||||
DISPOSITIONS_AVAILABLE = (
|
||||
('TwoSided', 'Two sided'),
|
||||
('OneSided', 'One sided'),
|
||||
('Booklet', 'Booklet')
|
||||
)
|
||||
STAPLING_OPTIONS = (
|
||||
('None', 'None'),
|
||||
('TopLeft', 'One top left'),
|
||||
('TopRight', 'One top right'),
|
||||
('LeftSided', 'Two left sided'),
|
||||
('RightSided', 'Two right sided')
|
||||
)
|
||||
PERFORATION_OPTIONS = (
|
||||
('None', 'None'),
|
||||
('TwoLeftSidedHoles', 'Two left sided holes'),
|
||||
('TwoRightSidedHoles', 'Two right sided holes'),
|
||||
('TwoTopHoles', 'Two top holes'),
|
||||
('TwoBottomHoles', 'Two bottom holes'),
|
||||
('FourLeftSidedHoles', 'Four left sided holes'),
|
||||
('FourRightSidedHoles', 'Four right sided holes')
|
||||
)
|
||||
|
||||
format = models.CharField(max_length=255, choices=FORMAT_AVAILABLE, default='A4')
|
||||
color = models.CharField(max_length=255, choices=COLOR_CHOICES, default='Greyscale')
|
||||
disposition = models.CharField(max_length=255, choices=DISPOSITIONS_AVAILABLE, default='TwoSided')
|
||||
count = models.PositiveIntegerField(default=1)
|
||||
stapling = models.CharField(max_length=255, choices=STAPLING_OPTIONS, default='None')
|
||||
perforation = models.CharField(max_length=255, choices=PERFORATION_OPTIONS, default='None')
|
13
printer/settings.py
Normal file
13
printer/settings.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
"""printer.settings
|
||||
Define variables, to be changed into a configuration table.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
MAX_PRINTFILE_SIZE = 25 * 1024 * 1024 # 25 MB
|
||||
ALLOWED_TYPES = ['application/pdf']
|
12
printer/templates/printer/echec.html
Normal file
12
printer/templates/printer/echec.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load i18n %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load massive_bootstrap_form %}
|
||||
{% load static %}
|
||||
{% block title %}Printing interface{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h3>{% trans "Failure" %}</h3>
|
||||
{% endblock %}
|
87
printer/templates/printer/newjob.html
Normal file
87
printer/templates/printer/newjob.html
Normal file
|
@ -0,0 +1,87 @@
|
|||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load i18n %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load massive_bootstrap_form %}
|
||||
{% load static %}
|
||||
{% block title %}Printing interface{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form class="form" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<h3>{% trans "Printing Menu" %}</h3>
|
||||
{{ jobform.management_form }}
|
||||
{% bootstrap_formset_errors jobform %}
|
||||
<div id="form_set" class="form-group">
|
||||
{% for job in jobform.forms %}
|
||||
<div class='file_to_print form-inline'>
|
||||
{% bootstrap_form job label_class='sr-only' %}
|
||||
<button class="btn btn-danger btn-sm" id="id_form-0-job-remove" type="button">
|
||||
<span class="fa fa-times"></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<input class="btn btn-primary btn-sm" role="button" value="{% trans "Add a file"%}" id="add_one">
|
||||
{% bootstrap_button action_name button_type="submit" icon="star" %}
|
||||
</form>
|
||||
<script type="text/javascript">
|
||||
|
||||
var template = `{% bootstrap_form jobform.empty_form label_class='sr-only' %}
|
||||
<button class="btn btn-danger btn-sm"
|
||||
id="id_form-__prefix__-job-remove" type="button">
|
||||
<span class="fa fa-times"></span>
|
||||
</button>`
|
||||
|
||||
function add_job() {
|
||||
var new_index =
|
||||
document.getElementsByClassName('file_to_print').length;
|
||||
document.getElementById('id_form-TOTAL_FORMS').value ++;
|
||||
var new_job = document.createElement('div');
|
||||
new_job.className = 'file_to_print form-inline';
|
||||
new_job.innerHTML = template.replace(/__prefix__/g, new_index);
|
||||
document.getElementById('form_set').appendChild(new_job);
|
||||
add_listener_for_id(new_index);
|
||||
}
|
||||
|
||||
|
||||
function del_job(event){
|
||||
var job = event.target.parentNode;
|
||||
job.parentNode.removeChild(job);
|
||||
document.getElementById('id_form-TOTAL_FORMS').value --;
|
||||
}
|
||||
|
||||
|
||||
function add_listener_for_id(i){
|
||||
document.getElementById('id_form-' + i.toString() + '-job-remove')
|
||||
.addEventListener("click", function(event){
|
||||
var job = event.target.parentNode;
|
||||
job.parentNode.removeChild(job);
|
||||
document.getElementById('id_form-TOTAL_FORMS').value --;
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Add events manager when DOM is fully loaded
|
||||
document.addEventListener(
|
||||
"DOMContentLoaded",
|
||||
function() {
|
||||
document.getElementById("add_one")
|
||||
.addEventListener("click", add_job, true);
|
||||
document.getElementById('id_form-0-job-remove')
|
||||
.addEventListener("click", function(event){
|
||||
var job = event.target.parentNode;
|
||||
job.parentNode.removeChild(job);
|
||||
document.getElementById('id_form-TOTAL_FORMS').value --;
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
12
printer/templates/printer/success.html
Normal file
12
printer/templates/printer/success.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load i18n %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load massive_bootstrap_form %}
|
||||
{% load static %}
|
||||
{% block title %}Printing interface{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h3>{% trans "Success" %}</h3>
|
||||
{% endblock %}
|
3
printer/tests.py
Normal file
3
printer/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
17
printer/urls.py
Normal file
17
printer/urls.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""printer.urls
|
||||
The defined URLs for the printer app
|
||||
Author : Maxime Bombar <bombar@crans.org>.
|
||||
Date : 29/06/2018
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
import re2o
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^new_job/$', views.new_job, name="new-job"),
|
||||
url(r'^success/$', views.success, name="success"),
|
||||
]
|
72
printer/validators.py
Normal file
72
printer/validators.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
# -*- mode: python; coding: utf-8 -*-
|
||||
|
||||
|
||||
"""printer.validators
|
||||
Custom validators useful for printer application.
|
||||
Author : Maxime Bombar <bombar@crans.org>.
|
||||
Date : 29/06/2018
|
||||
"""
|
||||
|
||||
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.template.defaultfilters import filesizeformat
|
||||
from django.utils.deconstruct import deconstructible
|
||||
|
||||
import mimetypes
|
||||
|
||||
@deconstructible
|
||||
class FileValidator(object):
|
||||
"""
|
||||
Custom validator for files. It checks the size and mimetype.
|
||||
|
||||
Parameters:
|
||||
* ```allowed_types``` is an iterable of allowed mimetypes. Example: ['application/pdf'] for a pdf file.
|
||||
* ```max_size``` is the maximum size allowed in bytes. Example: 25*1024*1024 for 25 MB.
|
||||
|
||||
Usage example:
|
||||
|
||||
class UploadModel(models.Model):
|
||||
file = fileField(..., validators=FileValidator(allowed_types = ['application/pdf'], max_size=25*1024*1024))
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initialize the custom validator.
|
||||
By default, all types and size are allowed.
|
||||
"""
|
||||
self.allowed_types = kwargs.pop('allowed_types', None)
|
||||
self.max_size = kwargs.pop('max_size', None)
|
||||
|
||||
def __call__(self, value):
|
||||
"""
|
||||
Check the type and size.
|
||||
"""
|
||||
|
||||
|
||||
type_message = _("MIME type '%(type)s' is not valid. Please, use one of these types: %(allowed_types)s.")
|
||||
type_code = 'invalidType'
|
||||
|
||||
oversized_message = _('The current file size is %(size)s. The maximum file size is %(max_size)s.')
|
||||
oversized_code = 'oversized'
|
||||
|
||||
|
||||
mimetype = mimetypes.guess_type(value.name)[0]
|
||||
if self.allowed_types and not (mimetype in self.allowed_types):
|
||||
type_params = {
|
||||
'type': mimetype,
|
||||
'allowed_types': ', '.join(self.allowed_types),
|
||||
}
|
||||
|
||||
raise ValidationError(type_message, code=type_code, params=type_params)
|
||||
|
||||
filesize = len(value)
|
||||
if self.max_size and filesize > self.max_size:
|
||||
oversized_params = {
|
||||
'size': '{}'.format(filesizeformat(filesize)),
|
||||
'max_size': '{}'.format(filesizeformat(self.max_size)),
|
||||
}
|
||||
|
||||
raise ValidationError(oversized_message, code=oversized_code, params=oversized_params)
|
55
printer/views.py
Normal file
55
printer/views.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
# -*- mode: python; coding: utf-8 -*-
|
||||
"""printer.views
|
||||
The views for the printer app
|
||||
Author : Maxime Bombar <bombar@crans.org>.
|
||||
Date : 29/06/2018
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.urls import reverse
|
||||
from django.shortcuts import render, redirect
|
||||
from django.forms import modelformset_factory, formset_factory
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
from re2o.views import form
|
||||
from users.models import User
|
||||
|
||||
from . import settings
|
||||
|
||||
from .forms import (
|
||||
JobWithOptionsForm,
|
||||
)
|
||||
|
||||
@login_required
|
||||
def new_job(request):
|
||||
"""
|
||||
View to create a new printing job
|
||||
"""
|
||||
job_formset = formset_factory(JobWithOptionsForm)(
|
||||
request.POST or None, request.FILES or None,
|
||||
)
|
||||
if job_formset.is_valid():
|
||||
for job in job_formset:
|
||||
job = job.save(commit=False)
|
||||
job.user=request.user
|
||||
job.status='Printable'
|
||||
job.save()
|
||||
return redirect(reverse(
|
||||
'printer:success',
|
||||
))
|
||||
return form(
|
||||
{
|
||||
'jobform': job_formset,
|
||||
'action_name': "Print",
|
||||
},
|
||||
'printer/newjob.html',
|
||||
request
|
||||
)
|
||||
|
||||
def success(request):
|
||||
return form(
|
||||
{},
|
||||
'printer/success.html',
|
||||
request
|
||||
)
|
52
re2o/templates/re2o/contact.html
Normal file
52
re2o/templates/re2o/contact.html
Normal file
|
@ -0,0 +1,52 @@
|
|||
{% extends "re2o/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 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Contact" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{% blocktrans %}Contacter l'association {{asso_name}}{% endblocktrans %}</h2>
|
||||
</br>
|
||||
|
||||
{% for contact in contacts %}
|
||||
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"><h4>{{ contact.get_name }}</h4></div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-9">{{ contact.commentary}}</div>
|
||||
<div class="col-sm-3"><a href="mailto:{{ contact.address }}">{{ contact.address }}</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -121,6 +121,7 @@ MODEL_NAME = {
|
|||
'OptionalTopologie': preferences.models.OptionalTopologie,
|
||||
'GeneralOption': preferences.models.GeneralOption,
|
||||
'preferences.Service': preferences.models.Service,
|
||||
'preferences.MailContact': preferences.models.MailContact,
|
||||
'AssoOption': preferences.models.AssoOption,
|
||||
'MailMessageOption': preferences.models.MailMessageOption,
|
||||
# topologie
|
||||
|
@ -133,9 +134,9 @@ MODEL_NAME = {
|
|||
'Room': topologie.models.Room,
|
||||
'Building': topologie.models.Building,
|
||||
'SwitchBay': topologie.models.SwitchBay,
|
||||
'PortProfile': topologie.models.PortProfile,
|
||||
# users
|
||||
'User': users.models.User,
|
||||
'Mail': users.models.Mail,
|
||||
'MailAlias': users.models.MailAlias,
|
||||
'Adherent': users.models.Adherent,
|
||||
'Club': users.models.Club,
|
||||
|
|
|
@ -71,6 +71,7 @@ urlpatterns = [
|
|||
r'^preferences/',
|
||||
include('preferences.urls', namespace='preferences')
|
||||
),
|
||||
url(r'^printer/', include('printer.urls', namespace='printer')),
|
||||
]
|
||||
# Add debug_toolbar URLs if activated
|
||||
if 'debug_toolbar' in settings.INSTALLED_APPS:
|
||||
|
|
|
@ -43,6 +43,7 @@ from django.views.decorators.cache import cache_page
|
|||
import preferences
|
||||
from preferences.models import (
|
||||
Service,
|
||||
MailContact,
|
||||
GeneralOption,
|
||||
AssoOption,
|
||||
HomeOption
|
||||
|
@ -86,6 +87,7 @@ HISTORY_BIND = {
|
|||
'users': {
|
||||
'user': users.models.User,
|
||||
'ban': users.models.Ban,
|
||||
'mailalias': users.models.MailAlias,
|
||||
'whitelist': users.models.Whitelist,
|
||||
'school': users.models.School,
|
||||
'listright': users.models.ListRight,
|
||||
|
@ -94,6 +96,7 @@ HISTORY_BIND = {
|
|||
},
|
||||
'preferences': {
|
||||
'service': preferences.models.Service,
|
||||
'mailcontact': preferences.models.MailContact,
|
||||
},
|
||||
'cotisations': {
|
||||
'facture': cotisations.models.Facture,
|
||||
|
@ -111,6 +114,7 @@ HISTORY_BIND = {
|
|||
'accesspoint': topologie.models.AccessPoint,
|
||||
'switchbay': topologie.models.SwitchBay,
|
||||
'building': topologie.models.Building,
|
||||
'portprofile': topologie.models.PortProfile,
|
||||
},
|
||||
'machines': {
|
||||
'machine': machines.models.Machine,
|
||||
|
@ -229,6 +233,21 @@ def about_page(request):
|
|||
}
|
||||
)
|
||||
|
||||
def contact_page(request):
|
||||
"""The view for the contact page
|
||||
Send all the objects MailContact
|
||||
"""
|
||||
address = MailContact.objects.all()
|
||||
|
||||
return render(
|
||||
request,
|
||||
"re2o/contact.html",
|
||||
{
|
||||
'contacts': address,
|
||||
'asso_name': AssoOption.objects.first().name
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def handler500(request):
|
||||
"""The handler view for a 500 error"""
|
||||
|
|
|
@ -262,9 +262,9 @@ def search_single_word(word, filters, user,
|
|||
) | Q(
|
||||
related__switch__interface__domain__name__icontains=word
|
||||
) | Q(
|
||||
radius__icontains=word
|
||||
custom_profile__name__icontains=word
|
||||
) | Q(
|
||||
vlan_force__name__icontains=word
|
||||
custom_profile__profil_default__icontains=word
|
||||
) | Q(
|
||||
details__icontains=word
|
||||
)
|
||||
|
|
|
@ -108,7 +108,6 @@ footer a {
|
|||
overflow-y: visible;
|
||||
}
|
||||
|
||||
|
||||
/* For tables with long text in cells */
|
||||
|
||||
.table.long_text{
|
||||
|
@ -124,3 +123,42 @@ td.long_text{
|
|||
th.long_text{
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
/* style for the user page */
|
||||
|
||||
.dashboard_container{
|
||||
margin-top: 30px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||
.panel-heading.dashboard{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.panel-body.dashboard{
|
||||
text-align: center;
|
||||
height: 60px;
|
||||
vertical-align:middle;
|
||||
}
|
||||
#grad_red {
|
||||
background: red; /* For browsers that do not support gradients */
|
||||
background: linear-gradient(#ff6363, #fefefe); /* Standard syntax (must be last) */
|
||||
}
|
||||
|
||||
#grad_green {
|
||||
background: green; /* For browsers that do not support gradients */
|
||||
background: linear-gradient(#C8DD58,#4FB64A); /* Standard syntax (must be last) */
|
||||
}
|
||||
|
||||
#grad_grey {
|
||||
background: gray; /* For browsers that do not support gradients */
|
||||
background: linear-gradient(#d4d4ff, #fefefe); /* Standard syntax (must be last) */
|
||||
}
|
||||
|
||||
#grad_machines{
|
||||
background: green;
|
||||
background: linear-gradient(#c266e0,#fefefe)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -112,6 +112,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</ul>
|
||||
</li>
|
||||
{% acl_end %}
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><i class="glyphicon glyphicon-print"></i> Printer<span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url "printer:new-job" %}"><i class="fa fa-print"></i> {% trans "Print" %}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% can_view_app logs %}
|
||||
<li><a href="{% url "logs:index" %}"><i class="fa fa-chart-area"></i> {% trans "Statistics" %}</a></li>
|
||||
{% acl_end %}
|
||||
|
@ -124,8 +130,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</a>
|
||||
</li>
|
||||
{% acl_end %}
|
||||
<li>
|
||||
<a href="{% url 'about' %}"><i class="fa fa-info-circle"></i> {% trans "About" %}</a>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><i class="fas fa-info"></i> {% trans "Info" %}<span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'about' %}"><i class="fa fa-info-circle"></i> {% trans "About" %}</a></li>
|
||||
<li><a href="{% url 'contact' %}"><i class="fas fa-at"></i> {% trans "Contact" %}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% if not request.user.is_authenticated %}
|
||||
{% if var_sa %}
|
||||
|
|
|
@ -38,7 +38,8 @@ from .models import (
|
|||
ConstructorSwitch,
|
||||
AccessPoint,
|
||||
SwitchBay,
|
||||
Building
|
||||
Building,
|
||||
PortProfile,
|
||||
)
|
||||
|
||||
|
||||
|
@ -86,6 +87,9 @@ class BuildingAdmin(VersionAdmin):
|
|||
"""Administration d'un batiment"""
|
||||
pass
|
||||
|
||||
class PortProfileAdmin(VersionAdmin):
|
||||
"""Administration of a port profile"""
|
||||
pass
|
||||
|
||||
admin.site.register(Port, PortAdmin)
|
||||
admin.site.register(AccessPoint, AccessPointAdmin)
|
||||
|
@ -96,3 +100,4 @@ admin.site.register(ModelSwitch, ModelSwitchAdmin)
|
|||
admin.site.register(ConstructorSwitch, ConstructorSwitchAdmin)
|
||||
admin.site.register(Building, BuildingAdmin)
|
||||
admin.site.register(SwitchBay, SwitchBayAdmin)
|
||||
admin.site.register(PortProfile, PortProfileAdmin)
|
||||
|
|
|
@ -35,6 +35,7 @@ from __future__ import unicode_literals
|
|||
from django import forms
|
||||
from django.forms import ModelForm
|
||||
from django.db.models import Prefetch
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from machines.models import Interface
|
||||
from machines.forms import (
|
||||
|
@ -53,6 +54,7 @@ from .models import (
|
|||
AccessPoint,
|
||||
SwitchBay,
|
||||
Building,
|
||||
PortProfile,
|
||||
)
|
||||
|
||||
|
||||
|
@ -78,8 +80,8 @@ class EditPortForm(FormRevMixin, ModelForm):
|
|||
optimiser le temps de chargement avec select_related (vraiment
|
||||
lent sans)"""
|
||||
class Meta(PortForm.Meta):
|
||||
fields = ['room', 'related', 'machine_interface', 'radius',
|
||||
'vlan_force', 'details']
|
||||
fields = ['room', 'related', 'machine_interface', 'custom_profile',
|
||||
'state', 'details']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
|
@ -107,8 +109,8 @@ class AddPortForm(FormRevMixin, ModelForm):
|
|||
'room',
|
||||
'machine_interface',
|
||||
'related',
|
||||
'radius',
|
||||
'vlan_force',
|
||||
'custom_profile',
|
||||
'state',
|
||||
'details'
|
||||
]
|
||||
|
||||
|
@ -262,3 +264,16 @@ class EditBuildingForm(FormRevMixin, ModelForm):
|
|||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(EditBuildingForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
class EditPortProfileForm(FormRevMixin, ModelForm):
|
||||
"""Form to edit a port profile"""
|
||||
class Meta:
|
||||
model = PortProfile
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(EditPortProfileForm, self).__init__(*args,
|
||||
prefix=prefix,
|
||||
**kwargs)
|
||||
|
||||
|
|
44
topologie/migrations/0061_portprofile.py
Normal file
44
topologie/migrations/0061_portprofile.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-06-26 16:37
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import re2o.mixins
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('machines', '0082_auto_20180525_2209'),
|
||||
('topologie', '0060_server'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PortProfile',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
||||
('profil_default', models.CharField(blank=True, choices=[('room', 'room'), ('nothing', 'nothing'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine')], max_length=32, null=True, unique=True, verbose_name='profil default')),
|
||||
('radius_type', models.CharField(choices=[('NO', 'NO'), ('802.1X', '802.1X'), ('MAC-radius', 'MAC-radius')], max_length=32, verbose_name='RADIUS type')),
|
||||
('radius_mode', models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', max_length=32, verbose_name='RADIUS mode')),
|
||||
('speed', models.CharField(choices=[('10-half', '10-half'), ('100-half', '100-half'), ('10-full', '10-full'), ('100-full', '100-full'), ('1000-full', '1000-full'), ('auto', 'auto'), ('auto-10', 'auto-10'), ('auto-100', 'auto-100')], default='auto', help_text='Mode de transmission et vitesse du port', max_length=32, verbose_name='Speed')),
|
||||
('mac_limit', models.IntegerField(blank=True, help_text='Limit du nombre de mac sur le port', null=True, verbose_name='Mac limit')),
|
||||
('flow_control', models.BooleanField(default=False, help_text='Gestion des débits', verbose_name='Flow control')),
|
||||
('dhcp_snooping', models.BooleanField(default=False, help_text='Protection dhcp pirate', verbose_name='Dhcp snooping')),
|
||||
('dhcpv6_snooping', models.BooleanField(default=False, help_text='Protection dhcpv6 pirate', verbose_name='Dhcpv6 snooping')),
|
||||
('arp_protect', models.BooleanField(default=False, help_text="Verification assignation de l'IP par dhcp", verbose_name='Arp protect')),
|
||||
('ra_guard', models.BooleanField(default=False, help_text='Protection contre ra pirate', verbose_name='Ra guard')),
|
||||
('loop_protect', models.BooleanField(default=False, help_text='Protection contre les boucles', verbose_name='Loop Protect')),
|
||||
('vlan_tagged', models.ManyToManyField(blank=True, related_name='vlan_tagged', to='machines.Vlan', verbose_name='VLAN(s) tagged')),
|
||||
('vlan_untagged', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vlan_untagged', to='machines.Vlan', verbose_name='VLAN untagged')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Port profile',
|
||||
'permissions': (('view_port_profile', 'Can view a port profile object'),),
|
||||
'verbose_name_plural': 'Port profiles',
|
||||
},
|
||||
bases=(re2o.mixins.AclMixin, re2o.mixins.RevMixin, models.Model),
|
||||
),
|
||||
]
|
25
topologie/migrations/0062_auto_20180627_0123.py
Normal file
25
topologie/migrations/0062_auto_20180627_0123.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-06-26 23:23
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('topologie', '0061_portprofile'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='portprofile',
|
||||
name='radius_mode',
|
||||
field=models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', help_text="En cas d'auth par mac, auth common ou strcit sur le port", max_length=32, verbose_name='RADIUS mode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='portprofile',
|
||||
name='radius_type',
|
||||
field=models.CharField(choices=[('NO', 'NO'), ('802.1X', '802.1X'), ('MAC-radius', 'MAC-radius')], help_text="Choix du type d'authentification radius : non actif, mac ou 802.1X", max_length=32, verbose_name='RADIUS type'),
|
||||
),
|
||||
]
|
21
topologie/migrations/0063_port_custom_profil.py
Normal file
21
topologie/migrations/0063_port_custom_profil.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-06-28 07:49
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('topologie', '0062_auto_20180627_0123'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='port',
|
||||
name='custom_profil',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='topologie.PortProfile'),
|
||||
),
|
||||
]
|
53
topologie/migrations/0064_createprofil.py
Normal file
53
topologie/migrations/0064_createprofil.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2017-12-31 19:53
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def transfer_profil(apps, schema_editor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
port = apps.get_model("topologie", "Port")
|
||||
profil = apps.get_model("topologie", "PortProfile")
|
||||
vlan = apps.get_model("machines", "Vlan")
|
||||
port_list = port.objects.using(db_alias).all()
|
||||
profil_nothing = profil.objects.using(db_alias).create(name='nothing', profil_default='nothing', radius_type='NO')
|
||||
profil_uplink = profil.objects.using(db_alias).create(name='uplink', profil_default='uplink', radius_type='NO')
|
||||
profil_machine = profil.objects.using(db_alias).create(name='asso_machine', profil_default='asso_machine', radius_type='NO')
|
||||
profil_room = profil.objects.using(db_alias).create(name='room', profil_default='room', radius_type='NO')
|
||||
profil_borne = profil.objects.using(db_alias).create(name='accesspoint', profil_default='accesspoint', radius_type='NO')
|
||||
for vlan_instance in vlan.objects.using(db_alias).all():
|
||||
if port.objects.using(db_alias).filter(vlan_force=vlan_instance):
|
||||
custom_profil = profil.objects.using(db_alias).create(name='vlan-force-' + str(vlan_instance.vlan_id), radius_type='NO', vlan_untagged=vlan_instance)
|
||||
port.objects.using(db_alias).filter(vlan_force=vlan_instance).update(custom_profil=custom_profil)
|
||||
if port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count():
|
||||
profil_room.radius_type = 'MAC-radius'
|
||||
profil_room.radius_mode = 'STRICT'
|
||||
common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON')
|
||||
no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO')
|
||||
port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').update(custom_profil=common_profil)
|
||||
port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil)
|
||||
elif port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count():
|
||||
profil_room.radius_type = 'MAC-radius'
|
||||
profil_room.radius_mode = 'COMMON'
|
||||
strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT')
|
||||
no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO')
|
||||
port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil)
|
||||
port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil)
|
||||
else:
|
||||
strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT')
|
||||
common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON')
|
||||
port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil)
|
||||
port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=common_profil)
|
||||
profil_room.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('topologie', '0063_port_custom_profil'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(transfer_profil),
|
||||
]
|
23
topologie/migrations/0065_auto_20180630_1703.py
Normal file
23
topologie/migrations/0065_auto_20180630_1703.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-06-30 15:03
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('topologie', '0064_createprofil'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='port',
|
||||
name='radius',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='port',
|
||||
name='vlan_force',
|
||||
),
|
||||
]
|
25
topologie/migrations/0066_auto_20180630_1855.py
Normal file
25
topologie/migrations/0066_auto_20180630_1855.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-06-30 16:55
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('topologie', '0065_auto_20180630_1703'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='port',
|
||||
name='state',
|
||||
field=models.BooleanField(default=True, help_text='Etat du port Actif', verbose_name='Etat du port Actif'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='portprofile',
|
||||
name='profil_default',
|
||||
field=models.CharField(blank=True, choices=[('room', 'room'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine'), ('nothing', 'nothing')], max_length=32, null=True, unique=True, verbose_name='profil default'),
|
||||
),
|
||||
]
|
75
topologie/migrations/0067_auto_20180701_0016.py
Normal file
75
topologie/migrations/0067_auto_20180701_0016.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-06-30 22:16
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('topologie', '0066_auto_20180630_1855'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='port',
|
||||
old_name='custom_profil',
|
||||
new_name='custom_profile',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='port',
|
||||
name='state',
|
||||
field=models.BooleanField(default=True, help_text='Port state Active', verbose_name='Port State Active'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='portprofile',
|
||||
name='arp_protect',
|
||||
field=models.BooleanField(default=False, help_text='Check if ip is dhcp assigned', verbose_name='Arp protect'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='portprofile',
|
||||
name='dhcp_snooping',
|
||||
field=models.BooleanField(default=False, help_text='Protect against rogue dhcp', verbose_name='Dhcp snooping'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='portprofile',
|
||||
name='dhcpv6_snooping',
|
||||
field=models.BooleanField(default=False, help_text='Protect against rogue dhcpv6', verbose_name='Dhcpv6 snooping'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='portprofile',
|
||||
name='flow_control',
|
||||
field=models.BooleanField(default=False, help_text='Flow control', verbose_name='Flow control'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='portprofile',
|
||||
name='loop_protect',
|
||||
field=models.BooleanField(default=False, help_text='Protect again loop', verbose_name='Loop Protect'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='portprofile',
|
||||
name='mac_limit',
|
||||
field=models.IntegerField(blank=True, help_text='Limit of mac-address on this port', null=True, verbose_name='Mac limit'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='portprofile',
|
||||
name='ra_guard',
|
||||
field=models.BooleanField(default=False, help_text='Protect against rogue ra', verbose_name='Ra guard'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='portprofile',
|
||||
name='radius_mode',
|
||||
field=models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', help_text='In case of mac-auth : mode common or strict on this port', max_length=32, verbose_name='RADIUS mode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='portprofile',
|
||||
name='radius_type',
|
||||
field=models.CharField(choices=[('NO', 'NO'), ('802.1X', '802.1X'), ('MAC-radius', 'MAC-radius')], help_text='Type of radius auth : inactive, mac-address or 802.1X', max_length=32, verbose_name='RADIUS type'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='portprofile',
|
||||
name='speed',
|
||||
field=models.CharField(choices=[('10-half', '10-half'), ('100-half', '100-half'), ('10-full', '10-full'), ('100-full', '100-full'), ('1000-full', '1000-full'), ('auto', 'auto'), ('auto-10', 'auto-10'), ('auto-100', 'auto-100')], default='auto', help_text='Port speed limit', max_length=32, verbose_name='Speed'),
|
||||
),
|
||||
]
|
|
@ -46,6 +46,7 @@ from django.dispatch import receiver
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.db import IntegrityError
|
||||
from django.db import transaction
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from reversion import revisions as reversion
|
||||
|
||||
from machines.models import Machine, regen
|
||||
|
@ -361,12 +362,6 @@ class Port(AclMixin, RevMixin, models.Model):
|
|||
de forcer un port sur un vlan particulier. S'additionne à la politique
|
||||
RADIUS"""
|
||||
PRETTY_NAME = "Port de switch"
|
||||
STATES = (
|
||||
('NO', 'NO'),
|
||||
('STRICT', 'STRICT'),
|
||||
('BLOQ', 'BLOQ'),
|
||||
('COMMON', 'COMMON'),
|
||||
)
|
||||
|
||||
switch = models.ForeignKey(
|
||||
'Switch',
|
||||
|
@ -392,13 +387,17 @@ class Port(AclMixin, RevMixin, models.Model):
|
|||
blank=True,
|
||||
related_name='related_port'
|
||||
)
|
||||
radius = models.CharField(max_length=32, choices=STATES, default='NO')
|
||||
vlan_force = models.ForeignKey(
|
||||
'machines.Vlan',
|
||||
on_delete=models.SET_NULL,
|
||||
custom_profile = models.ForeignKey(
|
||||
'PortProfile',
|
||||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
state = models.BooleanField(
|
||||
default=True,
|
||||
help_text='Port state Active',
|
||||
verbose_name=_("Port State Active")
|
||||
)
|
||||
details = models.CharField(max_length=255, blank=True)
|
||||
|
||||
class Meta:
|
||||
|
@ -407,6 +406,34 @@ class Port(AclMixin, RevMixin, models.Model):
|
|||
("view_port", "Peut voir un objet port"),
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def get_port_profil(self):
|
||||
"""Return the config profil for this port
|
||||
:returns: the profile of self (port)"""
|
||||
def profil_or_nothing(profil):
|
||||
port_profil = PortProfile.objects.filter(profil_default=profil).first()
|
||||
if port_profil:
|
||||
return port_profil
|
||||
else:
|
||||
nothing = PortProfile.objects.filter(profil_default='nothing').first()
|
||||
if not nothing:
|
||||
nothing = PortProfile.objects.create(profil_default='nothing', name='nothing', radius_type='NO')
|
||||
return nothing
|
||||
|
||||
if self.custom_profile:
|
||||
return self.custom_profile
|
||||
elif self.related:
|
||||
return profil_or_nothing('uplink')
|
||||
elif self.machine_interface:
|
||||
if hasattr(self.machine_interface.machine, 'accesspoint'):
|
||||
return profil_or_nothing('access_point')
|
||||
else:
|
||||
return profil_or_nothing('asso_machine')
|
||||
elif self.room:
|
||||
return profil_or_nothing('room')
|
||||
else:
|
||||
return profil_or_nothing('nothing')
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls, portid, *_args, **kwargs):
|
||||
return (cls.objects
|
||||
|
@ -484,6 +511,135 @@ class Room(AclMixin, RevMixin, models.Model):
|
|||
return self.name
|
||||
|
||||
|
||||
class PortProfile(AclMixin, RevMixin, models.Model):
|
||||
"""Contains the information of the ports' configuration for a switch"""
|
||||
TYPES = (
|
||||
('NO', 'NO'),
|
||||
('802.1X', '802.1X'),
|
||||
('MAC-radius', 'MAC-radius'),
|
||||
)
|
||||
MODES = (
|
||||
('STRICT', 'STRICT'),
|
||||
('COMMON', 'COMMON'),
|
||||
)
|
||||
SPEED = (
|
||||
('10-half', '10-half'),
|
||||
('100-half', '100-half'),
|
||||
('10-full', '10-full'),
|
||||
('100-full', '100-full'),
|
||||
('1000-full', '1000-full'),
|
||||
('auto', 'auto'),
|
||||
('auto-10', 'auto-10'),
|
||||
('auto-100', 'auto-100'),
|
||||
)
|
||||
PROFIL_DEFAULT= (
|
||||
('room', 'room'),
|
||||
('accespoint', 'accesspoint'),
|
||||
('uplink', 'uplink'),
|
||||
('asso_machine', 'asso_machine'),
|
||||
('nothing', 'nothing'),
|
||||
)
|
||||
name = models.CharField(max_length=255, verbose_name=_("Name"))
|
||||
profil_default = models.CharField(
|
||||
max_length=32,
|
||||
choices=PROFIL_DEFAULT,
|
||||
blank=True,
|
||||
null=True,
|
||||
unique=True,
|
||||
verbose_name=_("profil default")
|
||||
)
|
||||
vlan_untagged = models.ForeignKey(
|
||||
'machines.Vlan',
|
||||
related_name='vlan_untagged',
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_("VLAN untagged")
|
||||
)
|
||||
vlan_tagged = models.ManyToManyField(
|
||||
'machines.Vlan',
|
||||
related_name='vlan_tagged',
|
||||
blank=True,
|
||||
verbose_name=_("VLAN(s) tagged")
|
||||
)
|
||||
radius_type = models.CharField(
|
||||
max_length=32,
|
||||
choices=TYPES,
|
||||
help_text="Type of radius auth : inactive, mac-address or 802.1X",
|
||||
verbose_name=_("RADIUS type")
|
||||
)
|
||||
radius_mode = models.CharField(
|
||||
max_length=32,
|
||||
choices=MODES,
|
||||
default='COMMON',
|
||||
help_text="In case of mac-auth : mode common or strict on this port",
|
||||
verbose_name=_("RADIUS mode")
|
||||
)
|
||||
speed = models.CharField(
|
||||
max_length=32,
|
||||
choices=SPEED,
|
||||
default='auto',
|
||||
help_text='Port speed limit',
|
||||
verbose_name=_("Speed")
|
||||
)
|
||||
mac_limit = models.IntegerField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text='Limit of mac-address on this port',
|
||||
verbose_name=_("Mac limit")
|
||||
)
|
||||
flow_control = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Flow control',
|
||||
verbose_name=_("Flow control")
|
||||
)
|
||||
dhcp_snooping = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Protect against rogue dhcp',
|
||||
verbose_name=_("Dhcp snooping")
|
||||
)
|
||||
dhcpv6_snooping = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Protect against rogue dhcpv6',
|
||||
verbose_name=_("Dhcpv6 snooping")
|
||||
)
|
||||
arp_protect = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Check if ip is dhcp assigned',
|
||||
verbose_name=_("Arp protect")
|
||||
)
|
||||
ra_guard = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Protect against rogue ra',
|
||||
verbose_name=_("Ra guard")
|
||||
)
|
||||
loop_protect = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Protect again loop',
|
||||
verbose_name=_("Loop Protect")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
("view_port_profile", _("Can view a port profile object")),
|
||||
)
|
||||
verbose_name = _("Port profile")
|
||||
verbose_name_plural = _("Port profiles")
|
||||
|
||||
security_parameters_fields = ['loop_protect', 'ra_guard', 'arp_protect', 'dhcpv6_snooping', 'dhcp_snooping', 'flow_control']
|
||||
|
||||
@cached_property
|
||||
def security_parameters_enabled(self):
|
||||
return [parameter for parameter in self.security_parameters_fields if getattr(self, parameter)]
|
||||
|
||||
@cached_property
|
||||
def security_parameters_as_str(self):
|
||||
return ','.join(self.security_parameters_enabled)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
@receiver(post_save, sender=AccessPoint)
|
||||
def ap_post_save(**_kwargs):
|
||||
"""Regeneration des noms des bornes vers le controleur"""
|
||||
|
|
|
@ -32,8 +32,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<th>{% include "buttons/sort.html" with prefix='port' col='room' text='Room' %}</th>
|
||||
<th>{% include "buttons/sort.html" with prefix='port' col='interface' text='Interface machine' %}</th>
|
||||
<th>{% include "buttons/sort.html" with prefix='port' col='related' text='Related' %}</th>
|
||||
<th>{% include "buttons/sort.html" with prefix='port' col='radius' text='Radius' %}</th>
|
||||
<th>{% include "buttons/sort.html" with prefix='port' col='vlan' text='Vlan forcé' %}</th>
|
||||
<th>Etat du port</th>
|
||||
<th>Profil du port</th>
|
||||
<th>Détails</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
@ -66,8 +66,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% acl_end %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ port.radius }}</td>
|
||||
<td>{% if not port.vlan_force %}Aucun{% else %}{{ port.vlan_force }}{% endif %}</td>
|
||||
<td>{% if port.state %} <i class="text-success">Actif</i>{% else %}<i class="text-danger">Désactivé</i>{% endif %}</td>
|
||||
<td>{% if not port.custom_profile %}<u>Par défaut</u> : {% endif %}{{port.get_port_profil}}</td>
|
||||
<td>{{ port.details }}</td>
|
||||
<td class="text-right">
|
||||
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'port' port.pk %}">
|
||||
|
|
85
topologie/templates/topologie/aff_port_profile.html
Normal file
85
topologie/templates/topologie/aff_port_profile.html
Normal file
|
@ -0,0 +1,85 @@
|
|||
{% 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 © 2018 Gabriel Détraz
|
||||
|
||||
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 i18n %}
|
||||
|
||||
<div class="table-responsive">
|
||||
|
||||
{% if port_profile_list.paginator %}
|
||||
{% include "pagination.html" with list=port_profile_list %}
|
||||
{% endif %}
|
||||
|
||||
<thead>
|
||||
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Default for" %}</th>
|
||||
<th>{% trans "VLANs" %}</th>
|
||||
<th>{% trans "RADIUS settings" %}</th>
|
||||
<th>{% trans "Speed" %}</th>
|
||||
<th>{% trans "Mac address limit" %}</th>
|
||||
<th>{% trans "Security" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for port_profile in port_profile_list %}
|
||||
<tr>
|
||||
<td>{{port_profile.name}}</td>
|
||||
<td>{{port_profile.profil_default}}</td>
|
||||
<td>
|
||||
{% if port_profile.vlan_untagged %}
|
||||
<b>Untagged : </b>{{port_profile.vlan_untagged}}
|
||||
<br>
|
||||
{% endif %}
|
||||
{% if port_profile.vlan_tagged.all %}
|
||||
<b>Tagged : </b>{{port_profile.vlan_tagged.all|join:", "}}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<b>Type : </b>{{port_profile.radius_type}}
|
||||
{% if port_profile.radius_type == "MAC-radius" %}
|
||||
<br>
|
||||
<b>Mode : </b>{{port_profile.radius_mode}}</td>
|
||||
{% endif %}
|
||||
<td>{{port_profile.speed}}</td>
|
||||
<td>{{port_profile.mac_limit}}</td>
|
||||
<td>{{port_profile.security_parameters_enabled|join:"<br>"}}</td>
|
||||
<td class="text-right">
|
||||
{% include 'buttons/history.html' with href='topologie:history' name='portprofile' id=port_profile.pk %}
|
||||
{% can_edit port_profile %}
|
||||
{% include 'buttons/edit.html' with href='topologie:edit-port-profile' id=port_profile.pk %}
|
||||
{% acl_end %}
|
||||
{% can_delete port_profile %}
|
||||
{% include 'buttons/suppr.html' with href='topologie:del-port-profile' id=port_profile.pk %}
|
||||
{% acl_end %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% if port_profile_list.paginator %}
|
||||
{% include "pagination.html" with list=port_profile_list %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
|
@ -72,5 +72,4 @@ Topologie des Switchs
|
|||
<br />
|
||||
<br />
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
|
43
topologie/templates/topologie/index_portprofile.html
Normal file
43
topologie/templates/topologie/index_portprofile.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
{% extends "topologie/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 © 2018 Gabriel Détraz
|
||||
|
||||
|
||||
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 %}
|
||||
{% load acl %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}Switchs{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2>{% trans "Port profiles" %}</h2>
|
||||
{% can_create PortProfile %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:new-port-profile' %}"><i class="fa fa-plus"></i>{% trans " Add a port profile" %}</a>
|
||||
<hr>
|
||||
{% acl_end %}
|
||||
{% include "topologie/aff_port_profile.html" with port_profile_list=port_profile_list %}
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
{% endblock %}
|
|
@ -33,6 +33,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<i class="fa fa-microchip"></i>
|
||||
Switchs
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-info" href="{% url "topologie:index-port-profile" %}">
|
||||
<i class="fa fa-cogs"></i>
|
||||
Config des ports switchs
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-info" href="{% url "topologie:index-ap" %}">
|
||||
<i class="fa fa-wifi"></i>
|
||||
Bornes WiFi
|
||||
|
|
|
@ -113,4 +113,16 @@ urlpatterns = [
|
|||
url(r'^del_building/(?P<buildingid>[0-9]+)$',
|
||||
views.del_building,
|
||||
name='del-building'),
|
||||
url(r'^index_port_profile/$',
|
||||
views.index_port_profile,
|
||||
name='index-port-profile'),
|
||||
url(r'^new_port_profile/$',
|
||||
views.new_port_profile,
|
||||
name='new-port-profile'),
|
||||
url(r'^edit_port_profile/(?P<portprofileid>[0-9]+)$',
|
||||
views.edit_port_profile,
|
||||
name='edit-port-profile'),
|
||||
url(r'^del_port_profile/(?P<portprofileid>[0-9]+)$',
|
||||
views.del_port_profile,
|
||||
name='del-port-profile'),
|
||||
]
|
||||
|
|
|
@ -47,6 +47,7 @@ from django.template.loader import get_template
|
|||
from django.template import Context, Template, loader
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
import tempfile
|
||||
|
||||
|
@ -80,6 +81,7 @@ from .models import (
|
|||
SwitchBay,
|
||||
Building,
|
||||
Server,
|
||||
PortProfile,
|
||||
)
|
||||
from .forms import (
|
||||
EditPortForm,
|
||||
|
@ -94,7 +96,8 @@ from .forms import (
|
|||
AddAccessPointForm,
|
||||
EditAccessPointForm,
|
||||
EditSwitchBayForm,
|
||||
EditBuildingForm
|
||||
EditBuildingForm,
|
||||
EditPortProfileForm,
|
||||
)
|
||||
|
||||
from subprocess import (
|
||||
|
@ -124,10 +127,12 @@ def index(request):
|
|||
request.GET.get('order'),
|
||||
SortTable.TOPOLOGIE_INDEX
|
||||
)
|
||||
|
||||
|
||||
pagination_number = GeneralOption.get_cached_value('pagination_number')
|
||||
switch_list = re2o_paginator(request, switch_list, pagination_number)
|
||||
|
||||
if any(service_link.need_regen() for service_link in Service_link.objects.filter(service__service_type='graph_topo')):
|
||||
if any(service_link.need_regen for service_link in Service_link.objects.filter(service__service_type='graph_topo')):
|
||||
make_machine_graph()
|
||||
for service_link in Service_link.objects.filter(service__service_type='graph_topo'):
|
||||
service_link.done_regen()
|
||||
|
@ -141,6 +146,19 @@ def index(request):
|
|||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@can_view_all(PortProfile)
|
||||
def index_port_profile(request):
|
||||
pagination_number = GeneralOption.get_cached_value('pagination_number')
|
||||
port_profile_list = PortProfile.objects.all().select_related('vlan_untagged')
|
||||
port_profile_list = re2o_paginator(request, port_profile_list, pagination_number)
|
||||
return render(
|
||||
request,
|
||||
'topologie/index_portprofile.html',
|
||||
{'port_profile_list': port_profile_list}
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@can_view_all(Port)
|
||||
@can_view(Switch)
|
||||
|
@ -955,6 +973,59 @@ def del_constructor_switch(request, constructor_switch, **_kwargs):
|
|||
}, 'topologie/delete.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@can_create(PortProfile)
|
||||
def new_port_profile(request):
|
||||
"""Create a new port profile"""
|
||||
port_profile = EditPortProfileForm(request.POST or None)
|
||||
if port_profile.is_valid():
|
||||
port_profile.save()
|
||||
messages.success(request, _("Port profile created"))
|
||||
return redirect(reverse('topologie:index'))
|
||||
return form(
|
||||
{'topoform': port_profile, 'action_name': _("Create")},
|
||||
'topologie/topo.html',
|
||||
request
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@can_edit(PortProfile)
|
||||
def edit_port_profile(request, port_profile, **_kwargs):
|
||||
"""Edit a port profile"""
|
||||
port_profile = EditPortProfileForm(request.POST or None, instance=port_profile)
|
||||
if port_profile.is_valid():
|
||||
if port_profile.changed_data:
|
||||
port_profile.save()
|
||||
messages.success(request, _("Port profile modified"))
|
||||
return redirect(reverse('topologie:index'))
|
||||
return form(
|
||||
{'topoform': port_profile, 'action_name': _("Edit")},
|
||||
'topologie/topo.html',
|
||||
request
|
||||
)
|
||||
|
||||
|
||||
|
||||
@login_required
|
||||
@can_delete(PortProfile)
|
||||
def del_port_profile(request, port_profile, **_kwargs):
|
||||
"""Delete a port profile"""
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
port_profile.delete()
|
||||
messages.success(request,
|
||||
_("The port profile was successfully deleted"))
|
||||
except ProtectedError:
|
||||
messages.success(request,
|
||||
_("Impossible to delete the port profile"))
|
||||
return redirect(reverse('topologie:index'))
|
||||
return form(
|
||||
{'objet': port_profile, 'objet_name': _("Port profile")},
|
||||
'topologie/delete.html',
|
||||
request
|
||||
)
|
||||
|
||||
def make_machine_graph():
|
||||
"""
|
||||
Create the graph of switchs, machines and access points.
|
||||
|
|
|
@ -34,7 +34,6 @@ from reversion.admin import VersionAdmin
|
|||
|
||||
from .models import (
|
||||
User,
|
||||
Mail,
|
||||
MailAlias,
|
||||
ServiceUser,
|
||||
School,
|
||||
|
@ -110,6 +109,11 @@ class BanAdmin(VersionAdmin):
|
|||
pass
|
||||
|
||||
|
||||
class MailAliasAdmin(VersionAdmin):
|
||||
"""Gestion des alias mail"""
|
||||
pass
|
||||
|
||||
|
||||
class WhitelistAdmin(VersionAdmin):
|
||||
"""Gestion des whitelist"""
|
||||
pass
|
||||
|
@ -127,7 +131,7 @@ class UserAdmin(VersionAdmin, BaseUserAdmin):
|
|||
list_display = (
|
||||
'pseudo',
|
||||
'surname',
|
||||
'email',
|
||||
'external_mail',
|
||||
'school',
|
||||
'is_admin',
|
||||
'shell'
|
||||
|
@ -141,7 +145,7 @@ class UserAdmin(VersionAdmin, BaseUserAdmin):
|
|||
'Personal info',
|
||||
{
|
||||
'fields':
|
||||
('surname', 'email', 'school', 'shell', 'uid_number')
|
||||
('surname', 'external_mail', 'school', 'shell', 'uid_number')
|
||||
}
|
||||
),
|
||||
('Permissions', {'fields': ('is_admin', )}),
|
||||
|
@ -156,7 +160,7 @@ class UserAdmin(VersionAdmin, BaseUserAdmin):
|
|||
'fields': (
|
||||
'pseudo',
|
||||
'surname',
|
||||
'email',
|
||||
'external_mail',
|
||||
'school',
|
||||
'is_admin',
|
||||
'password1',
|
||||
|
@ -213,6 +217,7 @@ admin.site.register(School, SchoolAdmin)
|
|||
admin.site.register(ListRight, ListRightAdmin)
|
||||
admin.site.register(ListShell, ListShellAdmin)
|
||||
admin.site.register(Ban, BanAdmin)
|
||||
admin.site.register(MailAlias, MailAliasAdmin)
|
||||
admin.site.register(Whitelist, WhitelistAdmin)
|
||||
admin.site.register(Request, RequestAdmin)
|
||||
# Now register the new UserAdmin...
|
||||
|
|
|
@ -140,7 +140,7 @@ class UserCreationForm(FormRevMixin, forms.ModelForm):
|
|||
|
||||
class Meta:
|
||||
model = Adherent
|
||||
fields = ('pseudo', 'surname', 'email')
|
||||
fields = ('pseudo', 'surname')
|
||||
|
||||
def clean_password2(self):
|
||||
"""Verifie que password1 et 2 sont identiques"""
|
||||
|
@ -220,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__)
|
||||
|
@ -306,14 +306,12 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
|
|||
self.fields['room'].label = 'Chambre'
|
||||
self.fields['room'].empty_label = "Pas de chambre"
|
||||
self.fields['school'].empty_label = "Séléctionner un établissement"
|
||||
|
||||
class Meta:
|
||||
model = Adherent
|
||||
fields = [
|
||||
'name',
|
||||
'surname',
|
||||
'pseudo',
|
||||
'email',
|
||||
'school',
|
||||
'comment',
|
||||
'room',
|
||||
|
@ -365,7 +363,6 @@ class ClubForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
|
|||
fields = [
|
||||
'surname',
|
||||
'pseudo',
|
||||
'email',
|
||||
'school',
|
||||
'comment',
|
||||
'room',
|
||||
|
@ -597,9 +594,23 @@ class MailAliasForm(FormRevMixin, ModelForm):
|
|||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(MailAliasForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['valeur'].label = 'nom de l\'adresse mail'
|
||||
self.fields['extension'].label = 'extension de l\'adresse mail'
|
||||
self.fields['valeur'].label = "Prefixe de l'alias mail. Ne peut contenir de @"
|
||||
|
||||
class Meta:
|
||||
model = MailAlias
|
||||
exclude = ['mail']
|
||||
exclude = ['user']
|
||||
|
||||
class MailForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
|
||||
"""Creation, edition des paramètres mail"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(MailForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['external_mail'].label = 'Adresse mail externe'
|
||||
if 'redirection' in self.fields:
|
||||
self.fields['redirection'].label = 'Activation de la redirection vers l\'adress externe'
|
||||
if 'internal_address' in self.fields:
|
||||
self.fields['internal_address'].label = 'Adresse mail interne'
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['external_mail', 'redirection', 'internal_address']
|
||||
|
|
46
users/migrations/0073_auto_20180629_1614.py
Normal file
46
users/migrations/0073_auto_20180629_1614.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
# -*- 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):
|
||||
|
||||
dependencies = [
|
||||
('users', '0072_auto_20180426_2021'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='MailAlias',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('valeur', models.CharField(help_text="username de l'adresse mail", max_length=64, unique=True)),
|
||||
],
|
||||
bases=(re2o.mixins.RevMixin, re2o.mixins.AclMixin, models.Model),
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='user',
|
||||
old_name='email',
|
||||
new_name='external_mail',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='internal_address',
|
||||
field=models.BooleanField(default=False, help_text="Activer ou non l'utilisation de l'adresse mail interne"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='redirection',
|
||||
field=models.BooleanField(default=False, help_text='Activer ou non la redirection du mail interne vers le mail externe'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mailalias',
|
||||
name='user',
|
||||
field=models.ForeignKey(blank=True, help_text='Utilisateur associé', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
41
users/migrations/0074_auto_20180629_1717.py
Normal file
41
users/migrations/0074_auto_20180629_1717.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-06-29 15:17
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0073_auto_20180629_1614'),
|
||||
]
|
||||
|
||||
def transfer_pseudo(apps, schema_editor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
users = apps.get_model("users", "User")
|
||||
mailalias = apps.get_model("users", "MailAlias")
|
||||
users_list = users.objects.using(db_alias).all()
|
||||
for user in users_list:
|
||||
mailalias.objects.using(db_alias).create(valeur=user.pseudo, user=user)
|
||||
|
||||
def untransfer_pseudo(apps, schema_editor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
mailalias = apps.get_model("users", "MailAlias")
|
||||
mailalias.objects.using(db_alias).delete()
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='mailalias',
|
||||
name='user',
|
||||
field=models.ForeignKey(help_text='Utilisateur associé', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailalias',
|
||||
name='valeur',
|
||||
field=models.CharField(help_text="Valeur de l'alias mail", max_length=128, unique=True),
|
||||
),
|
||||
migrations.RunPython(transfer_pseudo, untransfer_pseudo),
|
||||
]
|
163
users/models.py
163
users/models.py
|
@ -51,7 +51,7 @@ import datetime
|
|||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django import forms
|
||||
from django.forms import ValidationError
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
from django.dispatch import receiver
|
||||
from django.utils.functional import cached_property
|
||||
|
@ -79,7 +79,7 @@ from re2o.field_permissions import FieldPermissionModelMixin
|
|||
from re2o.mixins import AclMixin, RevMixin
|
||||
|
||||
from cotisations.models import Cotisation, Facture, Paiement, Vente
|
||||
from machines.models import Domain, Interface, Machine, regen, Extension
|
||||
from machines.models import Domain, Interface, Machine, regen
|
||||
from preferences.models import GeneralOption, AssoOption, OptionalUser
|
||||
from preferences.models import OptionalMachine, MailMessageOption
|
||||
|
||||
|
@ -97,7 +97,7 @@ def linux_user_validator(login):
|
|||
""" Retourne une erreur de validation si le login ne respecte
|
||||
pas les contraintes unix (maj, min, chiffres ou tiret)"""
|
||||
if not linux_user_check(login):
|
||||
raise forms.ValidationError(
|
||||
raise ValidationError(
|
||||
", ce pseudo ('%(label)s') contient des carractères interdits",
|
||||
params={'label': login},
|
||||
)
|
||||
|
@ -134,7 +134,7 @@ class UserManager(BaseUserManager):
|
|||
self,
|
||||
pseudo,
|
||||
surname,
|
||||
email,
|
||||
external_mail,
|
||||
password=None,
|
||||
su=False
|
||||
):
|
||||
|
@ -148,7 +148,7 @@ class UserManager(BaseUserManager):
|
|||
pseudo=pseudo,
|
||||
surname=surname,
|
||||
name=surname,
|
||||
email=self.normalize_email(email),
|
||||
external_mail=external_mail,
|
||||
)
|
||||
|
||||
user.set_password(password)
|
||||
|
@ -157,19 +157,19 @@ class UserManager(BaseUserManager):
|
|||
user.save(using=self._db)
|
||||
return user
|
||||
|
||||
def create_user(self, pseudo, surname, email, password=None):
|
||||
def create_user(self, pseudo, surname, external_mail, password=None):
|
||||
"""
|
||||
Creates and saves a User with the given pseudo, name, surname, email,
|
||||
and password.
|
||||
"""
|
||||
return self._create_user(pseudo, surname, email, password, False)
|
||||
return self._create_user(pseudo, surname, external_mail, password, False)
|
||||
|
||||
def create_superuser(self, pseudo, surname, email, password):
|
||||
def create_superuser(self, pseudo, surname, external_mail, password):
|
||||
"""
|
||||
Creates and saves a superuser with the given pseudo, name, surname,
|
||||
email, and password.
|
||||
"""
|
||||
return self._create_user(pseudo, surname, email, password, True)
|
||||
return self._create_user(pseudo, surname, external_mail, password, True)
|
||||
|
||||
|
||||
class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
|
||||
|
@ -194,13 +194,15 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
|
|||
help_text="Doit contenir uniquement des lettres, chiffres, ou tirets",
|
||||
validators=[linux_user_validator]
|
||||
)
|
||||
email = models.EmailField()
|
||||
"""
|
||||
email= models.OneToOneField(
|
||||
Mail,
|
||||
on_delete=models.PROTECT
|
||||
external_mail = models.EmailField()
|
||||
redirection = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Activer ou non la redirection du mail interne vers le mail externe'
|
||||
)
|
||||
internal_address = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Activer ou non l\'utilisation de l\'adresse mail interne'
|
||||
)
|
||||
"""
|
||||
school = models.ForeignKey(
|
||||
'School',
|
||||
on_delete=models.PROTECT,
|
||||
|
@ -233,7 +235,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
|
|||
)
|
||||
|
||||
USERNAME_FIELD = 'pseudo'
|
||||
REQUIRED_FIELDS = ['surname', 'email']
|
||||
REQUIRED_FIELDS = ['surname', 'external_mail']
|
||||
|
||||
objects = UserManager()
|
||||
|
||||
|
@ -527,7 +529,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
|
|||
user_ldap.sn = self.pseudo
|
||||
user_ldap.dialupAccess = str(self.has_access())
|
||||
user_ldap.home_directory = '/home/' + self.pseudo
|
||||
user_ldap.mail = self.email
|
||||
user_ldap.mail = self.get_mail()
|
||||
user_ldap.given_name = self.surname.lower() + '_'\
|
||||
+ self.name.lower()[:3]
|
||||
user_ldap.gid = LDAP['user_gid']
|
||||
|
@ -680,10 +682,10 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
|
|||
"""
|
||||
Return the mail address choosen by the user
|
||||
"""
|
||||
if not self.mail.internal_activated:
|
||||
return(self.mail.external)
|
||||
if not OptionalUser.get_cached_value('mail_accounts') or not self.internal_address or self.redirection:
|
||||
return str(self.external_mail)
|
||||
else:
|
||||
return(self.mail.mailalias_set.first())
|
||||
return str(self.mailalias_set.get(valeur=self.pseudo))
|
||||
|
||||
def get_next_domain_name(self):
|
||||
"""Look for an available name for a new interface for
|
||||
|
@ -803,6 +805,32 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
|
|||
"Droit requis pour changer le shell"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def can_change_redirection(user_request, *_args, **_kwargs):
|
||||
""" Check if a user can change redirection.
|
||||
|
||||
: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('mail_accounts'),
|
||||
"La gestion des comptes mails doit être activée"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def can_change_internal_address(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('mail_accounts'),
|
||||
"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
|
||||
|
@ -897,12 +925,19 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
|
|||
'shell': self.can_change_shell,
|
||||
'force': self.can_change_force,
|
||||
'selfpasswd': self.check_selfpasswd,
|
||||
'redirection': self.can_change_redirection,
|
||||
'internal_address' : self.can_change_internal_address,
|
||||
}
|
||||
|
||||
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 MailAlias.objects.filter(valeur=self.pseudo).exclude(user=self):
|
||||
raise ValidationError("Ce pseudo est déjà utilisé")
|
||||
|
||||
def __str__(self):
|
||||
return self.pseudo
|
||||
|
||||
|
||||
class Adherent(User):
|
||||
""" A class representing a member (it's a user with special
|
||||
informations) """
|
||||
|
@ -1021,9 +1056,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']
|
||||
mail_alias, created = MailAlias.objects.get_or_create(valeur=user.pseudo, user=user)
|
||||
if is_created:
|
||||
user.notif_inscription()
|
||||
user.ldap_sync(
|
||||
|
@ -1594,62 +1631,53 @@ class LdapServiceUserGroup(ldapdb.models.Model):
|
|||
return self.name
|
||||
|
||||
|
||||
class Mail(RevMixin, AclMixin, models.Model):
|
||||
"""
|
||||
Mail account of a user
|
||||
|
||||
Compte mail d'un utilisateur
|
||||
"""
|
||||
external_mail = models.EmailField(help_text="Mail externe")
|
||||
user = models.ForeignKey(
|
||||
'User',
|
||||
on_delete=models.CASCADE,
|
||||
help_text="Object mail d'un User"
|
||||
)
|
||||
redirection = models.BooleanField(
|
||||
default=False
|
||||
)
|
||||
internal_address = models.BooleanField(
|
||||
default=False
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.mail
|
||||
|
||||
|
||||
class MailAlias(RevMixin, AclMixin, models.Model):
|
||||
"""
|
||||
Define a alias for a user Mail
|
||||
|
||||
Définit un aliase pour un Mail d'utilisateur
|
||||
Définit un alias pour un Mail d'utilisateur
|
||||
"""
|
||||
mail = models.ForeignKey(
|
||||
'Mail',
|
||||
user = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.CASCADE,
|
||||
help_text="Objects Mail associé"
|
||||
help_text="Utilisateur associé",
|
||||
)
|
||||
valeur = models.CharField(
|
||||
max_length=64,
|
||||
help_text="username de l'adresse mail"
|
||||
unique=True,
|
||||
max_length=128,
|
||||
help_text="Valeur de l'alias mail"
|
||||
)
|
||||
extension = models.ForeignKey(
|
||||
'Extension',
|
||||
on_delete=models.CASCADE,
|
||||
help_text="Extension mail interne"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('valeur', 'extension',)
|
||||
|
||||
def __str__(self):
|
||||
return self.valeur + "@" + self.extension
|
||||
return self.complete_mail
|
||||
|
||||
@cached_property
|
||||
def complete_mail(self):
|
||||
return self.valeur + OptionalUser.get_cached_value('mail_extension')
|
||||
|
||||
@staticmethod
|
||||
def can_create(user_request, userid, *_args, **_kwargs):
|
||||
"""Check if an user can create an mailalias object.
|
||||
|
||||
:param user_request: The user who wants to create a mailalias object.
|
||||
:return: a message and a boolean which is True if the user can create
|
||||
an user or if the `options.all_can_create` is set.
|
||||
"""
|
||||
if not user_request.has_perm('users.add_mailalias'):
|
||||
if int(user_request.id) != int(userid):
|
||||
return False, 'Vous n\'avez pas le droit d\'ajouter un alias à une autre personne'
|
||||
elif user_request.mailalias_set.all().count() >= OptionalUser.get_cached_value('max_mail_alias'):
|
||||
return False, "Vous avez atteint la limite de {} alias".format(OptionalUser.get_cached_value('max_mail_alias'))
|
||||
else:
|
||||
return True, None
|
||||
return True, None
|
||||
|
||||
def can_view(self, user_request, *_args, **_kwargs):
|
||||
"""
|
||||
Check if the user can view the aliases
|
||||
Check if the user can view the alias
|
||||
"""
|
||||
|
||||
if user_request.has_perm('users.view_mailalias') or user.request == self.mail.user:
|
||||
if user_request.has_perm('users.view_mailalias') or user.request == self.user:
|
||||
return True, None
|
||||
else:
|
||||
return False, "Vous n'avais pas les droits suffisants et n'êtes pas propriétaire de ces alias"
|
||||
|
@ -1662,8 +1690,8 @@ class MailAlias(RevMixin, AclMixin, models.Model):
|
|||
if user_request.has_perm('users.delete_mailalias'):
|
||||
return True, None
|
||||
else:
|
||||
if user_request == self.mail.user:
|
||||
if self.id != 0:
|
||||
if user_request == self.user:
|
||||
if self.valeur != self.user.pseudo:
|
||||
return True, None
|
||||
else:
|
||||
return False, "Vous ne pouvez pas supprimer l'alias lié à votre pseudo"
|
||||
|
@ -1678,13 +1706,16 @@ class MailAlias(RevMixin, AclMixin, models.Model):
|
|||
if user_request.has_perm('users.change_mailalias'):
|
||||
return True, None
|
||||
else:
|
||||
if user_request == self.mail.user:
|
||||
if self.id != 0:
|
||||
if user_request == self.user:
|
||||
if self.valeur != self.user.pseudo:
|
||||
return True, None
|
||||
else:
|
||||
return False, "Vous ne pouvez pas modifier l'alias lié à votre pseudo"
|
||||
else:
|
||||
return False, "Vous n'avez pas les droits suffisants et n'êtes pas propriétairs de cet alias"
|
||||
|
||||
|
||||
def clean(self, *args, **kwargs):
|
||||
if "@" in self.valeur:
|
||||
raise ValidationError("Cet alias ne peut contenir un @")
|
||||
super(MailAlias, self).clean(*args, **kwargs)
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% if alias_list.paginator %}
|
||||
{% include "pagination.html" with list=alias_list %}
|
||||
{% endif %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -37,12 +36,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td>{{ alias }}</td>
|
||||
<td class="text-right">
|
||||
{% can_delete alias %}
|
||||
{% include 'buttons/suppr.html' with href='users:del-alias' id=alias.id %}
|
||||
{% include 'buttons/suppr.html' with href='users:del-mailalias' id=alias.id %}
|
||||
{% acl_end %}
|
||||
{% can_edit alias %}
|
||||
{% include 'buttons/edit.html' with href='users:edit-alias' id=alias.id %}
|
||||
{% include 'buttons/edit.html' with href='users:edit-mailalias' id=alias.id %}
|
||||
{% acl_end %}
|
||||
{% include 'buttons/history.html' with href='users:history' name='alias' id=alias.id %}
|
||||
{% include 'buttons/history.html' with href='users:history' name='mailalias' id=alias.id %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
|
@ -27,21 +27,99 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% load acl %}
|
||||
{% block title %}Profil{% endblock %}
|
||||
{% block content %}
|
||||
<h2>{{ users.surname }} {{users.name}}</h2>
|
||||
<p>Vous êtes {% if users.end_adhesion != None %}<span class="label label-success">
|
||||
un {{ users.class_name | lower}}</span>{% else %}<span class="label label-danger">
|
||||
non adhérent</span>{% endif %} et votre connexion est {% if users.has_access %}
|
||||
<span class="label label-success">active</span>{% else %}<span class="label label-danger">désactivée</span>{% endif %}.</p>
|
||||
{% if user_solde %}
|
||||
<p>Votre solde est de <span class="badge">{{ user.solde }}€</span>.
|
||||
{% if allow_online_payment %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:recharge' %}">
|
||||
<div align="center">
|
||||
<h2>Bienvenue {{users.name}} {{ users.surname }}</h2>
|
||||
</div>
|
||||
<div class="dashboard_container">
|
||||
<div class="row">
|
||||
{% if solde_activated %}
|
||||
<div class="col-sm-6 col-md-4">
|
||||
{% else %}
|
||||
<div class="col-sm-6 col-md-6">
|
||||
{% endif %}
|
||||
<div class="col-12">
|
||||
{% if users.is_ban%}
|
||||
<div class="panel panel-danger">
|
||||
<div class="panel-heading dashboard">Votre compte est banni</div>
|
||||
<div class="panel-body dashboard">
|
||||
<i class="text-danger">Fin du ban : {{user.end_ban|date:"d M Y"}}</i>
|
||||
</div>
|
||||
</div>
|
||||
{% elif not users.is_connected%}
|
||||
<div class="panel panel-danger">
|
||||
<div class="panel-heading dashboard">Pas d'accès à internet</div>
|
||||
<div class="panel-body dashboard">
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'cotisations:credit-solde' users.id %}">
|
||||
<i class="fas fa-sign-in-alt"></i>
|
||||
Payer ma connexion
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="panel panel-success">
|
||||
<div class="panel-heading dashboard">Connecté</div>
|
||||
<div class="panel-body dashboard">
|
||||
<i class="text-success">Fin de connexion: {{user.end_adhesion|date:"d M Y"}}</i>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if solde_activated %}
|
||||
<div class="col-sm-6 col-md-4">
|
||||
<div class="col-12">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading dashboard" data-parent="#accordion" data-toggle="collapse" data-target="#collapse4">
|
||||
{{user.solde}} <i class="fas fa-euro-sign"></i>
|
||||
</div>
|
||||
<div class="panel-body dashboard">
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:credit-solde' users.id %}">
|
||||
<i class="fa fa-euro-sign"></i>
|
||||
Recharger
|
||||
</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
Modifier le solde
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if solde_activated %}
|
||||
<div class="col-sm-6 col-md-4">
|
||||
{% else %}
|
||||
<div class="col-sm-6 col-md-6">
|
||||
{% endif %}
|
||||
<div class="col-12">
|
||||
{% if nb_machines %}
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading dashboard" data-parent="#accordion" data-toggle="collapse" data-target="#collapse3">
|
||||
<span class="badge">{{nb_machines}}</span>
|
||||
Machines
|
||||
<i class="fa fa-desktop"></i>
|
||||
</div>
|
||||
<div class="panel-body dashboard">
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:new-machine' users.id %}">
|
||||
<i class="fa fa-desktop"></i>
|
||||
Ajouter une machine
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="panel panel-warning">
|
||||
<div class="panel-heading dashboard">Aucune machine</div>
|
||||
<div class="panel-body dashboard">
|
||||
<a class="btn btn-warning btn-sm" role="button" href="{% url 'machines:new-machine' users.id %}">
|
||||
<i class="fa fa-desktop"></i>
|
||||
Ajouter une machine
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="panel-group" id="accordion">
|
||||
<div class="panel panel-default">
|
||||
|
@ -50,7 +128,7 @@ non adhérent</span>{% endif %} et votre connexion est {% if users.has_access %}
|
|||
<i class="fa fa-user"></i> Informations détaillées
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-collapse collapse in" id="collapse1">
|
||||
<div class="panel-collapse collapse" id="collapse1">
|
||||
<div class="panel-body">
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'users:edit-info' users.id %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
|
@ -349,6 +427,65 @@ non adhérent</span>{% endif %} et votre connexion est {% if users.has_access %}
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading clearfix" data-parent="#accordion" data-toggle="collapse" data-target="#collapse7">
|
||||
<h3 class="panel-title pull-left">
|
||||
<i class="fa fa-envelope"></i>
|
||||
Paramètres mail
|
||||
</h3>
|
||||
</div>
|
||||
<div id="collapse7" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
{% can_edit users %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'users:edit-mail' users.id %}">
|
||||
<i class="fa fa-plus-square"></i>
|
||||
Modifier les options mail
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% if mail_accounts %}
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Adresse mail externe</th>
|
||||
<th>Compte mail {{ asso_name }}</th>
|
||||
<th>Adresse mail de contact</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ users.external_mail }}</td>
|
||||
<td>{{ users.internal_address|yesno:"Activé,Désactivé" }}</td>
|
||||
<td>{{ users.get_mail }}</td>
|
||||
</table>
|
||||
<div class="alert alert-info" role="alert">
|
||||
Vous pouvez bénéficier d'une adresse mail {{ asso_name }}.
|
||||
Vous pouvez également la rediriger vers une adresse externe en modifiant les options mail.
|
||||
</div>
|
||||
</div>
|
||||
{% if users.internal_address %}
|
||||
{% can_create MailAlias users.id %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'users:add-mailalias' users.id %}">
|
||||
<i class="fa fa-plus-square"></i>
|
||||
Ajouter un alias mail
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% if alias_list %}
|
||||
{% include "users/aff_mailalias.html" with alias_list=alias_list %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Adresse mail</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ users.external_mail }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
|
|
|
@ -68,6 +68,7 @@ urlpatterns = [
|
|||
url(r'^add_mailalias/(?P<userid>[0-9]+)$', views.add_mailalias, name='add-mailalias'),
|
||||
url(r'^edit_mailalias/(?P<mailaliasid>[0-9]+)$', views.edit_mailalias, name='edit-mailalias'),
|
||||
url(r'^del-mailalias/(?P<mailaliasid>[0-9]+)$', views.del_mailalias, name='del-mailalias'),
|
||||
url(r'^edit_mail/(?P<userid>[0-9]+)$', views.edit_mail, name='edit-mail'),
|
||||
url(r'^add_school/$', views.add_school, name='add-school'),
|
||||
url(r'^edit_school/(?P<schoolid>[0-9]+)$',
|
||||
views.edit_school,
|
||||
|
|
|
@ -80,10 +80,13 @@ from .models import (
|
|||
Adherent,
|
||||
Club,
|
||||
ListShell,
|
||||
MailAlias,
|
||||
)
|
||||
from .forms import (
|
||||
BanForm,
|
||||
WhitelistForm,
|
||||
MailAliasForm,
|
||||
MailForm,
|
||||
DelSchoolForm,
|
||||
DelListRightForm,
|
||||
NewListRightForm,
|
||||
|
@ -111,8 +114,8 @@ def new_user(request):
|
|||
GTU_sum_up = GeneralOption.get_cached_value('GTU_sum_up')
|
||||
GTU = GeneralOption.get_cached_value('GTU')
|
||||
if user.is_valid():
|
||||
user = user.save(commit=False)
|
||||
user.save()
|
||||
#user = user.save(commit=False)
|
||||
user = user.save()
|
||||
user.reset_passwd_mail(request)
|
||||
messages.success(request, "L'utilisateur %s a été crée, un mail\
|
||||
pour l'initialisation du mot de passe a été envoyé" % user.pseudo)
|
||||
|
@ -500,19 +503,18 @@ def del_whitelist(request, whitelist, **_kwargs):
|
|||
@can_edit(User)
|
||||
def add_mailalias(request, user, userid):
|
||||
""" Créer un alias """
|
||||
mailalias_instance = MailAlias(mail=user.mail)
|
||||
whitelist = WhitelistForm(
|
||||
mailalias_instance = MailAlias(user=user)
|
||||
mailalias = MailAliasForm(
|
||||
request.POST or None,
|
||||
instance=whitelist_instance
|
||||
instance=mailalias_instance
|
||||
)
|
||||
if whitelist.is_valid():
|
||||
whitelist.save()
|
||||
if mailalias.is_valid():
|
||||
mailalias.save()
|
||||
messages.success(request, "Alias créé")
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(userid)}
|
||||
))
|
||||
|
||||
return form(
|
||||
{'userform': mailalias, 'action_name': 'Ajouter un alias mail'},
|
||||
'users/user.html',
|
||||
|
@ -527,11 +529,14 @@ def edit_mailalias(request, mailalias_instance, **_kwargs):
|
|||
request.POST or None,
|
||||
instance=mailalias_instance
|
||||
)
|
||||
if whitelist.is_valid():
|
||||
if whitelist.changed_data:
|
||||
whitelist.save()
|
||||
if mailalias.is_valid():
|
||||
if mailalias.changed_data:
|
||||
mailalias.save()
|
||||
messages.success(request, "Alias modifiée")
|
||||
return redirect(reverse('users:index'))
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(mailalias_instance.user.id)}
|
||||
))
|
||||
return form(
|
||||
{'userform': mailalias, 'action_name': 'Editer un alias mail'},
|
||||
'users/user.html',
|
||||
|
@ -547,7 +552,7 @@ def del_mailalias(request, mailalias, **_kwargs):
|
|||
messages.success(request, "L'alias a été supprimé")
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(mailalias.mail.user.id)}
|
||||
kwargs={'userid': str(mailalias.user.id)}
|
||||
))
|
||||
return form(
|
||||
{'objet': mailalias, 'objet_name': 'mailalias'},
|
||||
|
@ -555,6 +560,29 @@ def del_mailalias(request, mailalias, **_kwargs):
|
|||
request
|
||||
)
|
||||
|
||||
@login_required
|
||||
@can_edit(User)
|
||||
def edit_mail(request, user_instance, **_kwargs):
|
||||
""" Editer un compte mail"""
|
||||
mail = MailForm(
|
||||
request.POST or None,
|
||||
instance=user_instance,
|
||||
user=request.user
|
||||
)
|
||||
if mail.is_valid():
|
||||
if mail.changed_data:
|
||||
mail.save()
|
||||
messages.success(request, "Option mail modifiée")
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(user_instance.id)}
|
||||
))
|
||||
return form(
|
||||
{'userform': mail, 'action_name': 'Editer les options mail'},
|
||||
'users/user.html',
|
||||
request
|
||||
)
|
||||
|
||||
@login_required
|
||||
@can_create(School)
|
||||
def add_school(request):
|
||||
|
@ -920,7 +948,7 @@ def profil(request, users, **_kwargs):
|
|||
)
|
||||
nb_machines = machines.count()
|
||||
machines = re2o_paginator(request, machines, pagination_large_number)
|
||||
factures = Facture.objects.filter(user=users)
|
||||
factures = Facture.objects.filter(user=users).select_related('paiement').select_related('user')
|
||||
factures = SortTable.sort(
|
||||
factures,
|
||||
request.GET.get('col'),
|
||||
|
@ -955,6 +983,10 @@ def profil(request, users, **_kwargs):
|
|||
'white_list': whitelists,
|
||||
'user_solde': user_solde,
|
||||
'allow_online_payment': allow_online_payment,
|
||||
'solde_activated': OptionalUser.objects.first().user_solde,
|
||||
'asso_name': AssoOption.objects.first().name,
|
||||
'alias_list': users.mailalias_set.all(),
|
||||
'mail_accounts': OptionalUser.objects.first().mail_accounts
|
||||
}
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in a new issue