diff --git a/re2o/utils.py b/re2o/utils.py index 5abe0c33..a3de2a89 100644 --- a/re2o/utils.py +++ b/re2o/utils.py @@ -233,6 +233,11 @@ class SortTable: 'room_name': ['name'], 'default': ['name'] } + TOPOLOGIE_INDEX_BORNE = { + 'borne_name': ['domain__name'], + 'borne_ipv4': ['borne__ipv4__ipv4'], + 'default': ['domain__name'] + } TOPOLOGIE_INDEX_STACK = { 'stack_name': ['name'], 'stack_id': ['stack_id'], diff --git a/re2o/views.py b/re2o/views.py index bfb2e6c5..2beb1636 100644 --- a/re2o/views.py +++ b/re2o/views.py @@ -82,6 +82,7 @@ HISTORY_BIND = { 'stack' : topologie.models.Stack, 'model_switch' : topologie.models.ModelSwitch, 'constructor_switch' : topologie.models.ConstructorSwitch, + 'borne' : topologie.models.Borne, }, 'machines' : { 'machine' : machines.models.Machine, diff --git a/topologie/admin.py b/topologie/admin.py index a4591222..f7021148 100644 --- a/topologie/admin.py +++ b/topologie/admin.py @@ -29,7 +29,15 @@ from __future__ import unicode_literals from django.contrib import admin from reversion.admin import VersionAdmin -from .models import Port, Room, Switch, Stack, ModelSwitch, ConstructorSwitch +from .models import ( + Port, + Room, + Switch, + Stack, + ModelSwitch, + ConstructorSwitch, + Borne +) class StackAdmin(VersionAdmin): @@ -47,6 +55,11 @@ class PortAdmin(VersionAdmin): pass +class BorneAdmin(VersionAdmin): + """Administration d'une borne""" + pass + + class RoomAdmin(VersionAdmin): """Administration d'un chambre""" pass @@ -63,6 +76,7 @@ class ConstructorSwitchAdmin(VersionAdmin): admin.site.register(Port, PortAdmin) +admin.site.register(Borne, BorneAdmin) admin.site.register(Room, RoomAdmin) admin.site.register(Switch, SwitchAdmin) admin.site.register(Stack, StackAdmin) diff --git a/topologie/forms.py b/topologie/forms.py index c4c369a6..42572fe7 100644 --- a/topologie/forms.py +++ b/topologie/forms.py @@ -33,9 +33,18 @@ NewSwitchForm) from __future__ import unicode_literals from machines.models import Interface +from machines.forms import EditInterfaceForm from django import forms from django.forms import ModelForm, Form -from .models import Port, Switch, Room, Stack, ModelSwitch, ConstructorSwitch +from .models import ( + Port, + Switch, + Room, + Stack, + ModelSwitch, + ConstructorSwitch, + Borne +) class PortForm(ModelForm): @@ -102,6 +111,21 @@ class StackForm(ModelForm): super(StackForm, self).__init__(*args, prefix=prefix, **kwargs) +class AddBorneForm(EditInterfaceForm): + """Formulaire pour la création d'une borne + Relié directement au modèle borne""" + class Meta: + model = Borne + fields = ['mac_address', 'type', 'ipv4', 'details', 'location'] + + +class EditBorneForm(EditInterfaceForm): + """Edition d'une interface. Edition complète""" + class Meta: + model = Borne + fields = ['machine', 'type', 'ipv4', 'mac_address', 'details', 'location'] + + class EditSwitchForm(ModelForm): """Permet d'éditer un switch : nom et nombre de ports""" class Meta: diff --git a/topologie/management/commands/sync_unifi_ap.py b/topologie/management/commands/sync_unifi_ap.py new file mode 100644 index 00000000..532aa5d5 --- /dev/null +++ b/topologie/management/commands/sync_unifi_ap.py @@ -0,0 +1,41 @@ +# ⁻*- 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. +# +# Copyright © 2018 Gabriel Detraz +# +# 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. + +from django.core.management.base import BaseCommand, CommandError +from pymongo import MongoClient + +class Command(BaseCommand): + help = 'Ce script donne un nom aux bornes dans le controleur unifi. + A lancer sur le serveur en local où se trouve le controleur' + + def handle(self, *args, **options): + # Connexion mongodb + client = MongoClient("mongodb://localhost:27117") + db = client.ace + device = db['device'] + + def set_bornes_names(liste_bornes): + """Met à jour les noms des bornes dans la bdd du controleur""" + for borne in liste_bornes: + device.find_one_and_update({'ip': str(borne['ipHostNumber'][0])}, {'$set': {'name': borne['host'][0].split('.')[0]}}) + return + + self.stdout.write(self.style.SUCCESS('Mise à jour de la base de donnée unifi avec succès')) diff --git a/topologie/migrations/0034_borne.py b/topologie/migrations/0034_borne.py new file mode 100644 index 00000000..6d827f3a --- /dev/null +++ b/topologie/migrations/0034_borne.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-03-23 01:18 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0076_auto_20180130_1623'), + ('topologie', '0033_auto_20171231_1743'), + ] + + operations = [ + migrations.CreateModel( + name='Borne', + fields=[ + ('interface_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='machines.Interface')), + ('location', models.CharField(help_text="Détails sur la localisation de l'AP", max_length=255)), + ], + options={ + 'permissions': (('view_borne', 'Peut voir une borne'),), + }, + bases=('machines.interface',), + ), + ] diff --git a/topologie/migrations/0035_auto_20180324_0023.py b/topologie/migrations/0035_auto_20180324_0023.py new file mode 100644 index 00000000..7fa69d01 --- /dev/null +++ b/topologie/migrations/0035_auto_20180324_0023.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-03-23 23:23 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0034_borne'), + ] + + operations = [ + migrations.AlterField( + model_name='borne', + name='location', + field=models.CharField(blank=True, help_text="Détails sur la localisation de l'AP", max_length=255, null=True), + ), + ] diff --git a/topologie/models.py b/topologie/models.py index 0871b402..5c48773f 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -47,6 +47,7 @@ from django.db import IntegrityError from django.db import transaction from reversion import revisions as reversion +from machines.models import Interface class Stack(models.Model): """Un objet stack. Regrouppe des switchs en foreign key @@ -108,6 +109,53 @@ class Stack(models.Model): inférieure à l'id minimale"}) +class Borne(Interface): + """Define a wireless AP. Inherit from machines.interfaces + + Definition pour une borne wifi , hérite de machines.interfaces + """ + PRETTY_NAME = "Borne WiFi" + + location = models.CharField( + max_length=255, + help_text="Détails sur la localisation de l'AP", + blank=True, + null=True + ) + + class Meta: + permissions = ( + ("view_borne", "Peut voir une borne"), + ) + + def get_instance(borne_id, *args, **kwargs): + return Borne.objects.get(pk=borne_id) + + def can_create(user_request, *args, **kwargs): + return user_request.has_perm('topologie.add_borne') , u"Vous n'avez pas le droit\ + de créer une borne" + + def can_edit(self, user_request, *args, **kwargs): + if not user_request.has_perm('topologie.change_borne'): + return False, u"Vous n'avez pas le droit d'éditer des bornes" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + if not user_request.has_perm('topologie.delete_borne'): + return False, u"Vous n'avez pas le droit de supprimer une borne" + return True, None + + def can_view_all(user_request, *args, **kwargs): + if not user_request.has_perm('topologie.view_borne'): + return False, u"Vous n'avez pas le droit de voir les bornes" + return True, None + + def can_view(self, user_request, *args, **kwargs): + if not user_request.has_perm('topologie.view_borne'): + return False, u"Vous n'avez pas le droit de voir les bornes" + return True, None + + class Switch(models.Model): """ Definition d'un switch. Contient un nombre de ports (number), un emplacement (location), un stack parent (optionnel, stack) @@ -192,6 +240,7 @@ class Switch(models.Model): else: raise ValidationError({'stack_member_id': "L'id dans la stack\ ne peut être nul"}) + def create_ports(self, begin, end): """ Crée les ports de begin à end si les valeurs données sont cohérentes. """ diff --git a/topologie/templates/topologie/aff_borne.html b/topologie/templates/topologie/aff_borne.html new file mode 100644 index 00000000..8080aaaf --- /dev/null +++ b/topologie/templates/topologie/aff_borne.html @@ -0,0 +1,74 @@ +{% 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 %} + +
+{% if borne_list.paginator %} +{% include "pagination.html" with list=borne_list %} +{% endif %} + + + + + + + + + + + + + + {% for borne in borne_list %} + + + + + + + + + {% endfor %} +
{% include "buttons/sort.html" with prefix='borne' col='name' text='Borne' %}{% include "buttons/sort.html" with prefix='borne' col='mac' text='Addresse mac' %}{% include "buttons/sort.html" with prefix='borne' col='ip' text='Ipv4' %}CommentaireLocalisation
{{borne}}{{borne.mac_address}}{{borne.ipv4}}{{borne.details}}{{borne.location}} + + + + {% can_edit borne %} + + + + {% acl_end %} + {% can_delete borne %} + + + + {% acl_end %} +
+ + +{% if borne_list.paginator %} +{% include "pagination.html" with list=borne_list %} +{% endif %} +
diff --git a/topologie/templates/topologie/borne.html b/topologie/templates/topologie/borne.html new file mode 100644 index 00000000..57e015d5 --- /dev/null +++ b/topologie/templates/topologie/borne.html @@ -0,0 +1,63 @@ +{% 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 © 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 massive_bootstrap_form %} + +{% block title %}Création et modification d'une borne{% endblock %} + +{% block content %} +{% if topoform %} +{% bootstrap_form_errors topoform %} +{% endif %} +{% if machineform %} +{% bootstrap_form_errors machineform %} +{% endif %} +{% if domainform %} +{% bootstrap_form_errors domainform %} +{% endif %} + + + +
+ {% csrf_token %} + {% if topoform %} +

Réglage spécifiques de la borne

+ {% massive_bootstrap_form topoform 'ipv4,machine' mbf_param=i_mbf_param%} + {% endif %} + {% if machineform %} +

Réglages généraux de la machine associée à la borne

+ {% massive_bootstrap_form machineform 'user' %} + {% endif %} + {% if domainform %} +

Nom de la machine

+ {% bootstrap_form domainform %} + {% endif %} + {% bootstrap_button "Créer ou modifier" button_type="submit" icon="ok" %} +
+
+
+
+{% endblock %} diff --git a/topologie/templates/topologie/index_borne.html b/topologie/templates/topologie/index_borne.html new file mode 100644 index 00000000..e6d20dfb --- /dev/null +++ b/topologie/templates/topologie/index_borne.html @@ -0,0 +1,41 @@ +{% 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 © 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 acl %} + +{% block title %}Bornes WiFi{% endblock %} + +{% block content %} +

Points d'accès WiFi

+{% can_create Room %} + Ajouter une borne +
+{% acl_end %} + {% include "topologie/aff_borne.html" with borne_list=borne_list %} +
+
+
+{% endblock %} diff --git a/topologie/templates/topologie/sidebar.html b/topologie/templates/topologie/sidebar.html index 826169f2..6351ffd0 100644 --- a/topologie/templates/topologie/sidebar.html +++ b/topologie/templates/topologie/sidebar.html @@ -26,12 +26,16 @@ with this program; if not, write to the Free Software Foundation, Inc., {% block sidebar %} - - Chambres + + Chambres et locaux - + Switchs + + + + Bornes WiFi diff --git a/topologie/urls.py b/topologie/urls.py index 3caf22c5..19b87947 100644 --- a/topologie/urls.py +++ b/topologie/urls.py @@ -35,7 +35,11 @@ from . import views urlpatterns = [ url(r'^$', views.index, name='index'), - url(r'^new_switch/$', views.new_switch, name='new-switch'), + url(r'^index_borne/$', views.index_borne, name='index-borne'), + url(r'^new_borne/$', views.new_borne, name='new-borne'), + url(r'^edit_borne/(?P[0-9]+)$', + views.edit_borne, + name='edit-borne'), url(r'^create_ports/(?P[0-9]+)$', views.create_ports, name='create-ports'), @@ -43,6 +47,7 @@ urlpatterns = [ url(r'^new_room/$', views.new_room, name='new-room'), url(r'^edit_room/(?P[0-9]+)$', views.edit_room, name='edit-room'), url(r'^del_room/(?P[0-9]+)$', views.del_room, name='del-room'), + url(r'^new_switch/$', views.new_switch, name='new-switch'), url(r'^switch/(?P[0-9]+)$', views.index_port, name='index-port'), diff --git a/topologie/views.py b/topologie/views.py index f8bc210b..4c50acf5 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -53,7 +53,8 @@ from topologie.models import ( Room, Stack, ModelSwitch, - ConstructorSwitch + ConstructorSwitch, + Borne ) from topologie.forms import EditPortForm, NewSwitchForm, EditSwitchForm from topologie.forms import ( @@ -62,7 +63,9 @@ from topologie.forms import ( StackForm, EditModelSwitchForm, EditConstructorSwitchForm, - CreatePortsForm + CreatePortsForm, + AddBorneForm, + EditBorneForm ) from users.views import form from re2o.utils import SortTable @@ -168,6 +171,33 @@ def index_room(request): }) +@login_required +@can_view_all(Borne) +def index_borne(request): + """ Affichage de l'ensemble des bornes""" + borne_list = Borne.objects + borne_list = SortTable.sort( + borne_list, + request.GET.get('col'), + request.GET.get('order'), + SortTable.TOPOLOGIE_INDEX_BORNE + ) + pagination_number = GeneralOption.get_cached_value('pagination_number') + paginator = Paginator(borne_list, pagination_number) + page = request.GET.get('page') + try: + borne_list = paginator.page(page) + except PageNotAnInteger: + # If page is not an integer, deliver first page. + borne_list = paginator.page(1) + except EmptyPage: + # If page is out of range (e.g. 9999), deliver last page of results. + borne_list = paginator.page(paginator.num_pages) + return render(request, 'topologie/index_borne.html', { + 'borne_list': borne_list + }) + + @login_required @can_view_all(Stack) def index_stack(request): @@ -510,6 +540,119 @@ def edit_switch(request, switch, switch_id): }, 'topologie/switch.html', request) +@login_required +@can_create(Borne) +def new_borne(request): + """ Creation d'une borne. Cree en meme temps l'interface et la machine + associée. Vue complexe. Appelle successivement les 3 models forms + adaptés : machine, interface, domain et switch""" + borne = AddBorneForm( + request.POST or None, + user=request.user + ) + machine = NewMachineForm( + request.POST or None, + user=request.user + ) + domain = DomainForm( + request.POST or None, + ) + if borne.is_valid() and machine.is_valid(): + user = AssoOption.get_cached_value('utilisateur_asso') + if not user: + messages.error(request, "L'user association n'existe pas encore,\ + veuillez le créer ou le linker dans preferences") + return redirect(reverse('topologie:index')) + new_machine = machine.save(commit=False) + new_machine.user = user + new_borne = borne.save(commit=False) + domain.instance.interface_parent = new_borne + if domain.is_valid(): + new_domain_instance = domain.save(commit=False) + with transaction.atomic(), reversion.create_revision(): + new_machine.save() + reversion.set_user(request.user) + reversion.set_comment("Création") + new_borne.machine = new_machine + with transaction.atomic(), reversion.create_revision(): + new_borne.save() + reversion.set_user(request.user) + reversion.set_comment("Création") + new_domain_instance.interface_parent = new_borne + with transaction.atomic(), reversion.create_revision(): + new_domain_instance.save() + reversion.set_user(request.user) + reversion.set_comment("Création") + messages.success(request, "La borne a été créé") + return redirect(reverse('topologie:index-borne')) + i_mbf_param = generate_ipv4_mbf_param(borne, False) + return form({ + 'topoform': borne, + 'machineform': machine, + 'domainform': domain, + 'i_mbf_param': i_mbf_param + }, 'topologie/borne.html', request) + + +@login_required +@can_edit(Borne) +def edit_borne(request, borne, borne_id): + """ Edition d'un switch. Permet de chambre nombre de ports, + place dans le stack, interface et machine associée""" + borne_form = EditBorneForm( + request.POST or None, + user=request.user, + instance=borne + ) + machine_form = NewMachineForm( + request.POST or None, + user=request.user, + instance=borne.machine + ) + domain_form = DomainForm( + request.POST or None, + instance=borne.domain + ) + if borne_form.is_valid() and machine_form.is_valid(): + user = AssoOption.get_cached_value('utilisateur_asso') + if not user: + messages.error(request, "L'user association n'existe pas encore,\ + veuillez le créer ou le linker dans preferences") + return redirect(reverse('topologie:index-borne')) + new_machine = machine_form.save(commit=False) + new_borne = borne_form.save(commit=False) + new_domain = domain_form.save(commit=False) + with transaction.atomic(), reversion.create_revision(): + new_machine.save() + reversion.set_user(request.user) + reversion.set_comment( + "Champs modifié(s) : %s" % ', '.join( + field for field in machine_form.changed_data) + ) + with transaction.atomic(), reversion.create_revision(): + new_borne.save() + reversion.set_user(request.user) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join( + field for field in borne_form.changed_data) + ) + reversion.set_comment("Création") + with transaction.atomic(), reversion.create_revision(): + new_domain.save() + reversion.set_user(request.user) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join( + field for field in domain_form.changed_data) + ) + messages.success(request, "La borne a été modifiée") + return redirect(reverse('topologie:index-borne')) + i_mbf_param = generate_ipv4_mbf_param(borne_form, False ) + return form({ + 'topoform': borne_form, + 'machineform': machine_form, + 'domainform': domain_form, + 'i_mbf_param': i_mbf_param + }, 'topologie/borne.html', request) + + @login_required @can_create(Room) def new_room(request):