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 %}
+
+
+
+
+ Stack
+ ID
+ Details
+
+
+ {% for stack in stack_list %}
+
+ {{stack.name}}
+ {{stack.stack_id}}
+ {{stack.details}}
+
+
+
+
+ {% if is_infra %}
+
+
+
+
+
+
+ {% endif %}
+
+
+ {% endfor %}
+
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 %}
+
+
+
+
+ Stack
+ ID
+ Details
+
+
+ {% for stack in stack_list %}
+
+ {{stack.name}}
+ {{stack.stack_id}}
+ {{stack.details}}
+
+
+
+
+ {% if is_infra %}
+
+
+
+
+
+
+ {% endif %}
+
+
+ {% endfor %}
+
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)