From d7e75e59b62ec048fa38aa54b15b781750dd52b8 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Wed, 25 Oct 2017 23:37:12 +0200 Subject: [PATCH 1/3] =?UTF-8?q?Les=20champs=20room=20sont=20port=C3=A9s=20?= =?UTF-8?q?par=20adherent=20et=20club=20:=20permet=20plusieurs=20clubs=20d?= =?UTF-8?q?ans=20un=20local?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- re2o/utils.py | 12 ++- search/views.py | 2 +- users/admin.py | 3 +- users/forms.py | 106 +++++++------------- users/migrations/0058_auto_20171025_0154.py | 61 +++++++++++ users/migrations/0059_auto_20171025_1854.py | 25 +++++ users/models.py | 37 ++++--- users/views.py | 33 ++++-- 8 files changed, 183 insertions(+), 96 deletions(-) create mode 100644 users/migrations/0058_auto_20171025_0154.py create mode 100644 users/migrations/0059_auto_20171025_1854.py diff --git a/re2o/utils.py b/re2o/utils.py index 7ec29e87..739dd200 100644 --- a/re2o/utils.py +++ b/re2o/utils.py @@ -42,7 +42,7 @@ from django.db.models import Q from cotisations.models import Cotisation, Facture, Paiement, Vente from machines.models import Domain, Interface, Machine -from users.models import User, Ban, Whitelist +from users.models import Adherent, User, Ban, Whitelist from preferences.models import Service DT_NOW = timezone.now() @@ -238,3 +238,13 @@ class SortTable: return request.reverse() else: return request + + +def remove_user_room(room): + """ Déménage de force l'ancien locataire de la chambre """ + try: + user = Adherent.objects.get(room=room) + except Adherent.DoesNotExist: + return + user.room = None + user.save() diff --git a/search/views.py b/search/views.py index 510837e3..16c365d8 100644 --- a/search/views.py +++ b/search/views.py @@ -81,7 +81,7 @@ def search_result(search, type, request): for i in aff: if i == '0': - query_user_list = Q(room__name__icontains = search) | Q(pseudo__icontains = search) | Q(adherent__name__icontains = search) | Q(surname__icontains = search) & query1 + query_user_list = Q(adherent__room__name__icontains = search) | Q(club__room__name__icontains = search) | Q(pseudo__icontains = search) | Q(adherent__name__icontains = search) | Q(surname__icontains = search) & query1 if request.user.has_perms(('cableur',)): recherche['users_list'] = User.objects.filter(query_user_list).order_by('state', 'surname').distinct() else : diff --git a/users/admin.py b/users/admin.py index e8472acd..488bf7bc 100644 --- a/users/admin.py +++ b/users/admin.py @@ -44,13 +44,12 @@ class UserAdmin(admin.ModelAdmin): list_display = ( 'surname', 'pseudo', - 'room', 'email', 'school', 'shell', 'state' ) - search_fields = ('surname', 'pseudo', 'room') + search_fields = ('surname', 'pseudo') class LdapUserAdmin(admin.ModelAdmin): diff --git a/users/forms.py b/users/forms.py index f07e3d28..8aa2ffe7 100644 --- a/users/forms.py +++ b/users/forms.py @@ -41,7 +41,8 @@ from django.utils import timezone from preferences.models import OptionalUser from .models import User, ServiceUser, Right, School, ListRight, Whitelist -from .models import Ban, remove_user_room, Adherent, Club +from .models import Ban, Adherent, Club +from re2o.utils import remove_user_room NOW = timezone.now() @@ -252,13 +253,13 @@ class MassArchiveForm(forms.Form): utilisateurs dont la fin d'accès se situe dans le futur !") -class NewUserForm(ModelForm): +class AdherentForm(ModelForm): """Formulaire de base d'edition d'un user. Formulaire de base, utilisé pour l'edition de self par self ou un cableur. On formate les champs avec des label plus jolis""" def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) - super(NewUserForm, self).__init__(*args, prefix=prefix, **kwargs) + super(AdherentForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields['name'].label = 'Prénom' self.fields['surname'].label = 'Nom' self.fields['school'].label = 'Établissement' @@ -291,18 +292,31 @@ class NewUserForm(ModelForm): ) return telephone + force = forms.BooleanField( + label="Forcer le déménagement ?", + initial=False, + required=False + ) -class NewClubForm(ModelForm): + def clean_force(self): + """On supprime l'ancien user de la chambre si et seulement si la + case est cochée""" + if self.cleaned_data.get('force', False): + remove_user_room(self.cleaned_data.get('room')) + return + + +class ClubForm(ModelForm): """Formulaire de base d'edition d'un user. Formulaire de base, utilisé pour l'edition de self par self ou un cableur. On formate les champs avec des label plus jolis""" def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) - super(NewClubForm, self).__init__(*args, prefix=prefix, **kwargs) + super(ClubForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields['surname'].label = 'Nom' self.fields['school'].label = 'Établissement' self.fields['comment'].label = 'Commentaire' - self.fields['room'].label = 'Chambre' + self.fields['room'].label = 'Local' self.fields['room'].empty_label = "Pas de chambre" self.fields['school'].empty_label = "Séléctionner un établissement" @@ -330,51 +344,13 @@ class NewClubForm(ModelForm): return telephone - -class BaseInfoForm(ModelForm): - """Formulaire de base d'edition d'un user. Formulaire de base, utilisé - pour l'edition de self par self ou un cableur. On formate les champs - avec des label plus jolis""" - def __init__(self, *args, **kwargs): - prefix = kwargs.pop('prefix', self.Meta.model.__name__) - super(BaseInfoForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['surname'].label = 'Nom' - self.fields['school'].label = 'Établissement' - self.fields['comment'].label = 'Commentaire' - 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 = User - fields = [ - 'surname', - 'pseudo', - 'email', - 'school', - 'comment', - 'room', - 'telephone', - ] - - def clean_telephone(self): - """Verifie que le tel est présent si 'option est validée - dans preferences""" - telephone = self.cleaned_data['telephone'] - preferences, _created = OptionalUser.objects.get_or_create() - if not telephone and preferences.is_tel_mandatory: - raise forms.ValidationError( - "Un numéro de téléphone valide est requis" - ) - return telephone - - -class EditInfoForm(BaseInfoForm): +class FullAdherentForm(AdherentForm): """Edition complète d'un user. Utilisé par admin, permet d'editer normalement la chambre, ou le shell Herite de la base""" - class Meta(BaseInfoForm.Meta): + class Meta(AdherentForm.Meta): fields = [ + 'name', 'surname', 'pseudo', 'email', @@ -386,27 +362,21 @@ class EditInfoForm(BaseInfoForm): ] -class InfoForm(EditInfoForm): - """ Utile pour forcer un déménagement quand il y a déjà un user en place - Formuaire utilisé pour la creation initiale""" - force = forms.BooleanField( - label="Forcer le déménagement ?", - initial=False, - required=False - ) - - def clean_force(self): - """On supprime l'ancien user de la chambre si et seulement si la - case est cochée""" - if self.cleaned_data.get('force', False): - remove_user_room(self.cleaned_data.get('room')) - return - - -class UserForm(InfoForm): - """ Model form general""" - class Meta(InfoForm.Meta): - fields = '__all__' +class FullClubForm(ClubForm): + """Edition complète d'un user. Utilisé par admin, + permet d'editer normalement la chambre, ou le shell + Herite de la base""" + class Meta(ClubForm.Meta): + fields = [ + 'surname', + 'pseudo', + 'email', + 'school', + 'comment', + 'room', + 'shell', + 'telephone', + ] class PasswordForm(ModelForm): diff --git a/users/migrations/0058_auto_20171025_0154.py b/users/migrations/0058_auto_20171025_0154.py new file mode 100644 index 00000000..01e64fbc --- /dev/null +++ b/users/migrations/0058_auto_20171025_0154.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-10-24 23:54 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + +def create_move_room(apps, schema_editor): + User = apps.get_model('users', 'User') + Adherent = apps.get_model('users', 'Adherent') + Club = apps.get_model('users', 'Club') + db_alias = schema_editor.connection.alias + users = Adherent.objects.using(db_alias).all() + clubs = Club.objects.using(db_alias).all() + for user in users: + user.room_adherent_id = user.room_id + user.save(using=db_alias) + for user in clubs: + user.room_club_id = user.room_id + user.save(using=db_alias) + + +def delete_move_room(apps, schema_editor): + User = apps.get_model('users', 'User') + Adherent = apps.get_model('users', 'Adherent') + Club = apps.get_model('users', 'Club') + db_alias = schema_editor.connection.alias + users = Adherent.objects.using(db_alias).all() + clubs = Club.objects.using(db_alias).all() + for user in users: + user.room_id = user.room_adherent_id + user.save(using=db_alias) + for user in clubs: + user.room_id = user.room_club_id + user.save(using=db_alias) + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0031_auto_20171015_2033'), + ('users', '0057_auto_20171023_0301'), + ] + + operations = [ + migrations.AddField( + model_name='adherent', + name='room_adherent', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='topologie.Room'), + ), + migrations.AddField( + model_name='club', + name='room_club', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='topologie.Room'), + ), + migrations.RunPython(create_move_room, delete_move_room), + migrations.RemoveField( + model_name='user', + name='room', + ), + ] diff --git a/users/migrations/0059_auto_20171025_1854.py b/users/migrations/0059_auto_20171025_1854.py new file mode 100644 index 00000000..0ab2f9c4 --- /dev/null +++ b/users/migrations/0059_auto_20171025_1854.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-10-25 16:54 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0058_auto_20171025_0154'), + ] + + operations = [ + migrations.RenameField( + model_name='adherent', + old_name='room_adherent', + new_name='room', + ), + migrations.RenameField( + model_name='club', + old_name='room_club', + new_name='room', + ), + ] diff --git a/users/models.py b/users/models.py index 9574002e..31795574 100644 --- a/users/models.py +++ b/users/models.py @@ -81,15 +81,6 @@ DT_NOW = timezone.now() # Utilitaires généraux -def remove_user_room(room): - """ Déménage de force l'ancien locataire de la chambre """ - try: - user = User.objects.get(room=room) - except User.DoesNotExist: - return - user.room = None - user.save() - def linux_user_check(login): """ Validation du pseudo pour respecter les contraintes unix""" @@ -230,12 +221,6 @@ class User(AbstractBaseUser): max_length=255, blank=True ) - room = models.OneToOneField( - 'topologie.Room', - on_delete=models.PROTECT, - blank=True, - null=True - ) pwd_ntlm = models.CharField(max_length=255) state = models.IntegerField(choices=STATES, default=STATE_ACTIVE) registered = models.DateTimeField(auto_now_add=True) @@ -256,6 +241,16 @@ class User(AbstractBaseUser): else: return '' + @cached_property + def room(self): + """Alias vers room """ + if self.is_class_adherent: + return self.adherent.room + elif self.is_class_club: + return self.club.room + else: + raise NotImplementedError("Type inconnu") + @cached_property def class_name(self): """Renvoie si il s'agit d'un adhérent ou d'un club""" @@ -712,10 +707,22 @@ class User(AbstractBaseUser): class Adherent(User): name = models.CharField(max_length=255) + room = models.OneToOneField( + 'topologie.Room', + on_delete=models.PROTECT, + blank=True, + null=True + ) pass class Club(User): + room = models.ForeignKey( + 'topologie.Room', + on_delete=models.PROTECT, + blank=True, + null=True + ) pass diff --git a/users/views.py b/users/views.py index 57ae64df..ad6ebbba 100644 --- a/users/views.py +++ b/users/views.py @@ -55,10 +55,10 @@ from users.serializers import MailSerializer from users.models import User, Right, Ban, Whitelist, School, ListRight from users.models import Request, ServiceUser, Adherent, Club from users.forms import DelRightForm, BanForm, WhitelistForm, DelSchoolForm -from users.forms import DelListRightForm, NewListRightForm -from users.forms import InfoForm, BaseInfoForm, StateForm +from users.forms import DelListRightForm, NewListRightForm, FullAdherentForm +from users.forms import StateForm, FullClubForm from users.forms import RightForm, SchoolForm, EditServiceUserForm -from users.forms import ServiceUserForm, ListRightForm, NewUserForm, NewClubForm +from users.forms import ServiceUserForm, ListRightForm, AdherentForm, ClubForm from users.forms import MassArchiveForm, PassForm, ResetPasswordForm from cotisations.models import Facture from machines.models import Machine @@ -85,7 +85,7 @@ def password_change_action(u_form, user, request, req=False): def new_user(request): """ Vue de création d'un nouvel utilisateur, envoie un mail pour le mot de passe""" - user = NewUserForm(request.POST or None) + user = AdherentForm(request.POST or None) if user.is_valid(): user = user.save(commit=False) with transaction.atomic(), reversion.create_revision(): @@ -104,7 +104,7 @@ def new_user(request): def new_club(request): """ Vue de création d'un nouveau club, envoie un mail pour le mot de passe""" - club = NewClubForm(request.POST or None) + club = ClubForm(request.POST or None) if club.is_valid(): club = club.save(commit=False) with transaction.atomic(), reversion.create_revision(): @@ -118,6 +118,24 @@ def new_club(request): return form({'userform': club}, 'users/user.html', request) +def select_user_edit_form(request, user): + """Fonction de choix du bon formulaire, en fonction de: + - droit + - type d'object + """ + if not request.user.has_perms(('cableur',)): + if user.is_class_adherent: + user = AdherentForm(request.POST or None, instance=user.adherent) + elif user.is_class_club: + user = ClubForm(request.POST or None, instance=user.club) + else: + if user.is_class_adherent: + user = FullAdherentForm(request.POST or None, instance=user.adherent) + elif user.is_class_club: + user = FullClubForm(request.POST or None, instance=user.club) + return user + + @login_required def edit_info(request, userid): """ Edite un utilisateur à partir de son id, @@ -132,10 +150,7 @@ def edit_info(request, userid): messages.error(request, "Vous ne pouvez pas modifier un autre\ user que vous sans droit cableur") return redirect("/users/profil/" + str(request.user.id)) - if not request.user.has_perms(('cableur',)): - user = BaseInfoForm(request.POST or None, instance=user) - else: - user = InfoForm(request.POST or None, instance=user) + user = select_user_edit_form(request, user) if user.is_valid(): with transaction.atomic(), reversion.create_revision(): user.save() From 95d55628610b2175030c5bd91b153dfe507de159 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Thu, 26 Oct 2017 00:55:53 +0200 Subject: [PATCH 2/3] Filter general sur clubs et user + pas d'autocapture si plus d'un user dans le local/chambre --- freeradius_utils/auth.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/freeradius_utils/auth.py b/freeradius_utils/auth.py index 2d1ebfe2..8f2a6f8a 100644 --- a/freeradius_utils/auth.py +++ b/freeradius_utils/auth.py @@ -292,11 +292,12 @@ def decide_vlan_and_register_switch(nas, nas_type, port_number, mac_address): if not port.room: return (sw_name, u'Chambre inconnue', VLAN_NOK) - room_user = User.objects.filter(room=port.room) + room_user = User.objects.filter(Q(club__room=port.room) | Q(adherent__room=port.room)) if not room_user: return (sw_name, u'Chambre non cotisante', VLAN_NOK) - elif not room_user.first().has_access(): - return (sw_name, u'Chambre resident desactive', VLAN_NOK) + for user in room_user: + if not user.has_access(): + return (sw_name, u'Chambre resident desactive', VLAN_NOK) # else: user OK, on passe à la verif MAC if port.radius == 'COMMON' or port.radius == 'STRICT': @@ -309,9 +310,12 @@ def decide_vlan_and_register_switch(nas, nas_type, port_number, mac_address): elif not port.room: return (sw_name, u'Chambre et machine inconnues', VLAN_NOK) else: - room_user = User.objects.filter(room=Room.objects.filter(name=port.room)) + if not room_user: + room_user = User.objects.filter(Q(club__room=port.room) | Q(adherent__room=port.room)) if not room_user: return (sw_name, u'Machine et propriétaire de la chambre inconnus', VLAN_NOK) + elif room_user.count() > 1: + return (sw_name, u'Machine inconnue, il y a au moins 2 users dans la chambre/local -> ajout de mac automatique impossible', VLAN_NOK) elif not room_user.first().has_access(): return (sw_name, u'Machine inconnue et adhérent non cotisant', VLAN_NOK) else: From 0efe0c27d2bd586257ed9865f8abbae9b8fe4225 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Thu, 26 Oct 2017 01:59:05 +0200 Subject: [PATCH 3/3] Notification pour l'autocapture mac --- users/models.py | 24 ++++++++++++++++++ users/templates/users/email_auto_newmachine | 27 +++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 users/templates/users/email_auto_newmachine diff --git a/users/models.py b/users/models.py index 31795574..2a403129 100644 --- a/users/models.py +++ b/users/models.py @@ -669,10 +669,34 @@ class User(AbstractBaseUser): domain.interface_parent = interface_cible domain.clean() domain.save() + self.notif_auto_newmachine(interface_cible) except Exception as error: return False, error return True, "Ok" + def notif_auto_newmachine(self, interface): + """Notification mail lorsque une machine est automatiquement + ajoutée par le radius""" + template = loader.get_template('users/email_auto_newmachine') + assooptions, _created = AssoOption.objects.get_or_create() + general_options, _created = GeneralOption.objects.get_or_create() + context = Context({ + 'nom': self.get_full_name(), + 'mac_address' : interface.mac_address, + 'asso_name': assooptions.name, + 'interface_name' : interface.domain, + 'asso_email': assooptions.contact, + 'pseudo': self.pseudo, + }) + send_mail( + "Ajout automatique d'une machine / New machine autoregistered", + '', + general_options.email_from, + [self.email], + html_message=template.render(context) + ) + return + def set_user_password(self, password): """ A utiliser de préférence, set le password en hash courrant et dans la version ntlm""" diff --git a/users/templates/users/email_auto_newmachine b/users/templates/users/email_auto_newmachine new file mode 100644 index 00000000..d98cc160 --- /dev/null +++ b/users/templates/users/email_auto_newmachine @@ -0,0 +1,27 @@ +

Bonjour {{nom}}

+ +

Une nouvelle machine a automatiquement été inscrite sur votre compte.

+ +

Si vous êtes à l'origine de la connexion de cet appareil en filaire ou wifi, ne tenez pas compte de cette notification

+ +

La nouvelle machine possède l'adresse mac {{ mac_address }}, et s'est vu assigner le nom suivant : {{ interface_name }}

+ +

Vous pouvez à tout moment modifier ces informations sur votre compte en ligne

+ +

Si vous n'êtes pas à l'origine de cette connexion, merci de le signaler rapidement à {{asso_email}}

+ +

À bientôt,
+L'équipe de {{asso_name}}.

+ +

---

+ +

A new device has been automatically added on your account.

+ +

If you connected a new device recently, please don't take this mail into account

+ +

The new device has this mac address : {{ mac_address }}, and this name : {{ interface_name }}

+ +

Please contact us if you didn't connect a new device with this mail address {{asso_email}}.

+ +

Regards,
+The {{asso_name}} team.