views.py 37.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
# 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.
chirac's avatar
chirac committed
22 23
"""
Page des vues de l'application topologie
24

chirac's avatar
chirac committed
25 26 27 28 29 30 31 32 33 34 35
Permet de créer, modifier et supprimer :
- un port (add_port, edit_port, del_port)
- un switch : les vues d'ajout et d'édition font appel aux forms de creation
de switch, mais aussi aux forms de machines.forms (domain, interface et
machine). Le views les envoie et les save en même temps. TODO : rationaliser
et faire que la creation de machines (interfaces, domain etc) soit gérée
coté models et forms de topologie
- une chambre (new_room, edit_room, del_room)
- une stack
- l'historique de tous les objets cités
"""
36 37
from __future__ import unicode_literals

38
from django.urls import reverse
Dalahro's avatar
Dalahro committed
39 40
from django.shortcuts import render, redirect
from django.contrib import messages
41
from django.contrib.auth.decorators import login_required
Dalahro's avatar
Dalahro committed
42
from django.db import IntegrityError
43
from django.db.models import ProtectedError, Prefetch
44
from django.core.exceptions import ValidationError
45
from django.contrib.staticfiles.storage import staticfiles_storage
grisel-davy's avatar
grisel-davy committed
46 47
from django.template.loader import get_template
from django.template import Context, Template, loader
grisel-davy's avatar
grisel-davy committed
48 49
from django.db.models.signals import post_save
from django.dispatch import receiver
Fernet Laouen's avatar
Fernet Laouen committed
50
from django.utils.translation import ugettext as _
grisel-davy's avatar
grisel-davy committed
51

grisel-davy's avatar
grisel-davy committed
52
import tempfile
Dalahro's avatar
Dalahro committed
53

54 55 56 57 58 59 60 61 62
from users.views import form
from re2o.utils import re2o_paginator, SortTable
from re2o.acl import (
    can_create,
    can_edit,
    can_delete,
    can_view,
    can_view_all,
)
grisel-davy's avatar
grisel-davy committed
63
from re2o.settings import MEDIA_ROOT
64 65 66 67 68 69
from machines.forms import (
    DomainForm,
    EditInterfaceForm,
    AddInterfaceForm
)
from machines.views import generate_ipv4_mbf_param
grisel-davy's avatar
grisel-davy committed
70
from machines.models import Interface, Service_link
71 72 73
from preferences.models import AssoOption, GeneralOption

from .models import (
74 75 76 77 78
    Switch,
    Port,
    Room,
    Stack,
    ModelSwitch,
79
    ConstructorSwitch,
80 81
    AccessPoint,
    SwitchBay,
grisel-davy's avatar
grisel-davy committed
82
    Building,
grisel-davy's avatar
grisel-davy committed
83
    Server,
Fernet Laouen's avatar
Fernet Laouen committed
84
    PortProfile,
85
)
86 87 88 89
from .forms import (
    EditPortForm,
    NewSwitchForm,
    EditSwitchForm,
90 91 92
    AddPortForm,
    EditRoomForm,
    StackForm,
93
    EditModelSwitchForm,
94
    EditConstructorSwitchForm,
95
    CreatePortsForm,
96
    AddAccessPointForm,
97 98
    EditAccessPointForm,
    EditSwitchBayForm,
Fernet Laouen's avatar
Fernet Laouen committed
99 100
    EditBuildingForm,
    EditPortProfileForm,
101
)
102

grisel-davy's avatar
grisel-davy committed
103 104 105 106 107 108 109
from subprocess import (
    Popen,
    PIPE
)

from os.path import isfile 
from os import remove
110

chirac's avatar
chirac committed
111

chirac's avatar
chirac committed
112
@login_required
113
@can_view_all(Switch)
Dalahro's avatar
Dalahro committed
114
def index(request):
115
    """ Vue d'affichage de tous les swicthes"""
116 117 118 119 120 121 122 123
    switch_list = (Switch.objects
                   .prefetch_related(Prefetch(
                       'interface_set',
                       queryset=(Interface.objects
                                 .select_related('ipv4__ip_type__extension')
                                 .select_related('domain__extension'))
                   ))
                   .select_related('stack'))
124 125 126 127 128 129
    switch_list = SortTable.sort(
        switch_list,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX
    )
Fernet Laouen's avatar
Fernet Laouen committed
130 131 132

    port_profile_list = PortProfile.objects.all()

133
    pagination_number = GeneralOption.get_cached_value('pagination_number')
134
    switch_list = re2o_paginator(request, switch_list, pagination_number)
Fernet Laouen's avatar
Fernet Laouen committed
135
    port_profile_list = re2o_paginator(request, port_profile_list, pagination_number)
grisel-davy's avatar
grisel-davy committed
136

137
    if any(service_link.need_regen for service_link in Service_link.objects.filter(service__service_type='graph_topo')):
grisel-davy's avatar
grisel-davy committed
138 139 140 141
        make_machine_graph()
        for service_link in Service_link.objects.filter(service__service_type='graph_topo'):
            service_link.done_regen()

grisel-davy's avatar
grisel-davy committed
142 143
    if not isfile("/var/www/re2o/media/images/switchs.png"):
        make_machine_graph()
144 145 146
    return render(
        request,
        'topologie/index.html',
Fernet Laouen's avatar
Fernet Laouen committed
147
        {'switch_list': switch_list, 'port_profile_list': port_profile_list}
148
    )
chirac's avatar
chirac committed
149

Dalahro's avatar
Dalahro committed
150

chirac's avatar
chirac committed
151
@login_required
152 153
@can_view_all(Port)
@can_view(Switch)
154
def index_port(request, switch, switchid):
155
    """ Affichage de l'ensemble des ports reliés à un switch particulier"""
156 157 158 159 160 161 162 163 164 165 166 167
    port_list = (Port.objects
                 .filter(switch=switch)
                 .select_related('room')
                 .select_related('machine_interface__domain__extension')
                 .select_related('machine_interface__machine__user')
                 .select_related('related__switch')
                 .prefetch_related(Prefetch(
                     'related__switch__interface_set',
                     queryset=(Interface.objects
                               .select_related('domain__extension'))
                 ))
                 .select_related('switch'))
168 169 170 171 172 173
    port_list = SortTable.sort(
        port_list,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX_PORT
    )
174 175 176 177 178 179 180 181 182
    return render(
        request,
        'topologie/index_p.html',
        {
            'port_list': port_list,
            'id_switch': switchid,
            'nom_switch': switch
        }
    )
chirac's avatar
chirac committed
183

Dalahro's avatar
Dalahro committed
184

chirac's avatar
chirac committed
185
@login_required
186
@can_view_all(Room)
chirac's avatar
chirac committed
187
def index_room(request):
188
    """ Affichage de l'ensemble des chambres"""
189
    room_list = Room.objects
190 191 192 193 194 195
    room_list = SortTable.sort(
        room_list,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX_ROOM
    )
196
    pagination_number = GeneralOption.get_cached_value('pagination_number')
197
    room_list = re2o_paginator(request, room_list, pagination_number)
198 199 200 201 202
    return render(
        request,
        'topologie/index_room.html',
        {'room_list': room_list}
    )
chirac's avatar
chirac committed
203

chirac's avatar
chirac committed
204

205
@login_required
206 207
@can_view_all(AccessPoint)
def index_ap(request):
208
    """ Affichage de l'ensemble des bornes"""
209 210 211 212 213 214 215
    ap_list = (AccessPoint.objects
               .prefetch_related(Prefetch(
                   'interface_set',
                   queryset=(Interface.objects
                             .select_related('ipv4__ip_type__extension')
                             .select_related('domain__extension'))
               )))
216 217
    ap_list = SortTable.sort(
        ap_list,
218 219 220 221 222
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX_BORNE
    )
    pagination_number = GeneralOption.get_cached_value('pagination_number')
223
    ap_list = re2o_paginator(request, ap_list, pagination_number)
224 225 226 227 228
    return render(
        request,
        'topologie/index_ap.html',
        {'ap_list': ap_list}
    )
229 230


231
@login_required
232
@can_view_all(Stack, Building, SwitchBay)
233
def index_physical_grouping(request):
chirac's avatar
chirac committed
234
    """Affichage de la liste des stacks (affiche l'ensemble des switches)"""
235 236 237 238
    stack_list = (Stack.objects
                  .prefetch_related(
                      'switch_set__interface_set__domain__extension'
                  ))
239 240
    building_list = Building.objects.all()
    switch_bay_list = SwitchBay.objects.select_related('building')
241 242 243 244 245 246
    stack_list = SortTable.sort(
        stack_list,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX_STACK
    )
247 248 249 250 251 252 253 254 255 256 257 258
    building_list = SortTable.sort(
        building_list,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX_BUILDING
    )
    switch_bay_list = SortTable.sort(
        switch_bay_list,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX_SWITCH_BAY
    )
259 260 261 262 263 264 265 266 267
    return render(
        request,
        'topologie/index_physical_grouping.html',
        {
            'stack_list': stack_list,
            'switch_bay_list': switch_bay_list,
            'building_list': building_list,
        }
    )
268 269


270
@login_required
271
@can_view_all(ModelSwitch, ConstructorSwitch)
272 273
def index_model_switch(request):
    """ Affichage de l'ensemble des modèles de switches"""
274
    model_switch_list = ModelSwitch.objects.select_related('constructor')
275 276 277 278 279 280 281 282 283 284 285 286 287
    constructor_switch_list = ConstructorSwitch.objects
    model_switch_list = SortTable.sort(
        model_switch_list,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX_MODEL_SWITCH
    )
    constructor_switch_list = SortTable.sort(
        constructor_switch_list,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX_CONSTRUCTOR_SWITCH
    )
288 289 290 291 292 293 294 295
    return render(
        request,
        'topologie/index_model_switch.html',
        {
            'model_switch_list': model_switch_list,
            'constructor_switch_list': constructor_switch_list,
        }
    )
296 297


chirac's avatar
chirac committed
298
@login_required
299
@can_create(Port)
300
def new_port(request, switchid):
301
    """ Nouveau port"""
Dalahro's avatar
Dalahro committed
302
    try:
303
        switch = Switch.objects.get(pk=switchid)
Dalahro's avatar
Dalahro committed
304 305
    except Switch.DoesNotExist:
        messages.error(request, u"Switch inexistant")
306
        return redirect(reverse('topologie:index'))
Dalahro's avatar
Dalahro committed
307 308 309 310 311
    port = AddPortForm(request.POST or None)
    if port.is_valid():
        port = port.save(commit=False)
        port.switch = switch
        try:
312
            port.save()
Dalahro's avatar
Dalahro committed
313 314
            messages.success(request, "Port ajouté")
        except IntegrityError:
chirac's avatar
chirac committed
315
            messages.error(request, "Ce port existe déjà")
316
        return redirect(reverse(
317
            'topologie:index-port',
318 319 320 321 322 323
            kwargs={'switchid': switchid}
        ))
    return form(
        {'id_switch': switchid, 'topoform': port, 'action_name': 'Ajouter'},
        'topologie/topo.html',
        request)
chirac's avatar
chirac committed
324

Dalahro's avatar
Dalahro committed
325

chirac's avatar
chirac committed
326
@login_required
327
@can_edit(Port)
328
def edit_port(request, port_object, **_kwargs):
chirac's avatar
chirac committed
329 330
    """ Edition d'un port. Permet de changer le switch parent et
    l'affectation du port"""
331

332
    port = EditPortForm(request.POST or None, instance=port_object)
Dalahro's avatar
Dalahro committed
333
    if port.is_valid():
334 335 336
        if port.changed_data:
            port.save()
            messages.success(request, "Le port a bien été modifié")
337 338
        return redirect(reverse(
            'topologie:index-port',
339
            kwargs={'switchid': str(port_object.switch.id)}
340 341 342 343 344 345 346 347 348 349
        ))
    return form(
        {
            'id_switch': str(port_object.switch.id),
            'topoform': port,
            'action_name': 'Editer'
        },
        'topologie/topo.html',
        request
    )
chirac's avatar
chirac committed
350

chirac's avatar
chirac committed
351

352
@login_required
353
@can_delete(Port)
354
def del_port(request, port, **_kwargs):
355
    """ Supprime le port"""
Gabriel Detraz's avatar
Gabriel Detraz committed
356 357
    if request.method == "POST":
        try:
358 359
            port.delete()
            messages.success(request, "Le port a été détruit")
Gabriel Detraz's avatar
Gabriel Detraz committed
360
        except ProtectedError:
361 362 363 364 365
            messages.error(
                request,
                ("Le port %s est affecté à un autre objet, impossible "
                 "de le supprimer" % port)
            )
366 367
        return redirect(reverse(
            'topologie:index-port',
368 369
            kwargs={'switchid': str(port.switch.id)}
        ))
chirac's avatar
chirac committed
370 371
    return form({'objet': port}, 'topologie/delete.html', request)

Gabriel Detraz's avatar
Gabriel Detraz committed
372 373

@login_required
374
@can_create(Stack)
375
def new_stack(request):
376
    """Ajoute un nouveau stack : stackid_min, max, et nombre de switches"""
377
    stack = StackForm(request.POST or None)
chirac's avatar
chirac committed
378
    if stack.is_valid():
379
        stack.save()
chirac's avatar
chirac committed
380
        messages.success(request, "Stack crée")
Gabriel Detraz's avatar
Gabriel Detraz committed
381
        return redirect(reverse('topologie:index-physical-grouping'))
382 383 384 385 386
    return form(
        {'topoform': stack, 'action_name': 'Créer'},
        'topologie/topo.html',
        request
    )
387 388 389


@login_required
390
@can_edit(Stack)
391
def edit_stack(request, stack, **_kwargs):
chirac's avatar
chirac committed
392
    """Edition d'un stack (nombre de switches, nom...)"""
393 394
    stack = StackForm(request.POST or None, instance=stack)
    if stack.is_valid():
395 396
        if stack.changed_data:
            stack.save()
Gabriel Detraz's avatar
Gabriel Detraz committed
397
        return redirect(reverse('topologie:index-physical-grouping'))
398 399 400 401 402
    return form(
        {'topoform': stack, 'action_name': 'Editer'},
        'topologie/topo.html',
        request
    )
chirac's avatar
chirac committed
403

404

405
@login_required
406
@can_delete(Stack)
407
def del_stack(request, stack, **_kwargs):
chirac's avatar
chirac committed
408
    """Supprime un stack"""
409 410
    if request.method == "POST":
        try:
411 412
            stack.delete()
            messages.success(request, "La stack a eté détruite")
413
        except ProtectedError:
414 415 416 417 418
            messages.error(
                request,
                ("La stack %s est affectée à un autre objet, impossible "
                 "de la supprimer" % stack)
            )
419
        return redirect(reverse('topologie:index-physical-grouping'))
chirac's avatar
chirac committed
420 421
    return form({'objet': stack}, 'topologie/delete.html', request)

422

423
@login_required
424
@can_edit(Stack)
425
def edit_switchs_stack(request, stack, **_kwargs):
chirac's avatar
chirac committed
426
    """Permet d'éditer la liste des switches dans une stack et l'ajouter"""
427

428 429 430 431 432 433 434 435
    if request.method == "POST":
        pass
    else:
        context = {'stack': stack}
        context['switchs_stack'] = stack.switchs_set.all()
        context['switchs_autres'] = Switch.object.filter(stack=None)


chirac's avatar
chirac committed
436
@login_required
437
@can_create(Switch)
Dalahro's avatar
Dalahro committed
438
def new_switch(request):
chirac's avatar
chirac committed
439 440 441
    """ Creation d'un switch. Cree en meme temps l'interface et la machine
    associée. Vue complexe. Appelle successivement les 4 models forms
    adaptés : machine, interface, domain et switch"""
442
    switch = NewSwitchForm(
443 444 445
        request.POST or None,
        user=request.user
    )
446
    interface = AddInterfaceForm(
chirac's avatar
chirac committed
447
        request.POST or None,
448
        user=request.user
449
    )
450
    domain = DomainForm(
chirac's avatar
chirac committed
451 452
        request.POST or None,
        )
453
    if switch.is_valid() and interface.is_valid():
454
        user = AssoOption.get_cached_value('utilisateur_asso')
455
        if not user:
456 457 458 459 460
            messages.error(
                request,
                ("L'user association n'existe pas encore, veuillez le "
                 "créer ou le linker dans preferences")
            )
461
            return redirect(reverse('topologie:index'))
462 463 464 465
        new_switch_obj = switch.save(commit=False)
        new_switch_obj.user = user
        new_interface_obj = interface.save(commit=False)
        domain.instance.interface_parent = new_interface_obj
466
        if domain.is_valid():
467 468 469 470 471 472
            new_domain_obj = domain.save(commit=False)
            new_switch_obj.save()
            new_interface_obj.machine = new_switch_obj
            new_interface_obj.save()
            new_domain_obj.interface_parent = new_interface_obj
            new_domain_obj.save()
473 474
            messages.success(request, "Le switch a été créé")
            return redirect(reverse('topologie:index'))
475
    i_mbf_param = generate_ipv4_mbf_param(interface, False)
476 477 478 479 480 481 482 483 484 485 486
    return form(
        {
            'topoform': interface,
            'machineform': switch,
            'domainform': domain,
            'i_mbf_param': i_mbf_param,
            'device': 'switch',
        },
        'topologie/topo_more.html',
        request
    )
chirac's avatar
chirac committed
487

Dalahro's avatar
Dalahro committed
488

489
@login_required
490
@can_create(Port)
491
def create_ports(request, switchid):
492 493
    """ Création d'une liste de ports pour un switch."""
    try:
494
        switch = Switch.objects.get(pk=switchid)
495 496
    except Switch.DoesNotExist:
        messages.error(request, u"Switch inexistant")
497
        return redirect(reverse('topologie:index'))
498

499
    s_begin = s_end = 0
500 501
    nb_ports = switch.ports.count()
    if nb_ports > 0:
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
502 503 504
        ports = switch.ports.order_by('port').values('port')
        s_begin = ports.first().get('port')
        s_end = ports.last().get('port')
505

506 507
    port_form = CreatePortsForm(
        request.POST or None,
508
        initial={'begin': s_begin, 'end': s_end}
509
    )
510

511 512 513
    if port_form.is_valid():
        begin = port_form.cleaned_data['begin']
        end = port_form.cleaned_data['end']
514 515 516 517 518
        try:
            switch.create_ports(begin, end)
            messages.success(request, "Ports créés.")
        except ValidationError as e:
            messages.error(request, ''.join(e))
519 520
        return redirect(reverse(
            'topologie:index-port',
521
            kwargs={'switchid': switchid}
522
            ))
523 524 525 526 527
    return form(
        {'id_switch': switchid, 'topoform': port_form},
        'topologie/switch.html',
        request
    )
528

Dalahro's avatar
Dalahro committed
529

chirac's avatar
chirac committed
530
@login_required
531
@can_edit(Switch)
532
def edit_switch(request, switch, switchid):
chirac's avatar
chirac committed
533 534
    """ Edition d'un switch. Permet de chambre nombre de ports,
    place dans le stack, interface et machine associée"""
535

536
    switch_form = EditSwitchForm(
chirac's avatar
chirac committed
537
        request.POST or None,
538
        instance=switch,
539
        user=request.user
chirac's avatar
chirac committed
540
        )
541
    interface_form = EditInterfaceForm(
chirac's avatar
chirac committed
542
        request.POST or None,
543
        instance=switch.interface_set.first(),
544
        user=request.user
chirac's avatar
chirac committed
545
        )
546
    domain_form = DomainForm(
chirac's avatar
chirac committed
547
        request.POST or None,
548
        instance=switch.interface_set.first().domain
chirac's avatar
chirac committed
549
        )
550
    if switch_form.is_valid() and interface_form.is_valid():
551 552 553
        new_switch_obj = switch_form.save(commit=False)
        new_interface_obj = interface_form.save(commit=False)
        new_domain_obj = domain_form.save(commit=False)
554
        if switch_form.changed_data:
555
            new_switch_obj.save()
556
        if interface_form.changed_data:
557
            new_interface_obj.save()
558
        if domain_form.changed_data:
559
            new_domain_obj.save()
560
        messages.success(request, "Le switch a bien été modifié")
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
561
        return redirect(reverse('topologie:index'))
562 563 564 565 566 567 568 569 570 571 572 573 574
    i_mbf_param = generate_ipv4_mbf_param(interface_form, False)
    return form(
        {
            'id_switch': switchid,
            'topoform': interface_form,
            'machineform': switch_form,
            'domainform': domain_form,
            'i_mbf_param': i_mbf_param,
            'device': 'switch',
        },
        'topologie/topo_more.html',
        request
    )
chirac's avatar
chirac committed
575

chirac's avatar
chirac committed
576

577
@login_required
578 579 580
@can_create(AccessPoint)
def new_ap(request):
    """ Creation d'une ap. Cree en meme temps l'interface et la machine
581 582
    associée. Vue complexe. Appelle successivement les 3 models forms
    adaptés : machine, interface, domain et switch"""
583
    ap = AddAccessPointForm(
584 585 586
        request.POST or None,
        user=request.user
    )
587
    interface = AddInterfaceForm(
588 589 590 591 592 593
        request.POST or None,
        user=request.user
    )
    domain = DomainForm(
        request.POST or None,
        )
594
    if ap.is_valid() and interface.is_valid():
595 596
        user = AssoOption.get_cached_value('utilisateur_asso')
        if not user:
597 598 599 600 601
            messages.error(
                request,
                ("L'user association n'existe pas encore, veuillez le "
                 "créer ou le linker dans preferences")
            )
602
            return redirect(reverse('topologie:index'))
603 604 605 606
        new_ap_obj = ap.save(commit=False)
        new_ap_obj.user = user
        new_interface_obj = interface.save(commit=False)
        domain.instance.interface_parent = new_interface_obj
607
        if domain.is_valid():
608 609 610 611 612 613
            new_domain_obj = domain.save(commit=False)
            new_ap_obj.save()
            new_interface_obj.machine = new_ap_obj
            new_interface_obj.save()
            new_domain_obj.interface_parent = new_interface_obj
            new_domain_obj.save()
614
            messages.success(request, "La borne a été créé")
615
            return redirect(reverse('topologie:index-ap'))
616
    i_mbf_param = generate_ipv4_mbf_param(interface, False)
617 618 619 620 621 622 623 624 625 626 627
    return form(
        {
            'topoform': interface,
            'machineform': ap,
            'domainform': domain,
            'i_mbf_param': i_mbf_param,
            'device': 'wifi ap',
        },
        'topologie/topo_more.html',
        request
    )
628 629 630


@login_required
631
@can_edit(AccessPoint)
632
def edit_ap(request, ap, **_kwargs):
633 634
    """ Edition d'un switch. Permet de chambre nombre de ports,
    place dans le stack, interface et machine associée"""
635
    interface_form = EditInterfaceForm(
636 637
        request.POST or None,
        user=request.user,
638
        instance=ap.interface_set.first()
639
    )
640
    ap_form = EditAccessPointForm(
641 642
        request.POST or None,
        user=request.user,
643
        instance=ap
644 645 646
    )
    domain_form = DomainForm(
        request.POST or None,
647
        instance=ap.interface_set.first().domain
648
        )
649
    if ap_form.is_valid() and interface_form.is_valid():
650 651
        user = AssoOption.get_cached_value('utilisateur_asso')
        if not user:
652 653 654 655 656
            messages.error(
                request,
                ("L'user association n'existe pas encore, veuillez le "
                 "créer ou le linker dans preferences")
            )
657
            return redirect(reverse('topologie:index-ap'))
658 659 660
        new_ap_obj = ap_form.save(commit=False)
        new_interface_obj = interface_form.save(commit=False)
        new_domain_obj = domain_form.save(commit=False)
661
        if ap_form.changed_data:
662
            new_ap_obj.save()
663
        if interface_form.changed_data:
664
            new_interface_obj.save()
665
        if domain_form.changed_data:
666
            new_domain_obj.save()
667
        messages.success(request, "La borne a été modifiée")
668
        return redirect(reverse('topologie:index-ap'))
669 670 671 672 673 674 675 676 677 678 679 680 681
    i_mbf_param = generate_ipv4_mbf_param(interface_form, False)
    return form(
        {
            'topoform': interface_form,
            'machineform': ap_form,
            'domainform': domain_form,
            'i_mbf_param': i_mbf_param,
            'device': 'wifi ap',
        },
        'topologie/topo_more.html',
        request
    )

682

chirac's avatar
chirac committed
683
@login_required
684
@can_create(Room)
chirac's avatar
chirac committed
685
def new_room(request):
686
    """Nouvelle chambre """
chirac's avatar
chirac committed
687 688
    room = EditRoomForm(request.POST or None)
    if room.is_valid():
689
        room.save()
chirac's avatar
chirac committed
690
        messages.success(request, "La chambre a été créé")
691
        return redirect(reverse('topologie:index-room'))
692 693 694 695 696
    return form(
        {'topoform': room, 'action_name': 'Ajouter'},
        'topologie/topo.html',
        request
    )
chirac's avatar
chirac committed
697

chirac's avatar
chirac committed
698 699

@login_required
700
@can_edit(Room)
701
def edit_room(request, room, **_kwargs):
702
    """ Edition numero et details de la chambre"""
chirac's avatar
chirac committed
703 704
    room = EditRoomForm(request.POST or None, instance=room)
    if room.is_valid():
705 706 707
        if room.changed_data:
            room.save()
            messages.success(request, "La chambre a bien été modifiée")
708
        return redirect(reverse('topologie:index-room'))
709 710 711 712 713
    return form(
        {'topoform': room, 'action_name': 'Editer'},
        'topologie/topo.html',
        request
    )
chirac's avatar
chirac committed
714

chirac's avatar
chirac committed
715 716

@login_required
717
@can_delete(Room)
718
def del_room(request, room, **_kwargs):
719
    """ Suppression d'un chambre"""
chirac's avatar
chirac committed
720
    if request.method == "POST":
721
        try:
722 723
            room.delete()
            messages.success(request, "La chambre/prise a été détruite")
724
        except ProtectedError:
725 726 727 728 729
            messages.error(
                request,
                ("La chambre %s est affectée à un autre objet, impossible "
                 "de la supprimer (switch ou user)" % room)
            )
730
        return redirect(reverse('topologie:index-room'))
731 732 733 734 735
    return form(
        {'objet': room, 'objet_name': 'Chambre'},
        'topologie/delete.html',
        request
    )
736 737 738


@login_required
739
@can_create(ModelSwitch)
740 741 742 743
def new_model_switch(request):
    """Nouveau modèle de switch"""
    model_switch = EditModelSwitchForm(request.POST or None)
    if model_switch.is_valid():
744
        model_switch.save()
745
        messages.success(request, "Le modèle a été créé")
746
        return redirect(reverse('topologie:index-model-switch'))
747 748 749 750 751
    return form(
        {'topoform': model_switch, 'action_name': 'Ajouter'},
        'topologie/topo.html',
        request
    )
752 753 754


@login_required
755
@can_edit(ModelSwitch)
756
def edit_model_switch(request, model_switch, **_kwargs):
757
    """ Edition d'un modèle de switch"""
758

759 760 761 762
    model_switch = EditModelSwitchForm(
        request.POST or None,
        instance=model_switch
    )
763
    if model_switch.is_valid():
764 765 766
        if model_switch.changed_data:
            model_switch.save()
            messages.success(request, "Le modèle a bien été modifié")
767
        return redirect(reverse('topologie:index-model-switch'))
768 769 770 771 772
    return form(
        {'topoform': model_switch, 'action_name': 'Editer'},
        'topologie/topo.html',
        request
    )
773 774 775


@login_required
776
@can_delete(ModelSwitch)
777
def del_model_switch(request, model_switch, **_kwargs):
778 779 780
    """ Suppression d'un modèle de switch"""
    if request.method == "POST":
        try:
781 782
            model_switch.delete()
            messages.success(request, "Le modèle a été détruit")
783
        except ProtectedError:
784 785 786 787 788
            messages.error(
                request,
                ("Le modèle %s est affectée à un autre objet, impossible "
                 "de la supprimer (switch ou user)" % model_switch)
            )
789
        return redirect(reverse('topologie:index-model-switch'))
790 791 792 793 794
    return form(
        {'objet': model_switch, 'objet_name': 'Modèle de switch'},
        'topologie/delete.html',
        request
    )
795 796


797 798 799 800 801 802 803 804
@login_required
@can_create(SwitchBay)
def new_switch_bay(request):
    """Nouvelle baie de switch"""
    switch_bay = EditSwitchBayForm(request.POST or None)
    if switch_bay.is_valid():
        switch_bay.save()
        messages.success(request, "La baie a été créé")
805
        return redirect(reverse('topologie:index-physical-grouping'))
806 807 808 809 810
    return form(
        {'topoform': switch_bay, 'action_name': 'Ajouter'},
        'topologie/topo.html',
        request
    )
811 812 813 814


@login_required
@can_edit(SwitchBay)
815
def edit_switch_bay(request, switch_bay, **_kwargs):
816 817 818 819 820 821
    """ Edition d'une baie de switch"""
    switch_bay = EditSwitchBayForm(request.POST or None, instance=switch_bay)
    if switch_bay.is_valid():
        if switch_bay.changed_data:
            switch_bay.save()
            messages.success(request, "Le switch a bien été modifié")
822
        return redirect(reverse('topologie:index-physical-grouping'))
823 824 825 826 827
    return form(
        {'topoform': switch_bay, 'action_name': 'Editer'},
        'topologie/topo.html',
        request
    )
828 829 830 831


@login_required
@can_delete(SwitchBay)
832
def del_switch_bay(request, switch_bay, **_kwargs):
833 834 835 836 837 838
    """ Suppression d'une baie de switch"""
    if request.method == "POST":
        try:
            switch_bay.delete()
            messages.success(request, "La baie a été détruite")
        except ProtectedError:
839 840 841 842 843
            messages.error(
                request,
                ("La baie %s est affecté à un autre objet, impossible "
                 "de la supprimer (switch ou user)" % switch_bay)
            )
844
        return redirect(reverse('topologie:index-physical-grouping'))
845 846 847 848 849
    return form(
        {'objet': switch_bay, 'objet_name': 'Baie de switch'},
        'topologie/delete.html',
        request
    )
850 851


852 853 854 855 856 857 858 859
@login_required
@can_create(Building)
def new_building(request):
    """Nouveau batiment"""
    building = EditBuildingForm(request.POST or None)
    if building.is_valid():
        building.save()
        messages.success(request, "Le batiment a été créé")
860
        return redirect(reverse('topologie:index-physical-grouping'))
861 862 863 864 865
    return form(
        {'topoform': building, 'action_name': 'Ajouter'},
        'topologie/topo.html',
        request
    )
866 867 868 869


@login_required
@can_edit(Building)
870
def edit_building(request, building, **_kwargs):
871 872 873 874 875 876
    """ Edition d'un batiment"""
    building = EditBuildingForm(request.POST or None, instance=building)
    if building.is_valid():
        if building.changed_data:
            building.save()
            messages.success(request, "Le batiment a bien été modifié")
877
        return redirect(reverse('topologie:index-physical-grouping'))
878 879 880 881 882
    return form(
        {'topoform': building, 'action_name': 'Editer'},
        'topologie/topo.html',
        request
    )
883 884 885 886


@login_required
@can_delete(Building)
887
def del_building(request, building, **_kwargs):
888 889 890 891 892 893
    """ Suppression d'un batiment"""
    if request.method == "POST":
        try:
            building.delete()
            messages.success(request, "La batiment a été détruit")
        except ProtectedError:
894 895 896 897 898
            messages.error(
                request,
                ("Le batiment %s est affecté à un autre objet, impossible "
                 "de la supprimer (switch ou user)" % building)
            )
899
        return redirect(reverse('topologie:index-physical-grouping'))
900 901 902 903 904
    return form(
        {'objet': building, 'objet_name': 'Bâtiment'},
        'topologie/delete.html',
        request
    )
905 906


907
@login_required
908
@can_create(ConstructorSwitch)
909 910 911 912
def new_constructor_switch(request):
    """Nouveau constructeur de switch"""
    constructor_switch = EditConstructorSwitchForm(request.POST or None)
    if constructor_switch.is_valid():
913
        constructor_switch.save()
914
        messages.success(request, "Le constructeur a été créé")
915
        return redirect(reverse('topologie:index-model-switch'))
916 917 918 919 920
    return form(
        {'topoform': constructor_switch, 'action_name': 'Ajouter'},
        'topologie/topo.html',
        request
    )
921 922 923


@login_required
924
@can_edit(ConstructorSwitch)
925
def edit_constructor_switch(request, constructor_switch, **_kwargs):
926
    """ Edition d'un constructeur de switch"""
927

928 929 930 931
    constructor_switch = EditConstructorSwitchForm(
        request.POST or None,
        instance=constructor_switch
    )
932
    if constructor_switch.is_valid():
933 934 935
        if constructor_switch.changed_data:
            constructor_switch.save()
            messages.success(request, "Le modèle a bien été modifié")
936
        return redirect(reverse('topologie:index-model-switch'))
937 938 939 940 941
    return form(
        {'topoform': constructor_switch, 'action_name': 'Editer'},
        'topologie/topo.html',
        request
    )
942 943 944


@login_required
945
@can_delete(ConstructorSwitch)
946
def del_constructor_switch(request, constructor_switch, **_kwargs):
947 948 949
    """ Suppression d'un constructeur de switch"""
    if request.method == "POST":
        try:
950 951
            constructor_switch.delete()
            messages.success(request, "Le constructeur a été détruit")
952
        except ProtectedError:
953 954 955 956 957
            messages.error(
                request,
                ("Le constructeur %s est affecté à un autre objet, impossible "
                 "de la supprimer (switch ou user)" % constructor_switch)
            )
958
        return redirect(reverse('topologie:index-model-switch'))
959 960 961 962
    return form({
        'objet': constructor_switch,
        'objet_name': 'Constructeur de switch'
        }, 'topologie/delete.html', request)
963 964


Fernet Laouen's avatar
Fernet Laouen committed
965 966 967 968
@login_required
@can_create(PortProfile)
def new_port_profile(request):
    """Create a new port profile"""
969
    port_profile = EditPortProfileForm(request.POST or None)
Fernet Laouen's avatar
Fernet Laouen committed
970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
    if port_profile.is_valid():
        port_profile.save()
        messages.success(request, _("Port profile created"))
        return redirect(reverse('topologie:index'))
    return form(
        {'topoform': port_profile, 'action_name': _("Create")},
        'topologie/topo.html',
        request
    )


@login_required
@can_edit(PortProfile)
def edit_port_profile(request, port_profile, **_kwargs):
    """Edit a port profile"""
    port_profile = EditPortProfileForm(request.POST or None, instance=port_profile)
    if port_profile.is_valid():
        if port_profile.changed_data:
            port_profile.save()
            messages.success(request, _("Port profile modified"))
        return redirect(reverse('topologie:index'))
    return form(
        {'topoform': port_profile, 'action_name': _("Edit")},
        'topologie/topo.html',
        request
    )



@login_required
@can_delete(PortProfile)
def del_port_profile(request, port_profile, **_kwargs):
    """Delete a port profile"""
    if request.method == 'POST':
        try:
            port_profile.delete()
            messages.success(request, _("The port profile was successfully"
                                        " deleted"))
        except ProtectedError:
            messages.success(request, _("Impossible to delete the port"
                                        " profile"))
        return redirect(reverse('topologie:index'))
    return form(
            {'objet': port_profile, 'objet_name': _("Port profile")},
            'topologie/delete.html',
            request
    )

1018 1019
def make_machine_graph():
    """
grisel-davy's avatar
grisel-davy committed
1020
    Create the graph of switchs, machines and access points.
1021
    """
grisel-davy's avatar
grisel-davy committed
1022 1023 1024 1025 1026
    dico = {
        'subs': [],
        'links' : [],
        'alone': [],
        'colors': {
grisel-davy's avatar
grisel-davy committed
1027
            'head': "#7f0505",  # Color parameters for the graph
grisel-davy's avatar
grisel-davy committed
1028 1029 1030 1031 1032 1033 1034 1035 1036
            'back': "#b5adad",
            'texte': "#563d01",
            'border_bornes': "#02078e",
            'head_bornes': "#25771c",
            'head_server': "#1c3777"
            }
        }
    missing = list(Switch.objects.all())
    detected = []
grisel-davy's avatar
grisel-davy committed
1037 1038
    for building in Building.objects.all():  # Visit all buildings

grisel-davy's avatar
grisel-davy committed
1039 1040 1041 1042 1043 1044 1045 1046 1047
        dico['subs'].append(
            {
            'bat_id': building.id,
            'bat_name': building,
            'switchs': [],
            'bornes': [],
            'machines': []
            }
        )
grisel-davy's avatar
grisel-davy committed
1048 1049
        # Visit all switchs in this building
        for switch in Switch.objects.filter(switchbay__building=building):   
grisel-davy's avatar
grisel-davy committed
1050 1051 1052 1053 1054 1055 1056 1057
            dico['subs'][-1]['switchs'].append({
                'name': switch.main_interface().domain.name,
                'nombre': switch.number,
                'model': switch.model,
                'id': switch.id,
                'batiment': building,
                'ports': []
            })
grisel-davy's avatar
grisel-davy committed
1058
            # visit all ports of this switch and add the switchs linked to it
grisel-davy's avatar
grisel-davy committed
1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079
            for port in switch.ports.filter(related__isnull=False): 
                dico['subs'][-1]['switchs'][-1]['ports'].append({
                    'numero': port.port,
                    'related': port.related.switch.main_interface().domain.name
                })

        for ap in AccessPoint.all_ap_in(building):
            dico['subs'][-1]['bornes'].append({
                'name': ap.short_name,
                'switch': ap.switch()[0].main_interface().domain.name,
                'port': ap.switch()[0].ports.filter(
                    machine_interface__machine=ap
                )[0].port
            })
        for server in Server.all_server_in(building):
            dico['subs'][-1]['machines'].append({
                'name': server.short_name,
                'switch': server.switch()[0].main_interface().domain.name,
                'port': Port.objects.filter(machine_interface__machine=server)[0].port
            })

grisel-davy's avatar
grisel-davy committed
1080
    # While the list of forgotten ones is not empty
grisel-davy's avatar
grisel-davy committed
1081
    while missing:
grisel-davy's avatar
grisel-davy committed
1082
        if missing[0].ports.count():  # The switch is not empty
grisel-davy's avatar
grisel-davy committed
1083
            links, new_detected = recursive_switchs(missing[0], None, [missing[0]])
grisel-davy's avatar
grisel-davy committed
1084 1085
            for link in links:
                dico['links'].append(link)
grisel-davy's avatar
grisel-davy committed
1086
            # Update the lists of missings and already detected switchs
grisel-davy's avatar
grisel-davy committed
1087 1088
            missing=[i for i in missing if i not in new_detected]
            detected += new_detected
grisel-davy's avatar
grisel-davy committed
1089
        else:  # If the switch have no ports, don't explore it and hop to the next one
grisel-davy's avatar
grisel-davy committed
1090
            del missing[0]
grisel-davy's avatar
grisel-davy committed
1091
    # Switchs that are not connected or not in a building
grisel-davy's avatar
grisel-davy committed
1092 1093 1094 1095
    for switch in Switch.objects.filter(switchbay__isnull=True).exclude(ports__related__isnull=False):
        dico['alone'].append({
            'id': switch.id,
            'name': switch.main_interface().domain.name
grizzly's avatar
grizzly committed
1096
            })
grisel-davy's avatar
grisel-davy committed
1097

grisel-davy's avatar
grisel-davy committed
1098

grisel-davy's avatar
grisel-davy committed
1099
    dot_data=generate_dot(dico,'topologie/graph_switch.dot')  # generate the dot file
grisel-davy's avatar
grisel-davy committed
1100 1101 1102 1103

    f = tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False)  # Create a temporary file to store the dot data
    with f:
            f.write(dot_data)
grisel-davy's avatar
grisel-davy committed
1104
    unflatten = Popen(  # unflatten the graph to make it look better
grisel-davy's avatar
grisel-davy committed
1105
            ["unflatten","-l", "3", f.name],
grisel-davy's avatar
grisel-davy committed
1106 1107
        stdout=PIPE
    )
grisel-davy's avatar
grisel-davy committed
1108
    image = Popen(  # pipe the result of the first command into the second
grisel-davy's avatar
grisel-davy committed
1109 1110 1111 1112
        ["dot", "-Tpng", "-o", MEDIA_ROOT + "/images/switchs.png"],
        stdin=unflatten.stdout,
        stdout=PIPE
    )
1113

grisel-davy's avatar
grisel-davy committed
1114
def generate_dot(data,template):
grisel-davy's avatar
grisel-davy committed
1115
    """create the dot file
grisel-davy's avatar
grisel-davy committed
1116 1117 1118
    :param data: dictionary passed to the template
    :param template: path to the dot template
    :return: all the lines of the dot file"""
grisel-davy's avatar
grisel-davy committed
1119 1120 1121 1122 1123 1124 1125 1126 1127
    t = loader.get_template(template)
    if not isinstance(t, Template) and not (hasattr(t, 'template') and isinstance(t.template, Template)):
        raise Exception("Le template par défaut de Django n'est pas utilisé."
                        "Cela peut mener à des erreurs de rendu."
                        "Vérifiez les paramètres")
    c = Context(data).flatten()
    dot = t.render(c)
    return(dot)

grisel-davy's avatar
grisel-davy committed
1128 1129
def recursive_switchs(switch_start, switch_before, detected):
    """Visit the switch and travel to the switchs linked to it.
grisel-davy's avatar
grisel-davy committed
1130 1131 1132 1133
    :param switch_start: the switch to begin the visit on
    :param switch_before: the switch that you come from. None if switch_start is the first one
    :param detected: list of all switchs already visited. None if switch_start is the first one
    :return: A list of all the links found and a list of all the switchs visited"""
grizzly's avatar
grizzly committed
1134
    detected.append(switch_start)
grisel-davy's avatar
grisel-davy committed
1135
    links_return=[]  # list of dictionaries of the links to be detected
grizzly's avatar
grizzly committed
1136 1137
    for port in switch_start.ports.filter(related__isnull=False):  # create links to every switchs below
        if port.related.switch != switch_before and port.related.switch != port.switch and port.related.switch not in detected:  # Not the switch that we come from, not the current switch
grisel-davy's avatar
grisel-davy committed
1138
            links = {  # Dictionary of a link
grisel-davy's avatar
grisel-davy committed
1139
                'depart':switch_start.id,
grisel-davy's avatar
grisel-davy committed
1140 1141
                'arrive':port.related.switch.id
            }
grisel-davy's avatar
grisel-davy committed
1142
            links_return.append(links)  # Add current and below levels links
grizzly's avatar
grizzly committed
1143 1144 1145 1146 1147 1148 1149

    for port in switch_start.ports.filter(related__isnull=False):  # go down on every related switchs
        if port.related.switch not in detected:  # The switch at the end of this link has not been visited
            links_down, detected = recursive_switchs(port.related.switch, switch_start, detected)  # explore it and get the results
            for link in links_down:  # Add the non empty links to the current list
                if link:
                    links_return.append(link) 
grisel-davy's avatar
grisel-davy committed
1150 1151
    return (links_return, detected)