Commit b735b044 authored by root's avatar root

Merge branch 'master' into ouverture_des_ports

parents 1b7617dd 7057eafa
......@@ -106,7 +106,7 @@ def index(request):
'user_id': v.revision.user_id,
'version': v }
else :
to_remove.append(i)
to_remove.insert(0,i)
# Remove all tagged invalid items
for i in to_remove :
versions.object_list.pop(i)
......
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
......@@ -5,6 +6,7 @@
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
# Copyright © 2017 Maël Kervella
#
# 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
......@@ -52,8 +54,7 @@ class BaseEditMachineForm(EditMachineForm):
class EditInterfaceForm(ModelForm):
class Meta:
model = Interface
# fields = '__all__'
exclude = ['port_lists']
fields = ['machine', 'type', 'ipv4', 'mac_address', 'details']
def __init__(self, *args, **kwargs):
super(EditInterfaceForm, self).__init__(*args, **kwargs)
......@@ -63,12 +64,14 @@ class EditInterfaceForm(ModelForm):
if "ipv4" in self.fields:
self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4"
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True)
# Add it's own address
self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance)
if "machine" in self.fields:
self.fields['machine'].queryset = Machine.objects.all().select_related('user')
class AddInterfaceForm(EditInterfaceForm):
class Meta(EditInterfaceForm.Meta):
fields = ['ipv4','mac_address','type','details']
fields = ['type','ipv4','mac_address','details']
def __init__(self, *args, **kwargs):
infra = kwargs.pop('infra')
......@@ -82,11 +85,11 @@ class AddInterfaceForm(EditInterfaceForm):
class NewInterfaceForm(EditInterfaceForm):
class Meta(EditInterfaceForm.Meta):
fields = ['mac_address','type','details']
fields = ['type','mac_address','details']
class BaseEditInterfaceForm(EditInterfaceForm):
class Meta(EditInterfaceForm.Meta):
fields = ['ipv4','mac_address','type','details']
fields = ['type','ipv4','mac_address','details']
def __init__(self, *args, **kwargs):
infra = kwargs.pop('infra')
......@@ -95,8 +98,11 @@ class BaseEditInterfaceForm(EditInterfaceForm):
if not infra:
self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False))
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False))
# Add it's own address
self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance)
else:
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True)
self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance)
class AliasForm(ModelForm):
class Meta:
......
......@@ -56,6 +56,7 @@ class MachineType(models.Model):
ip_type = models.ForeignKey('IpType', on_delete=models.PROTECT, blank=True, null=True)
def all_interfaces(self):
""" Renvoie toutes les interfaces (cartes réseaux) de type machinetype"""
return Interface.objects.filter(type=self)
def __str__(self):
......@@ -76,23 +77,31 @@ class IpType(models.Model):
@cached_property
def ip_range(self):
return IPRange(self.domaine_ip_start, end=self.domaine_ip_stop)
""" Renvoie un objet IPRange à partir de l'objet IpType"""
return IPRange(self.domaine_ip_start, end=self.domaine_ip_stop)
@cached_property
def ip_set(self):
""" Renvoie une IPSet à partir de l'iptype"""
return IPSet(self.ip_range)
@cached_property
def ip_set_as_str(self):
""" Renvoie une liste des ip en string"""
return [str(x) for x in self.ip_set]
def ip_objects(self):
""" Renvoie tous les objets ipv4 relié à ce type"""
return IpList.objects.filter(ip_type=self)
def free_ip(self):
""" Renvoie toutes les ip libres associées au type donné (self)"""
return IpList.objects.filter(interface__isnull=True).filter(ip_type=self)
def gen_ip_range(self):
""" Cree les IpList associées au type self. Parcours pédestrement et crée
les ip une par une. Si elles existent déjà, met à jour le type associé
à l'ip"""
# Creation du range d'ip dans les objets iplist
networks = []
for net in self.ip_range.cidrs():
......@@ -115,6 +124,11 @@ class IpType(models.Model):
ip.delete()
def clean(self):
""" Nettoyage. Vérifie :
- Que ip_stop est après ip_start
- Qu'on ne crée pas plus gros qu'un /16
- Que le range crée ne recoupe pas un range existant
- Formate l'ipv6 donnée en /64"""
if IPAddress(self.domaine_ip_start) > IPAddress(self.domaine_ip_stop):
raise ValidationError("Domaine end doit être après start...")
# On ne crée pas plus grand qu'un /16
......@@ -137,6 +151,7 @@ class IpType(models.Model):
return self.type
class Vlan(models.Model):
""" Un vlan : vlan_id et nom"""
PRETTY_NAME = "Vlans"
vlan_id = models.IntegerField()
......@@ -147,6 +162,9 @@ class Vlan(models.Model):
return self.name
class Nas(models.Model):
""" Les nas. Associé à un machine_type.
Permet aussi de régler le port_access_mode (802.1X ou mac-address) pour
le radius. Champ autocapture de la mac à true ou false"""
PRETTY_NAME = "Correspondance entre les nas et les machines connectées"
default_mode = '802.1X'
......@@ -165,6 +183,8 @@ class Nas(models.Model):
return self.name
class Extension(models.Model):
""" Extension dns type example.org. Précise si tout le monde peut l'utiliser,
associé à un origin (ip d'origine)"""
PRETTY_NAME = "Extensions dns"
name = models.CharField(max_length=255, unique=True)
......@@ -173,12 +193,15 @@ class Extension(models.Model):
@cached_property
def dns_entry(self):
""" Une entrée DNS A"""
return "@ IN A " + str(self.origin)
def __str__(self):
return self.name
class Mx(models.Model):
""" Entrées des MX. Enregistre la zone (extension) associée et la priorité
Todo : pouvoir associer un MX à une interface """
PRETTY_NAME = "Enregistrements MX"
zone = models.ForeignKey('Extension', on_delete=models.PROTECT)
......@@ -206,6 +229,7 @@ class Ns(models.Model):
return str(self.zone) + ' ' + str(self.ns)
class Text(models.Model):
""" Un enregistrement TXT associé à une extension"""
PRETTY_NAME = "Enregistrement text"
zone = models.ForeignKey('Extension', on_delete=models.PROTECT)
......@@ -220,6 +244,12 @@ class Text(models.Model):
return str(self.field1) + " IN TXT " + str(self.field2)
class Interface(models.Model):
""" Une interface. Objet clef de l'application machine :
- une address mac unique. Possibilité de la rendre unique avec le typemachine
- une onetoone vers IpList pour attribution ipv4
- le type parent associé au range ip et à l'extension
- un objet domain associé contenant son nom
- la liste des ports oiuvert"""
PRETTY_NAME = "Interface"
ipv4 = models.OneToOneField('IpList', on_delete=models.PROTECT, blank=True, null=True)
......@@ -239,6 +269,7 @@ class Interface(models.Model):
@cached_property
def ipv6_object(self):
""" Renvoie un objet type ipv6 à partir du prefix associé à l'iptype parent"""
if self.type.ip_type.prefix_v6:
return EUI(self.mac_address).ipv6(IPNetwork(self.type.ip_type.prefix_v6).network)
else:
......@@ -246,18 +277,23 @@ class Interface(models.Model):
@cached_property
def ipv6(self):
""" Renvoie l'ipv6 en str. Mise en cache et propriété de l'objet"""
return str(self.ipv6_object)
def mac_bare(self):
""" Formatage de la mac type mac_bare"""
return str(EUI(self.mac_address, dialect=mac_bare)).lower()
def filter_macaddress(self):
""" Tente un formatage mac_bare, si échoue, lève une erreur de validation"""
try:
self.mac_address = str(EUI(self.mac_address))
except :
raise ValidationError("La mac donnée est invalide")
def clean(self, *args, **kwargs):
""" Formate l'addresse mac en mac_bare (fonction filter_mac)
et assigne une ipv4 dans le bon range si inexistante ou incohérente"""
self.filter_macaddress()
self.mac_address = str(EUI(self.mac_address)) or None
if not self.ipv4 or self.type.ip_type != self.ipv4.ip_type:
......@@ -274,6 +310,7 @@ class Interface(models.Model):
return
def unassign_ipv4(self):
""" Sans commentaire, désassigne une ipv4"""
self.ipv4 = None
def update_type(self):
......@@ -296,15 +333,20 @@ class Interface(models.Model):
return str(domain)
def has_private_ip(self):
""" True si l'ip associée est privée"""
if self.ipv4:
return IPAddress(str(self.ipv4)).is_private()
else:
return False
def may_have_port_open(self):
""" True si l'interface a une ip et une ip publique.
Permet de ne pas exporter des ouvertures sur des ip privées (useless)"""
return self.ipv4 and not self.has_private_ip()
class Domain(models.Model):
""" Objet domain. Enregistrement A et CNAME en même temps : permet de stocker les
alias et les nom de machines, suivant si interface_parent ou cname sont remplis"""
PRETTY_NAME = "Domaine dns"
interface_parent = models.OneToOneField('Interface', on_delete=models.CASCADE, blank=True, null=True)
......@@ -316,6 +358,8 @@ class Domain(models.Model):
unique_together = (("name", "extension"),)
def get_extension(self):
""" Retourne l'extension de l'interface parente si c'est un A
Retourne l'extension propre si c'est un cname, renvoie None sinon"""
if self.interface_parent:
return self.interface_parent.type.ip_type.extension
elif hasattr(self,'extension'):
......@@ -324,6 +368,11 @@ class Domain(models.Model):
return None
def clean(self):
""" Validation :
- l'objet est bien soit A soit CNAME
- le cname est pas pointé sur lui-même
- le nom contient bien les caractères autorisés par la norme dns et moins de 63 caractères au total
- le couple nom/extension est bien unique"""
if self.get_extension():
self.extension=self.get_extension()
""" Validation du nom de domaine, extensions dans type de machine, prefixe pas plus long que 63 caractères """
......@@ -342,10 +391,12 @@ class Domain(models.Model):
@cached_property
def dns_entry(self):
""" Une entrée DNS"""
if self.cname:
return str(self.name) + " IN CNAME " + str(self.cname) + "."
def save(self, *args, **kwargs):
""" Empèche le save sans extension valide. Force à avoir appellé clean avant"""
if not self.get_extension():
raise ValidationError("Extension invalide")
self.full_clean()
......@@ -362,9 +413,11 @@ class IpList(models.Model):
@cached_property
def need_infra(self):
""" Permet de savoir si un user basique peut assigner cette ip ou non"""
return self.ip_type.need_infra
def clean(self):
""" Erreur si l'ip_type est incorrect"""
if not str(self.ipv4) in self.ip_type.ip_set_as_str:
raise ValidationError("L'ipv4 et le range de l'iptype ne correspondent pas!")
return
......
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
......
......@@ -27,30 +27,36 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endif %}
<table class="table">
<colgroup>
<col>
<col>
<col>
<col width="{% if ipv6_enabled %}300{% else %}150{% endif %}px">
<col width="144px">
</colgroup>
<thead>
<tr>
<th>Actions</th>
<th>Proprietaire</th>
<th>Nom dns</th>
<th>Type</th>
<th>Mac</th>
<th>IP</th>
<th></th>
</tr>
</thead>
<th>Nom DNS</th>
<th>Type</th>
<th>MAC</th>
<th>IP</th>
<th>Actions</th>
<tbody>
{% for machine in machines_list %}
{% for interface in machine.interface_set.all %}
<tr class="active">
{% if forloop.first %}
<td rowspan="{{ machine.interface_set.all|length }}">
<tr class="info">
<td colspan="4">
<b>{{ machine.name }}</b> <i class="glyphicon glyphicon-chevron-right"></i>
<a href="{% url 'users:profil' userid=machine.user.id %}" title="Voir le profil">
<i class="glyphicon glyphicon-user"></i> {{ machine.user }}
</a>
</td>
<td class="text-right">
{% include 'buttons/add.html' with href='machines:new-interface' id=machine.id desc='Ajouter une interface' %}
{% include 'buttons/suppr.html' with href='machines:del-machine' id=machine.id %}
{% include 'buttons/history.html' with href='machines:history' name='machine' id=machine.id %}
{% include 'buttons/suppr.html' with href='machines:del-machine' id=machine.id %}
</td>
<td rowspan="{{ machine.interface_set.all|length }}">
<a href="{% url 'users:profil' userid=machine.user.id %}"><b>{{ machine.user }}</b></a>
</td>
{% endif %}
</tr>
{% for interface in machine.interface_set.all %}
<tr>
<td>
{% if interface.domain.related_domain.all %}
<div class="dropdown">
......@@ -72,19 +78,26 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{{ interface.domain }}
{% endif %}
</td>
<td>{{ interface.type }}</td>
<td>{{ interface.mac_address }}</td>
<td><b>IPv4</b> {{ interface.ipv4 }}
{% if ipv6_enabled %}
<br>
<b>IPv6</b> {{ interface.ipv6 }}
{% endif %}
</td>
<td>
<div class="dropdown">
<button class="btn btn-default dropdown-toggle" type="button" id="editioninterface" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
Modifier <span class="caret"></span>
{{ interface.type }}
</td>
<td>
{{ interface.mac_address }}
</td>
<td>
<b>IPv4</b> {{ interface.ipv4 }}
<br>
{% if ipv6_enabled and interface.ipv6 != 'None'%}
<b>IPv6</b> {{ interface.ipv6 }}
{% endif %}
</td>
<td class="text-right">
<div class="dropdown" style="width: 128px;">
<button class="btn btn-primary btn-sm dropdown-toggle" type="button" id="editioninterface" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="glyphicon glyphicon-edit"></i> <span class="caret"></span>
</button>
{% include 'buttons/history.html' with href='machines:history' name='interface' id=interface.id %}
{% include 'buttons/suppr.html' with href='machines:del-interface' id=interface.id %}
<ul class="dropdown-menu" aria-labelledby="editioninterface">
<li>
<a href="{% url 'machines:edit-interface' interface.id %}">
......@@ -96,19 +109,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<i class="glyphicon glyphicon-edit"></i> Gerer les alias
</a>
</li>
<li>
<a href="{% url 'machines:port-config' interface.id%}">
<i class="glyphicon glyphicon-edit"></i> Gerer la configuration des ports
</a>
</li>
<li>
<a href="{% url 'machines:history' 'interface' interface.id %}">
<i class="glyphicon glyphicon-time"></i> Historique
</a>
</li>
<li>
<a href="{% url 'machines:del-interface' interface.id %}">
<i class="glyphicon glyphicon-trash"></i> Supprimer
<a href="{% url 'machines:port-config' interface.id%}">
<i class="glyphicon glyphicon-edit"></i> Gerer la configuration des ports
</a>
</li>
</ul>
......@@ -120,5 +123,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td colspan="8"></td>
</tr>
{% endfor %}
</tbody>
</table>
......@@ -7,6 +7,7 @@ quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
Copyright © 2017 Maël Kervella
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
......@@ -24,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load bootstrap_form_typeahead %}
{% block title %}Création et modification de machines{% endblock %}
......@@ -38,16 +40,30 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% bootstrap_form_errors domainform %}
{% endif %}
<form class="form" method="post">
{% csrf_token %}
{% if machineform %}
<h3>Machine</h3>
{% bootstrap_form machineform %}
{% endif %}
{% if interfaceform %}
{% bootstrap_form interfaceform %}
<h3>Interface</h3>
{% if i_bft_param %}
{% if 'machine' in interfaceform.fields %}
{% bootstrap_form_typeahead interfaceform 'ipv4,machine' bft_param=i_bft_param %}
{% else %}
{% bootstrap_form_typeahead interfaceform 'ipv4' bft_param=i_bft_param %}
{% endif %}
{% else %}
{% if 'machine' in interfaceform.fields %}
{% bootstrap_form_typeahead interfaceform 'ipv4,machine' %}
{% else %}
{% bootstrap_form_typeahead interfaceform 'ipv4' %}
{% endif %}
{% endif %}
{% endif %}
{% if domainform %}
<h3>Domaine</h3>
{% bootstrap_form domainform %}
{% endif %}
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
......
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Maël Kervella
#
# 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.
This diff is collapsed.
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
......
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
......
This diff is collapsed.
span.twitter-typeahead .tt-menu,
span.twitter-typeahead .tt-dropdown-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
display: none;
float: left;
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0;
list-style: none;
font-size: 14px;
text-align: left;
background-color: #ffffff;
border: 1px solid #cccccc;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 4px;
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
background-clip: padding-box;
}
span.twitter-typeahead .tt-suggestion {
display: block;
padding: 3px 20px;
clear: both;
font-weight: normal;
line-height: 1.42857143;
color: #333333;
white-space: nowrap;
}
span.twitter-typeahead .tt-suggestion.tt-cursor,
span.twitter-typeahead .tt-suggestion:hover,
span.twitter-typeahead .tt-suggestion:focus {
color: #ffffff;
text-decoration: none;
outline: 0;
background-color: #337ab7;
}
.input-group.input-group-lg span.twitter-typeahead .form-control {
height: 46px;
padding: 10px 16px;
font-size: 18px;
line-height: 1.3333333;
border-radius: 6px;
}
.input-group.input-group-sm span.twitter-typeahead .form-control {
height: 30px;
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px;
}
span.twitter-typeahead {
width: 100%;
}
.input-group span.twitter-typeahead {
display: block !important;
height: 34px;
}
.input-group span.twitter-typeahead .tt-menu,
.input-group span.twitter-typeahead .tt-dropdown-menu {
top: 32px !important;
}
.input-group span.twitter-typeahead:not(:first-child):not(:last-child) .form-control {
border-radius: 0;
}
.input-group span.twitter-typeahead:first-child .form-control {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.input-group span.twitter-typeahead:last-child .form-control {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.input-group.input-group-sm span.twitter-typeahead {
height: 30px;
}
.input-group.input-group-sm span.twitter-typeahead .tt-menu,
.input-group.input-group-sm span.twitter-typeahead .tt-dropdown-menu {
top: 30px !important;
}
.input-group.input-group-lg span.twitter-typeahead {
height: 46px;
}
.input-group.input-group-lg span.twitter-typeahead .tt-menu,
.input-group.input-group-lg span.twitter-typeahead .tt-dropdown-menu {
top: 46px !important;
}
This diff is collapsed.
This diff is collapsed.
......@@ -32,8 +32,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<head>
{# Load CSS and JavaScript #}
{% bootstrap_css %}
<link href="/static/css/typeaheadjs.css" rel="stylesheet">
{% bootstrap_javascript %}
<script src="/static/js/typeahead.js"></script>
<script src="/static/js/handlebars.js"></script>
<link rel="stylesheet" href="{% static "/css/base.css" %}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ site_name }} : {% block title %}Accueil{% endblock %}</title>
......
......@@ -34,17 +34,11 @@ import reversion
from machines.models import Vlan
def make_port_related(port):
related_port = port.related
related_port.related = port
related_port.save()
def clean_port_related(port):
related_port = port.related_port
related_port.related = None
related_port.save()
class Stack(models.Model):
""" Un objet stack. Regrouppe des switchs en foreign key
, contient une id de stack, un switch id min et max dans
le stack"""
PRETTY_NAME = "Stack de switchs"
name = models.CharField(max_length=32, blank=True, null=True)
......@@ -57,15 +51,25 @@ class Stack(models.Model):
return " ".join([self.name, self.stack_id])
def save(self, *args, **kwargs):
self.clean()
if not self.name:
self.name = self.stack_id
super(Stack, self).save(*args, **kwargs)
def clean(self):
""" Verification que l'id_max < id_min"""
if self.member_id_max < self.member_id_min:
raise ValidationError({'member_id_max':"L'id maximale est inférieure à l'id minimale"})
class Switch(models.Model):
""" Definition d'un switch. Contient un nombre de ports (number),
un emplacement (location), un stack parent (optionnel, stack)
et un id de membre dans le stack (stack_member_id)
relié en onetoone à une interface
Pourquoi ne pas avoir fait hériter switch de interface ?
Principalement par méconnaissance de la puissance de cette façon de faire.
Ceci étant entendu, django crée en interne un onetoone, ce qui a un
effet identique avec ce que l'on fait ici"""
PRETTY_NAME = "Switch / Commutateur"
switch_interface = models.OneToOneField('machines.Interface', on_delete=models.CASCADE)
......@@ -82,6 +86,7 @@ class Switch(models.Model):
return str(self.location) + ' ' + str(self.switch_interface)
def clean(self):
""" Verifie que l'id stack est dans le bon range"""
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):
......@@ -90,6 +95,20 @@ class Switch(models.Model):
raise ValidationError({'stack_member_id': "L'id dans la stack ne peut être nul"})
class Port(models.Model):
""" Definition d'un port. Relié à un switch(foreign_key),
un port peut etre relié de manière exclusive à :
- une chambre (room)
- une machine (serveur etc) (machine_interface)
- un autre port (uplink) (related)
Champs supplémentaires :
- RADIUS (mode STRICT : connexion sur port uniquement si machine
d'un adhérent à jour de cotisation et que la chambre est également à jour de cotisation
mode COMMON : vérification uniquement du statut de la machine
mode NO : accepte toute demande venant du port et place sur le vlan normal
mode BLOQ : rejet de toute authentification
- vlan_force : override la politique générale de placement vlan, permet
de forcer un port sur un vlan particulier. S'additionne à la politique
RADIUS"""
PRETTY_NAME = "Port de switch"
STATES = (
('NO', 'NO'),
......@@ -110,7 +129,26 @@ class Port(models.Model):
class Meta:
unique_together = ('switch', 'port')
def make_port_related(self):
""" Synchronise le port distant sur self"""
related_port = self.related
related_port.related = self
related_port.save()
def clean_port_related(self):
""" Supprime la relation related sur self"""
related_port = self.related_port
related_port.related = None
related_port.save()
def clean(self):
""" Verifie que un seul de chambre, interface_parent et related_port est rempli.
Verifie que le related n'est pas le port lui-même....
Verifie que le related n'est pas déjà occupé par une machine ou une chambre. Si
ce n'est pas le cas, applique la relation related
Si un port related point vers self, on nettoie la relation
A priori pas d'autre solution que de faire ça à la main. A priori tout cela est dans
un bloc transaction, donc pas de problème de cohérence"""
if hasattr(self, 'switch'):
if self.port > self.switch.number:
raise ValidationError("Ce port ne peut exister, numero trop élevé")
......@@ -122,14 +160,15 @@ class Port(models.Model):
if self.related.machine_interface or self.related.room:
raise ValidationError("Le port relié est déjà occupé, veuillez le libérer avant de créer une relation")
else:
make_port_related(self)
self.make_port_related()
elif hasattr(self, 'related_port'):
clean_port_related(self)
self.clean_port_related()
def __str__(self):
return str(self.switch) + " - " + str(self.port)
class Room(models.Model):
""" Une chambre/local contenant une prise murale"""
PRETTY_NAME = "Chambre/ Prise murale"
name = models.CharField(max_length=255, unique=True)
......
......@@ -44,12 +44,14 @@ from preferences.models import AssoOption, GeneralOption
@login_required
@permission_required('cableur')
def index(request):
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')
""" Vue d'affichage de tous les swicthes"""
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').select_related('stack')
return render(request, 'topologie/index.html', {'switch_list': switch_list})
@login_required
@permission_required('cableur')
def history(request, object, id):
""" Vue générique pour afficher l'historique complet d'un objet"""