Commit 5453b845 authored by chirac's avatar chirac

Merge branch 'fix_stack' into 'master'

Fix stack

Closes #104 and #108

See merge request federez/re2o!140
parents 79f4f805 56242474
...@@ -27,9 +27,16 @@ from __future__ import unicode_literals ...@@ -27,9 +27,16 @@ from __future__ import unicode_literals
from django.forms import ModelForm, Form from django.forms import ModelForm, Form
from django import forms from django import forms
from .models import OptionalUser, OptionalMachine, OptionalTopologie from .models import (
from .models import GeneralOption, AssoOption, MailMessageOption, Service OptionalUser,
OptionalMachine,
OptionalTopologie,
GeneralOption,
AssoOption,
MailMessageOption,
AccueilOption,
Service
)
class EditOptionalUserForm(ModelForm): class EditOptionalUserForm(ModelForm):
"""Formulaire d'édition des options de l'user. (solde, telephone..)""" """Formulaire d'édition des options de l'user. (solde, telephone..)"""
...@@ -185,6 +192,21 @@ class EditMailMessageOptionForm(ModelForm): ...@@ -185,6 +192,21 @@ class EditMailMessageOptionForm(ModelForm):
mail de bienvenue en anglais' mail de bienvenue en anglais'
class EditAccueilOptionForm(ModelForm):
"""Formulaire d'édition des options de la page d'accueil"""
class Meta:
model = AccueilOption
fields = '__all__'
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(EditAccueilOptionForm, self).__init__(
*args,
prefix=prefix,
**kwargs
)
class ServiceForm(ModelForm): class ServiceForm(ModelForm):
"""Edition, ajout de services sur la page d'accueil""" """Edition, ajout de services sur la page d'accueil"""
class Meta: class Meta:
......
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-04-16 02:35
from __future__ import unicode_literals
from django.db import migrations, models
import re2o.mixins
class Migration(migrations.Migration):
dependencies = [
('preferences', '0032_optionaluser_shell_default'),
]
operations = [
migrations.CreateModel(
name='AccueilOption',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('facebook_url', models.URLField(blank=True, help_text='Url du compte facebook', null=True)),
('twitter_url', models.URLField(blank=True, help_text='Url du compte twitter', null=True)),
('twitter_account_name', models.CharField(blank=True, help_text='Nom du compte à afficher', max_length=32, null=True)),
],
options={
'permissions': (('view_accueiloption', "Peut voir les options de l'accueil"),),
},
bases=(re2o.mixins.AclMixin, models.Model),
),
]
...@@ -331,6 +331,40 @@ def assooption_post_save(**kwargs): ...@@ -331,6 +331,40 @@ def assooption_post_save(**kwargs):
asso_pref.set_in_cache() asso_pref.set_in_cache()
class AccueilOption(AclMixin, PreferencesModel):
"""Reglages de la page d'accueil"""
PRETTY_NAME = "Options de la page d'accueil"
facebook_url = models.URLField(
null=True,
blank=True,
help_text="Url du compte facebook"
)
twitter_url = models.URLField(
null=True,
blank=True,
help_text="Url du compte twitter"
)
twitter_account_name = models.CharField(
max_length=32,
null=True,
blank=True,
help_text="Nom du compte à afficher"
)
class Meta:
permissions = (
("view_accueiloption", "Peut voir les options de l'accueil"),
)
@receiver(post_save, sender=AccueilOption)
def accueiloption_post_save(**kwargs):
"""Ecriture dans le cache"""
accueil_pref = kwargs['instance']
accueil_pref.set_in_cache()
class MailMessageOption(AclMixin, models.Model): class MailMessageOption(AclMixin, models.Model):
"""Reglages, mail de bienvenue et autre""" """Reglages, mail de bienvenue et autre"""
PRETTY_NAME = "Options de corps de mail" PRETTY_NAME = "Options de corps de mail"
......
...@@ -211,12 +211,31 @@ with this program; if not, write to the Free Software Foundation, Inc., ...@@ -211,12 +211,31 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ mailmessageoptions.welcome_mail_en | safe }}</td> <td>{{ mailmessageoptions.welcome_mail_en | safe }}</td>
</tr> </tr>
</table> </table>
<h2>Liste des services page d'accueil</h2> <h2>Liste des services et préférences page d'accueil</h2>
{% can_create preferences.Service%} {% 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> <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 %} {% 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-services' %}"><i class="fa fa-trash"></i> Supprimer un ou plusieurs service</a>
{% include "preferences/aff_service.html" with service_list=service_list %} {% include "preferences/aff_service.html" with service_list=service_list %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'AccueilOption' %}">
<i class="fa fa-edit"></i>
Editer
</a>
<p>
<table class="table table-striped">
<tr>
<th>Url du compte twitter</th>
<td>{{ accueiloptions.twitter_url }}</td>
<th>Nom utilisé pour afficher le compte</th>
<td>{{ accueiloptions.twitter_account_name }}</td>
</tr>
<tr>
<th>Url du compte facebook</th>
<td>{{ accueiloptions.facebook_url }}</td>
</tr>
</table>
<br /> <br />
<br /> <br />
<br /> <br />
......
...@@ -57,6 +57,11 @@ urlpatterns = [ ...@@ -57,6 +57,11 @@ urlpatterns = [
views.edit_options, views.edit_options,
name='edit-options' name='edit-options'
), ),
url(
r'^edit_options/(?P<section>AccueilOption)$',
views.edit_options,
name='edit-options'
),
url( url(
r'^edit_options/(?P<section>MailMessageOption)$', r'^edit_options/(?P<section>MailMessageOption)$',
views.edit_options, views.edit_options,
......
...@@ -43,8 +43,16 @@ from re2o.views import form ...@@ -43,8 +43,16 @@ from re2o.views import form
from re2o.acl import can_create, can_edit, can_delete_set, can_view_all from re2o.acl import can_create, can_edit, can_delete_set, can_view_all
from .forms import ServiceForm, DelServiceForm from .forms import ServiceForm, DelServiceForm
from .models import Service, OptionalUser, OptionalMachine, AssoOption from .models import (
from .models import MailMessageOption, GeneralOption, OptionalTopologie Service,
OptionalUser,
OptionalMachine,
AssoOption,
MailMessageOption,
GeneralOption,
OptionalTopologie,
AccueilOption
)
from . import models from . import models
from . import forms from . import forms
...@@ -56,6 +64,7 @@ from . import forms ...@@ -56,6 +64,7 @@ from . import forms
@can_view_all(GeneralOption) @can_view_all(GeneralOption)
@can_view_all(AssoOption) @can_view_all(AssoOption)
@can_view_all(MailMessageOption) @can_view_all(MailMessageOption)
@can_view_all(AccueilOption)
def display_options(request): def display_options(request):
"""Vue pour affichage des options (en vrac) classé selon les models """Vue pour affichage des options (en vrac) classé selon les models
correspondants dans un tableau""" correspondants dans un tableau"""
...@@ -64,6 +73,7 @@ def display_options(request): ...@@ -64,6 +73,7 @@ def display_options(request):
topologieoptions, _created = OptionalTopologie.objects.get_or_create() topologieoptions, _created = OptionalTopologie.objects.get_or_create()
generaloptions, _created = GeneralOption.objects.get_or_create() generaloptions, _created = GeneralOption.objects.get_or_create()
assooptions, _created = AssoOption.objects.get_or_create() assooptions, _created = AssoOption.objects.get_or_create()
accueiloptions, _created = AccueilOption.objects.get_or_create()
mailmessageoptions, _created = MailMessageOption.objects.get_or_create() mailmessageoptions, _created = MailMessageOption.objects.get_or_create()
service_list = Service.objects.all() service_list = Service.objects.all()
return form({ return form({
...@@ -72,6 +82,7 @@ def display_options(request): ...@@ -72,6 +82,7 @@ def display_options(request):
'topologieoptions': topologieoptions, 'topologieoptions': topologieoptions,
'generaloptions': generaloptions, 'generaloptions': generaloptions,
'assooptions': assooptions, 'assooptions': assooptions,
'accueiloptions': accueiloptions,
'mailmessageoptions': mailmessageoptions, 'mailmessageoptions': mailmessageoptions,
'service_list': service_list 'service_list': service_list
}, 'preferences/display_preferences.html', request) }, 'preferences/display_preferences.html', request)
......
...@@ -25,4 +25,30 @@ with this program; if not, write to the Free Software Foundation, Inc., ...@@ -25,4 +25,30 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% block sidebar %} {% block sidebar %}
{% if facebook_url %}
<div class="fb-page" data-href="{{ facebook_url }}" data-tabs="timeline" data-small-header="false" data-adapt-container-width="true" data-hide-cover="false" data-show-facepile="false"><blockquote cite="{{ facebook_url }}" class="fb-xfbml-parse-ignore"><a href="{{ facebook_url }}">{{ asso_name }}</a></blockquote></div>
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = 'https://connect.facebook.net/fr_FR/sdk.js#xfbml=1&version=v2.12';
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<hr>
{% endif %}
{% if twitter_url %}
<a class="twitter-timeline" data-lang="fr" data-height="500" href="{{ twitter_url }}?ref_src=twsrc%5Etfw">Tweets de @{{ twitter_account_name }}</a>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<a href="{{ twitter_url }}?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false"> Suivre @{{ twitter_account_name }}</a>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
{% endif %}
{% endblock %} {% endblock %}
...@@ -41,7 +41,12 @@ from django.utils.translation import ugettext as _ ...@@ -41,7 +41,12 @@ from django.utils.translation import ugettext as _
from django.views.decorators.cache import cache_page from django.views.decorators.cache import cache_page
import preferences import preferences
from preferences.models import Service, GeneralOption, AssoOption from preferences.models import (
Service,
GeneralOption,
AssoOption,
AccueilOption
)
import users import users
import cotisations import cotisations
import topologie import topologie
...@@ -63,7 +68,17 @@ def index(request): ...@@ -63,7 +68,17 @@ def index(request):
services = [[], [], []] services = [[], [], []]
for indice, serv in enumerate(Service.objects.all()): for indice, serv in enumerate(Service.objects.all()):
services[indice % 3].append(serv) services[indice % 3].append(serv)
return form({'services_urls': services}, 're2o/index.html', request) twitter_url = AccueilOption.get_cached_value('twitter_url')
facebook_url = AccueilOption.get_cached_value('facebook_url')
twitter_account_name = AccueilOption.get_cached_value('twitter_account_name')
asso_name = AssoOption.get_cached_value('pseudo')
return form({
'services_urls': services,
'twitter_url': twitter_url,
'twitter_account_name' : twitter_account_name,
'facebook_url': facebook_url,
'asso_name': asso_name
}, 're2o/index.html', request)
#: Binding the corresponding char sequence of history url to re2o models. #: Binding the corresponding char sequence of history url to re2o models.
......
This diff is collapsed.
...@@ -31,58 +31,31 @@ with this program; if not, write to the Free Software Foundation, Inc., ...@@ -31,58 +31,31 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<th>{% include "buttons/sort.html" with prefix='stack' col='id' text='ID' %}</th> <th>{% include "buttons/sort.html" with prefix='stack' col='id' text='ID' %}</th>
<th>Détails</th> <th>Détails</th>
<th>Membres</th> <th>Membres</th>
<th></th>
</tr> </tr>
</thead> </thead>
{% for stack in stack_list %} {% for stack in stack_list %}
{% for switch in stack.switch_set.all %} <tr>
<tbody> <td>{{ stack.name }}</td>
<tr class="active"> <td>{{stack.stack_id}}</td>
{% if forloop.first %} <td>{{stack.details}}</td>
<td rowspan="{{ stack.switch_set.all|length }}">{{stack.name}}</td> <td>{% for switch in stack.switch_set.all %}<a href="{% url 'topologie:index-port' switch.pk %}">{{switch }} </a>{% endfor %}</td>
<td rowspan="{{ stack.switch_set.all|length }}">{{stack.stack_id}}</td> <td class="text-right">
<td rowspan="{{ stack.switch_set.all|length }}" >{{stack.details}}</td> <a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'stack' stack.pk %}">
{% endif %} <i class="fa fa-history"></i>
<td><a href="{% url 'topologie:index-port' switch.pk %}">{{switch}}</a></td> </a>
{% if forloop.first %} {% can_edit stack %}
<td rowspan="{{ stack.switch_set.all|length }}"> <a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-stack' stack.id %}">
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'stack' stack.pk %}"> <i class="fa fa-edit"></i>
<i class="fa fa-history"></i> </a>
</a> {% acl_end %}
{% can_edit stack %} {% can_delete stack %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-stack' stack.id %}"> <a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-stack' stack.pk %}">
<i class="fa fa-edit"></i> <i class="fa fa-trash"></i>
</a> </a>
{% acl_end %} {% acl_end %}
{% can_delete stack %} </td>
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-stack' stack.pk %}"> </tr>
<i class="fa fa-trash"></i> {% endfor %}
</a>
{% acl_end %}
</td>
{% endif %}
</tr>
{% empty %}
<tr class="active">
<td>{{stack.name}}</td>
<td>{{stack.stack_id}}</td>
<td>{{stack.details}}</td>
<td>Aucun</td>
<td>
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'stack' stack.pk %}">
<i class="fa fa-history"></i>
</a>
{% can_edit stack %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-stack' stack.id %}">
<i class="fa fa-edit"></i>
</a>
{% acl_end %}
{% can_delete stack %}
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-stack' stack.pk %}">
<i class="fa fa-trash"></i>
</a>
{% acl_end %}
</td>
{% endfor %}
</tbody>
{% endfor %}
</table> </table>
...@@ -24,11 +24,12 @@ with this program; if not, write to the Free Software Foundation, Inc., ...@@ -24,11 +24,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load acl %} {% load acl %}
<div class="table-responsive">
{% if switch_list.paginator %} {% if switch_list.paginator %}
{% include "pagination.html" with list=switch_list %} {% include "pagination.html" with list=switch_list %}
{% endif %} {% endif %}
<div class="table-responsive">
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>
...@@ -72,8 +73,9 @@ with this program; if not, write to the Free Software Foundation, Inc., ...@@ -72,8 +73,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
</div>
{% if switch_list.paginator %} {% if switch_list.paginator %}
{% include "pagination.html" with list=switch_list %} {% include "pagination.html" with list=switch_list %}
{% endif %} {% endif %}
</div>
...@@ -43,7 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc., ...@@ -43,7 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{switch_bay.name}}</td> <td>{{switch_bay.name}}</td>
<td>{{switch_bay.building}}</td> <td>{{switch_bay.building}}</td>
<td>{{switch_bay.info}}</td> <td>{{switch_bay.info}}</td>
<td>{{switch_bay.switch_set.all |join:", "}}</td> <td>{% for switch in switch_bay.switch_set.all %}<a href="{% url 'topologie:index-port' switch.pk %}">{{switch }} </a>{% endfor %}</td>
<td class="text-right"> <td class="text-right">
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'switchbay' switch_bay.pk %}"> <a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'switchbay' switch_bay.pk %}">
<i class="fa fa-history"></i> <i class="fa fa-history"></i>
......
...@@ -29,7 +29,19 @@ with this program; if not, write to the Free Software Foundation, Inc., ...@@ -29,7 +29,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% block title %}Switchs{% endblock %} {% block title %}Switchs{% endblock %}
{% block content %} {% block content %}
<h2>Switchs</h2> <img id="zoom_01" src="/media/images/switchs.png" data-zoom-image="/media/images/switchs.png" width=100% />
<script type="text/javascript" src="/static/js/jquery.ez-plus.js"></script>
<script>
$("#zoom_01").ezPlus({
scrollZoom: true,
zoomType: 'inner',
cursor: 'crosshair'
});
</script>
<h2>Switchs</h2>
{% can_create Switch %} {% can_create Switch %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:new-switch' %}"><i class="fa fa-plus"></i> Ajouter un switch</a> <a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:new-switch' %}"><i class="fa fa-plus"></i> Ajouter un switch</a>
<hr> <hr>
...@@ -38,4 +50,6 @@ with this program; if not, write to the Free Software Foundation, Inc., ...@@ -38,4 +50,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<br /> <br />
<br /> <br />
<br /> <br />
{% endblock %} {% endblock %}
...@@ -42,6 +42,7 @@ from django.contrib.auth.decorators import login_required ...@@ -42,6 +42,7 @@ from django.contrib.auth.decorators import login_required
from django.db import IntegrityError from django.db import IntegrityError
from django.db.models import ProtectedError, Prefetch from django.db.models import ProtectedError, Prefetch
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.contrib.staticfiles.storage import staticfiles_storage
from users.views import form from users.views import form
from re2o.utils import re2o_paginator, SortTable from re2o.utils import re2o_paginator, SortTable
...@@ -88,6 +89,8 @@ from .forms import ( ...@@ -88,6 +89,8 @@ from .forms import (
EditBuildingForm EditBuildingForm
) )
from subprocess import Popen,PIPE
@login_required @login_required
@can_view_all(Switch) @can_view_all(Switch)
...@@ -349,6 +352,7 @@ def new_stack(request): ...@@ -349,6 +352,7 @@ def new_stack(request):
if stack.is_valid(): if stack.is_valid():
stack.save() stack.save()
messages.success(request, "Stack crée") messages.success(request, "Stack crée")
return redirect(reverse('topologie:index-physical-grouping'))
return form( return form(
{'topoform': stack, 'action_name': 'Créer'}, {'topoform': stack, 'action_name': 'Créer'},
'topologie/topo.html', 'topologie/topo.html',
...@@ -364,7 +368,7 @@ def edit_stack(request, stack, **_kwargs): ...@@ -364,7 +368,7 @@ def edit_stack(request, stack, **_kwargs):
if stack.is_valid(): if stack.is_valid():
if stack.changed_data: if stack.changed_data:
stack.save() stack.save()
return redirect(reverse('topologie:index-physical-grouping')) return redirect(reverse('topologie:index-physical-grouping'))
return form( return form(
{'topoform': stack, 'action_name': 'Editer'}, {'topoform': stack, 'action_name': 'Editer'},
'topologie/topo.html', 'topologie/topo.html',
...@@ -926,8 +930,103 @@ def del_constructor_switch(request, constructor_switch, **_kwargs): ...@@ -926,8 +930,103 @@ def del_constructor_switch(request, constructor_switch, **_kwargs):
"de la supprimer (switch ou user)" % constructor_switch) "de la supprimer (switch ou user)" % constructor_switch)
) )
return redirect(reverse('topologie:index-model-switch')) return redirect(reverse('topologie:index-model-switch'))
return form( return form({
{'objet': constructor_switch, 'objet_name': 'Constructeur de switch'}, 'objet': constructor_switch,
'topologie/delete.html', 'objet_name': 'Constructeur de switch'
request }, 'topologie/delete.html', request)
)
def make_machine_graph():
"""
Crée le fichier dot et l'image du graph des Switchs
"""
#Syntaxe DOT temporaire, A mettre dans un template:
lignes=['''digraph Switchs {
node [
fontname=Helvetica
fontsize=8
shape=plaintext]
edge[arrowhead=odot,arrowtail=dot]''']
node_fixe='''node [label=<
<TABLE BGCOLOR="palegoldenrod" BORDER="0" CELLBORDER="0" CELLSPACING="0">
<TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER" BGCOLOR="olivedrab4">
<FONT FACE="Helvetica Bold" COLOR="white">
{}
</FONT></TD></TR>
<TR><TD ALIGN="LEFT" BORDER="0">
<FONT COLOR="#7B7B7B" >{}</FONT>
</TD>
<TD ALIGN="LEFT">
<FONT COLOR="#7B7B7B" >{}</FONT>
</TD></TR>
<TR><TD ALIGN="LEFT" BORDER="0">
<FONT COLOR="#7B7B7B" >{}</FONT>
</TD>
<TD ALIGN="LEFT">
<FONT>{}</FONT>
</TD></TR>'''
node_ports='''<TR><TD ALIGN="LEFT" BORDER="0">
<FONT COLOR="#7B7B7B" >{}</FONT>
</TD>
<TD ALIGN="LEFT">
<FONT>{}</FONT>
</TD></TR>'''
cluster='''subgraph cluster_{} {{
color=blue;
label="Batiment {}";'''
end_table='''</TABLE>
>] \"{}_{}\" ;'''
switch_alone='''{} [label=<
<TABLE BGCOLOR="palegoldenrod" BORDER="0" CELLBORDER="0" CELLSPACING="0">
<TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER" BGCOLOR="olivedrab4">
<FONT FACE="Helvetica Bold" COLOR="white">
{}
</FONT></TD></TR>
</TABLE>
>]'''
missing=[]
detected=[]
for sw in Switch.objects.all():
if(sw not in detected):
missing.append(sw)
for building in Building.objects.all():
lignes.append(cluster.format(len(lignes),building))
for switch in Switch.objects.filter(switchbay__building=building):
lignes.append(node_fixe.format(switch.main_interface().domain.name,"Modèle",switch.model,"Nombre de ports",switch.number))
for p in switch.ports.all().filter(related__isnull=False):
lignes.append(node_ports.format(p.port,p.related.switch.main_interface().domain.name))
lignes.append(end_table.format(building.id,switch.id))
lignes.append("}")
while(missing!=[]):
lignes,new_detected=recursive_switchs(missing[0].ports.all().filter(related=None).first(),None,lignes,[missing[0]])
missing=[i for i in missing if i not in new_detected]
detected+=new_detected
for switch in Switch.objects.all().filter(switchbay__isnull=True).exclude(ports__related__isnull=False):
lignes.append(switch_alone.format(switch.id,switch.main_interface().domain.name))
lignes.append("}")
fichier = open("media/images/switchs.dot","w")
for ligne in lig