diff --git a/topologie/admin.py b/topologie/admin.py index 6af830ab..f998f3bd 100644 --- a/topologie/admin.py +++ b/topologie/admin.py @@ -24,7 +24,10 @@ from django.contrib import admin from reversion.admin import VersionAdmin -from .models import Port, Room, Switch +from .models import Port, Room, Switch, Stack + +class StackAdmin(VersionAdmin): + list_display = ('name', 'stack_id', 'details') class SwitchAdmin(VersionAdmin): list_display = ('switch_interface','location','number','details') @@ -38,3 +41,4 @@ class RoomAdmin(VersionAdmin): admin.site.register(Port, PortAdmin) 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 6d62ad7f..c2227e5d 100644 --- a/topologie/forms.py +++ b/topologie/forms.py @@ -20,7 +20,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from .models import Port, Switch, Room +from .models import Port, Switch, Room, Stack from django.forms import ModelForm, Form from machines.models import Interface @@ -42,6 +42,11 @@ class AddPortForm(ModelForm): class Meta(PortForm.Meta): fields = ['port', 'room', 'machine_interface', 'related', 'radius', 'details'] +class StackForm(ModelForm): + class Meta: + model = Stack + fields = '__all__' + class EditSwitchForm(ModelForm): class Meta: model = Switch @@ -54,9 +59,10 @@ class EditSwitchForm(ModelForm): class NewSwitchForm(ModelForm): class Meta(EditSwitchForm.Meta): - fields = ['location', 'number', 'details'] + fields = ['location', 'number', 'details', 'stack', 'stack_member_id'] class EditRoomForm(ModelForm): class Meta: model = Room fields = '__all__' + diff --git a/topologie/migrations/0023_auto_20170817_1654.py b/topologie/migrations/0023_auto_20170817_1654.py new file mode 100644 index 00000000..0f84d7de --- /dev/null +++ b/topologie/migrations/0023_auto_20170817_1654.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-08-17 14:54 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0022_auto_20161211_1622'), + ] + + operations = [ + migrations.CreateModel( + name='Stack', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(blank=True, max_length=32, null=True)), + ('stack_id', models.CharField(max_length=32, unique=True)), + ('details', models.CharField(blank=True, max_length=255, null=True)), + ('member_id_min', models.IntegerField()), + ('member_id_max', models.IntegerField()), + ], + ), + migrations.AddField( + model_name='switch', + name='stack_member_id', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='switch', + name='stack', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='topologie.Stack'), + ), + migrations.AlterUniqueTogether( + name='switch', + unique_together=set([('stack', 'stack_member_id')]), + ), + ] diff --git a/topologie/migrations/0024_auto_20170818_1021.py b/topologie/migrations/0024_auto_20170818_1021.py new file mode 100644 index 00000000..0d11be65 --- /dev/null +++ b/topologie/migrations/0024_auto_20170818_1021.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-08-18 08:21 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0023_auto_20170817_1654'), + ] + + operations = [ + migrations.AlterField( + model_name='switch', + name='stack', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='topologie.Stack'), + ), + ] diff --git a/topologie/migrations/0025_merge_20170902_1242.py b/topologie/migrations/0025_merge_20170902_1242.py new file mode 100644 index 00000000..23632040 --- /dev/null +++ b/topologie/migrations/0025_merge_20170902_1242.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-09-02 10:42 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0024_auto_20170818_1021'), + ('topologie', '0024_auto_20170826_1800'), + ] + + operations = [ + ] diff --git a/topologie/migrations/0026_auto_20170902_1245.py b/topologie/migrations/0026_auto_20170902_1245.py new file mode 100644 index 00000000..e6bd4d96 --- /dev/null +++ b/topologie/migrations/0026_auto_20170902_1245.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-09-02 10:45 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0025_merge_20170902_1242'), + ] + + operations = [ + migrations.AlterField( + model_name='port', + name='radius', + field=models.CharField(choices=[('NO', 'NO'), ('STRICT', 'STRICT'), ('BLOQ', 'BLOQ'), ('COMMON', 'COMMON'), ('3', '3'), ('7', '7'), ('8', '8'), ('13', '13'), ('20', '20'), ('42', '42'), ('69', '69')], default='NO', max_length=32), + ), + ] diff --git a/topologie/models.py b/topologie/models.py index f924f6cd..c28f1d4f 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -21,6 +21,8 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from django.db import models +from django.db.models.signals import post_delete +from django.dispatch import receiver from django.forms import ModelForm, Form from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey @@ -39,6 +41,29 @@ def clean_port_related(port): related_port.related = None related_port.save() +class Stack(models.Model): + PRETTY_NAME = "Stack de switchs" + + name = models.CharField(max_length=32, blank=True, null=True) + stack_id = models.CharField(max_length=32, unique=True) + details = models.CharField(max_length=255, blank=True, null=True) + member_id_min = models.IntegerField() + member_id_max = models.IntegerField() + + def __str__(self): + return " ".join([self.name, self.stack_id]) + + def save(self, *args, **kwargs): + if not self.name: + self.name = self.stack_id + super(Stack, self).save(*args, **kwargs) + + def clean(self): + if self.member_id_max < self.member_id_min: + import traceback + traceback.print_exc() + raise ValidationError({'member_id_max':"L'id maximale est inférieure à l'id minimale"}) + class Switch(models.Model): PRETTY_NAME = "Switch / Commutateur" @@ -46,10 +71,23 @@ class Switch(models.Model): location = models.CharField(max_length=255) number = models.IntegerField() details = models.CharField(max_length=255, blank=True) + stack = models.ForeignKey(Stack, blank=True, null=True, on_delete=models.SET_NULL) + stack_member_id = models.IntegerField(blank=True, null=True) + + class Meta: + unique_together = ('stack','stack_member_id') def __str__(self): return str(self.location) + ' ' + str(self.switch_interface) + def clean(self): + if self.stack is not None: + if self.stack_member_id is not None: + if (self.stack_member_id > self.stack.member_id_max) or (self.stack_member_id < self.stack.member_id_min): + raise ValidationError({'stack_member_id': "L'id de ce switch est en dehors des bornes permises pas la stack"}) + else: + raise ValidationError({'stack_member_id': "L'id dans la stack ne peut être nul"}) + class Port(models.Model): PRETTY_NAME = "Port de switch" STATES_BASE = ( @@ -102,3 +140,6 @@ class Room(models.Model): def __str__(self): return str(self.name) +@receiver(post_delete, sender=Stack) +def stack_post_delete(sender, **kwargs): + Switch.objects.filter(stack=None).update(stack_member_id = None) diff --git a/topologie/templates/topologie/aff_stacks.html b/topologie/templates/topologie/aff_stacks.html new file mode 100644 index 00000000..e4ca286d --- /dev/null +++ b/topologie/templates/topologie/aff_stacks.html @@ -0,0 +1,53 @@ +{% 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 %} + + + + + + + + + + {% for stack in stack_list %} + + + + + + + {% endfor %} +
StackIDDetails
{{stack.name}}{{stack.stack_id}}{{stack.details}} + + + + {% if is_infra %} + + + + + + + {% endif %} +
diff --git a/topologie/templates/topologie/aff_switch.html b/topologie/templates/topologie/aff_switch.html index 8b950bf9..7096909b 100644 --- a/topologie/templates/topologie/aff_switch.html +++ b/topologie/templates/topologie/aff_switch.html @@ -29,6 +29,8 @@ with this program; if not, write to the Free Software Foundation, Inc., Ipv4 Localisation Ports + Stack + id interne Stack Détails @@ -43,6 +45,8 @@ with this program; if not, write to the Free Software Foundation, Inc., {{switch.switch_interface.ipv4}} {{switch.location}} {{switch.number}} + {{switch.stack.name}} + {{switch.stack_member_id}} {{switch.details}} diff --git a/topologie/templates/topologie/edit_stack_sw.html b/topologie/templates/topologie/edit_stack_sw.html new file mode 100644 index 00000000..e4ca286d --- /dev/null +++ b/topologie/templates/topologie/edit_stack_sw.html @@ -0,0 +1,53 @@ +{% 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 %} + + + + + + + + + + {% for stack in stack_list %} + + + + + + + {% endfor %} +
StackIDDetails
{{stack.name}}{{stack.stack_id}}{{stack.details}} + + + + {% if is_infra %} + + + + + + + {% endif %} +
diff --git a/topologie/templates/topologie/index_stack.html b/topologie/templates/topologie/index_stack.html new file mode 100644 index 00000000..b358e07d --- /dev/null +++ b/topologie/templates/topologie/index_stack.html @@ -0,0 +1,39 @@ +{% 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 %} + +{% block title %}Stacks{% endblock %} + +{% block content %} +

Stacks

+{% if is_infra %} +
Ajouter une stack +{% endif %} + {% include "topologie/aff_stacks.html" with stack_list=stack_list %} +
+
+
+{% endblock %} diff --git a/topologie/templates/topologie/sidebar.html b/topologie/templates/topologie/sidebar.html index fd26b60f..833af2e3 100644 --- a/topologie/templates/topologie/sidebar.html +++ b/topologie/templates/topologie/sidebar.html @@ -29,8 +29,12 @@ with this program; if not, write to the Free Software Foundation, Inc., Chambres - + Switchs + + + Stacks + {% endblock %} diff --git a/topologie/urls.py b/topologie/urls.py index 241dc3a9..776c4259 100644 --- a/topologie/urls.py +++ b/topologie/urls.py @@ -35,8 +35,13 @@ urlpatterns = [ url(r'^history/(?Pswitch)/(?P[0-9]+)$', views.history, name='history'), url(r'^history/(?Pport)/(?P[0-9]+)$', views.history, name='history'), url(r'^history/(?Proom)/(?P[0-9]+)$', views.history, name='history'), + url(r'^history/(?Pstack)/(?P[0-9]+)$', views.history, name='history'), url(r'^edit_port/(?P[0-9]+)$', views.edit_port, name='edit-port'), url(r'^new_port/(?P[0-9]+)$', views.new_port, name='new-port'), url(r'^edit_switch/(?P[0-9]+)$', views.edit_switch, name='edit-switch'), + url(r'^new_stack/$', views.new_stack, name='new-stack'), + url(r'^index_stack/$', views.index_stack, name='index-stack'), + url(r'^edit_stack/(?P[0-9]+)$', views.edit_stack, name='edit-stack'), + url(r'^del_stack/(?P[0-9]+)$', views.del_stack, name='del-stack'), ] diff --git a/topologie/views.py b/topologie/views.py index 721e1d3e..d9bd849f 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -25,12 +25,13 @@ from django.contrib import messages from django.contrib.auth.decorators import login_required, permission_required from django.db import IntegrityError from django.db import transaction +from django.db.models import ProtectedError from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from reversion import revisions as reversion from reversion.models import Version -from topologie.models import Switch, Port, Room -from topologie.forms import EditPortForm, NewSwitchForm, EditSwitchForm, AddPortForm, EditRoomForm +from topologie.models import Switch, Port, Room, Stack +from topologie.forms import EditPortForm, NewSwitchForm, EditSwitchForm, AddPortForm, EditRoomForm, StackForm from users.views import form from users.models import User @@ -41,7 +42,7 @@ from preferences.models import AssoOption, GeneralOption @login_required @permission_required('cableur') def index(request): - switch_list = Switch.objects.order_by('location').select_related('switch_interface__domain__extension').select_related('switch_interface__ipv4').select_related('switch_interface__domain') + switch_list = Switch.objects.order_by('stack','stack_member_id','location').select_related('switch_interface__domain__extension').select_related('switch_interface__ipv4').select_related('switch_interface__domain') return render(request, 'topologie/index.html', {'switch_list': switch_list}) @login_required @@ -65,6 +66,12 @@ def history(request, object, id): except Room.DoesNotExist: messages.error(request, "Chambre inexistante") return redirect("/topologie/") + elif object == 'stack': + try: + object_instance = Stack.objects.get(pk=id) + except Room.DoesNotExist: + messages.error(request, "Stack inexistante") + return redirect("/topologie/") else: messages.error(request, "Objet inconnu") return redirect("/topologie/") @@ -100,6 +107,13 @@ def index_room(request): room_list = Room.objects.order_by('name') return render(request, 'topologie/index_room.html', {'room_list': room_list}) +@login_required +@permission_required('infra') +def index_stack(request): + stack_list = Stack.objects.order_by('name') + return render(request, 'topologie/index_stack.html', {'stack_list': stack_list}) + + @login_required @permission_required('infra') def new_port(request, switch_id): @@ -141,6 +155,77 @@ def edit_port(request, port_id): return redirect("/topologie/switch/" + str(port_object.switch.id)) return form({'topoform':port}, 'topologie/topo.html', request) +@login_required +@permission_required('infra') +def new_stack(request): + stack = StackForm(request.POST or None) + #if stack.is_valid(): + if request.POST: + try: + with transaction.atomic(), reversion.create_revision(): + stack.save() + reversion.set_user(request.user) + reversion.set_comment("Création") + messages.success(request, "Stack crée") + except: + messages.error(request, "Cette stack existe déjà") + return form({'topoform':stack}, 'topologie/topo.html', request) + + +@login_required +@permission_required('infra') +def edit_stack(request,stack_id): + try: + stack = Stack.objects.get(pk=stack_id) + except Stack.DoesNotExist: + messages.error(request, u"Stack inexistante") + return redirect('/topologie/index_stack/') + stack = StackForm(request.POST or None, instance=stack) + if stack.is_valid(): + with transaction.atomic(), reversion.create_revision(): + stack.save() + reversion.set_user(request.user) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in stack.changed_data)) + return redirect('/topologie/index_stack') + return form({'topoform':stack}, 'topologie/topo.html', request) + +@login_required +@permission_required('infra') +def del_stack(request,stack_id): + try: + stack = Stack.objects.get(pk=stack_id) + except Stack.DoesNotExist: + messages.error(request, u"Stack inexistante") + return redirect('/topologie/index_stack') + if request.method == "POST": + try: + with transaction.atomic(), reversion.create_revision(): + stack.delete() + reversion.set_user(request.user) + reversion.set_comment("Destruction") + messages.success(request, "La stack a eté détruite") + except ProtectedError: + messages.error(request, "La stack %s est affectée à un autre objet, impossible de la supprimer" % stack) + return redirect('/topologie/index_stack') + return form({'objet':stack}, 'topologie/delete.html', request) + +@login_required +@permission_required('infra') +def edit_switchs_stack(request,stack_id): + try: + stack = Stack.objects.get(pk=stack_id) + except Stack.DoesNotExist: + messages.error(request, u"Stack inexistante") + return redirect('/topologie/index_stack') + if request.method == "POST": + pass + else: + context = {'stack': stack} + context['switchs_stack'] = stack.switchs_set.all() + context['switchs_autres'] = Switch.object.filter(stack=None) + pass + + @login_required @permission_required('infra') def new_switch(request): @@ -259,10 +344,13 @@ def del_room(request, room_id): messages.error(request, u"Chambre inexistante" ) return redirect("/topologie/index_room/") if request.method == "POST": - with transaction.atomic(), reversion.create_revision(): - room.delete() - reversion.set_user(request.user) - reversion.set_comment("Destruction") - messages.success(request, "La chambre/prise a été détruite") + try: + with transaction.atomic(), reversion.create_revision(): + room.delete() + reversion.set_user(request.user) + reversion.set_comment("Destruction") + messages.success(request, "La chambre/prise a été détruite") + except ProtectedError: + messages.error(request, "La chambre %s est affectée à un autre objet, impossible de la supprimer (switch ou user)" % room) return redirect("/topologie/index_room/") return form({'objet': room, 'objet_name': 'Chambre'}, 'topologie/delete.html', request)