8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-12 12:56:26 +00:00

Merge branch 'new_upstream' into 'master'

New upstream

See merge request federez/re2o!104
This commit is contained in:
chirac 2018-03-27 01:29:13 +02:00
commit 708d897ec8
62 changed files with 1743 additions and 174 deletions

View file

@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %} {% endcomment %}
{% load acl %} {% load acl %}
<div class="table-responsive">
{% if facture_list.paginator %} {% if facture_list.paginator %}
{% include "pagination.html" with list=facture_list %} {% include "pagination.html" with list=facture_list %}
{% endif %} {% endif %}
@ -85,4 +85,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% if facture_list.paginator %} {% if facture_list.paginator %}
{% include "pagination.html" with list=facture_list %} {% include "pagination.html" with list=facture_list %}
{% endif %} {% endif %}
</div>

View file

@ -192,19 +192,21 @@ def post_auth(data):
mac = data.get('Calling-Station-Id', None) mac = data.get('Calling-Station-Id', None)
# Switch et bornes héritent de machine et peuvent avoir plusieurs interfaces filles
nas_machine = nas_instance.machine
# Si il s'agit d'un switch # Si il s'agit d'un switch
if hasattr(nas_instance, 'switch'): if hasattr(nas_machine, 'switch'):
port = data.get('NAS-Port-Id', data.get('NAS-Port', None)) port = data.get('NAS-Port-Id', data.get('NAS-Port', None))
#Pour les infrastructures possédant des switchs Juniper : #Pour les infrastructures possédant des switchs Juniper :
#On vérifie si le switch fait partie d'un stack Juniper #On vérifie si le switch fait partie d'un stack Juniper
instance_stack = nas_instance.switch.stack instance_stack = nas_machine.switch.stack
if instance_stack: if instance_stack:
# Si c'est le cas, on resélectionne le bon switch dans la stack # Si c'est le cas, on resélectionne le bon switch dans la stack
id_stack_member = port.split("-")[1].split('/')[0] id_stack_member = port.split("-")[1].split('/')[0]
nas_instance = Interface.objects.filter(switch__in=Switch.objects.filter(stack=instance_stack).filter(stack_member_id=id_stack_member)).select_related('domain__extension').first() nas_machine = Switch.objects.filter(stack=instance_stack).filter(stack_member_id=id_stack_member).prefetch_related('interface_set__domain__extension').first()
# On récupère le numéro du port sur l'output de freeradius. La ligne suivante fonctionne pour cisco, HP et Juniper # On récupère le numéro du port sur l'output de freeradius. La ligne suivante fonctionne pour cisco, HP et Juniper
port = port.split(".")[0].split('/')[-1][-2:] port = port.split(".")[0].split('/')[-1][-2:]
out = decide_vlan_and_register_switch(nas_instance, nas_type, port, mac) out = decide_vlan_and_register_switch(nas_machine, nas_type, port, mac)
sw_name, room, reason, vlan_id = out sw_name, room, reason, vlan_id = out
log_message = '(fil) %s -> %s [%s%s]' % \ log_message = '(fil) %s -> %s [%s%s]' % \
@ -271,7 +273,7 @@ def check_user_machine_and_register(nas_type, username, mac_address):
return (False, u"Machine inconnue", '') return (False, u"Machine inconnue", '')
def decide_vlan_and_register_switch(nas, nas_type, port_number, mac_address): def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, mac_address):
"""Fonction de placement vlan pour un switch en radius filaire auth par mac. """Fonction de placement vlan pour un switch en radius filaire auth par mac.
Plusieurs modes : Plusieurs modes :
- nas inconnu, port inconnu : on place sur le vlan par defaut VLAN_OK - nas inconnu, port inconnu : on place sur le vlan par defaut VLAN_OK
@ -296,12 +298,12 @@ def decide_vlan_and_register_switch(nas, nas_type, port_number, mac_address):
# Get port from switch and port number # Get port from switch and port number
extra_log = "" extra_log = ""
# Si le NAS est inconnu, on place sur le vlan defaut # Si le NAS est inconnu, on place sur le vlan defaut
if not nas: if not nas_machine:
return ('?', u'Chambre inconnue', u'Nas inconnu', VLAN_OK) return ('?', u'Chambre inconnue', u'Nas inconnu', VLAN_OK)
sw_name = str(nas) sw_name = str(nas_machine)
port = Port.objects.filter(switch=Switch.objects.filter(switch_interface=nas), port=port_number).first() port = Port.objects.filter(switch=Switch.objects.filter(machine_ptr=nas_machine), port=port_number).first()
#Si le port est inconnu, on place sur le vlan defaut #Si le port est inconnu, on place sur le vlan defaut
if not port: if not port:
return (sw_name, "Chambre inconnue", u'Port inconnu', VLAN_OK) return (sw_name, "Chambre inconnue", u'Port inconnu', VLAN_OK)

View file

@ -0,0 +1,82 @@
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% for droit,users in stats_list.items %}
<div class="panel panel-default">
<div class="panel-heading clearfix" data-parent="#accordion" data-toggle="collapse" data-target="#collapse{{droit.id}}">
<h2 class="panel-title pull-left">
<i class="fa fa-address-book"></i>
{{droit}}
<span class="badge">{{users.count}}</span>
</h2>
</div>
<div class="panel-collapse collapse" id="collapse{{droit.id}}">
<div class="panel-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Pseudo</th>
<th>Adhésion</th>
<th>Derniere connexion</th>
<th>Nombre d'actions</th>
<th>Date de la dernière action</th>
<th></th>
</tr>
</thead>
{% for utilisateur in users %}
<tr>
<td>{{ utilisateur.pseudo }}</td>
{% if utilisateur.is_adherent %}
<td><p class="text-success">Adhérent</p></td>
{% elif not utilisateur.end_adhesion %}
<td><p class="text-warning">On ne s'en souvient plus...</p></td>
{% else %}
<td><p class="text-danger">Plus depuis {{ utilisateur.end_adhesion }}</p></td>
{% endif %}
<td>{{ utilisateur.last_login }}</td>
<td>{{ utilisateur.num }}</td>
{% if not utilisateur.last %}
<td><p class="text-danger">Jamais</p></td>
{% else %}
<td><p class="text-success">{{utilisateur.last}}</p></td>
{% endif %}
<td>
<a href="{% url 'users:del-group' utilisateur.id droit.id %}">
<button type="button" class="btn btn-danger" aria-label="Left Align">
<span class="fa fa-user-times" aria-hidden="true"></span>
</button>
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
</div>
{% endfor %}

View file

@ -51,5 +51,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<i class="fa fa-users"></i> <i class="fa fa-users"></i>
Utilisateurs Utilisateurs
</a> </a>
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-droits" %}">
<i class="fa fa-balance-scale"></i>
Groupes de droit
</a>
{% acl_end %} {% acl_end %}
{% endblock %} {% endblock %}

View file

@ -0,0 +1,36 @@
{% extends "logs/sidebar.html" %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% block title %}Statistiques des droits{% endblock %}
{% block content %}
<h2>Statistiques des droits</h2>
{% include "logs/aff_stats_droits.html" with stats_list=stats_list %}
<br />
<br />
<br />
{% endblock %}

View file

@ -39,4 +39,5 @@ urlpatterns = [
url(r'^stats_models/$', views.stats_models, name='stats-models'), url(r'^stats_models/$', views.stats_models, name='stats-models'),
url(r'^stats_users/$', views.stats_users, name='stats-users'), url(r'^stats_users/$', views.stats_users, name='stats-users'),
url(r'^stats_actions/$', views.stats_actions, name='stats-actions'), url(r'^stats_actions/$', views.stats_actions, name='stats-actions'),
url(r'^stats_droits/$', views.stats_droits, name='stats-droits'),
] ]

View file

@ -42,11 +42,13 @@ from django.shortcuts import render, redirect
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.db.models import Count from django.db.models import Count, Max
from reversion.models import Revision from reversion.models import Revision
from reversion.models import Version, ContentType from reversion.models import Version, ContentType
from time import time
from users.models import ( from users.models import (
User, User,
ServiceUser, ServiceUser,
@ -446,3 +448,15 @@ def stats_actions(request):
}, },
} }
return render(request, 'logs/stats_users.html', {'stats_list': stats}) return render(request, 'logs/stats_users.html', {'stats_list': stats})
@login_required
@can_view_app('users')
def stats_droits(request):
"""Affiche la liste des droits et les users ayant chaque droit"""
depart=time()
stats_list={}
for droit in ListRight.objects.all().select_related('group_ptr'):
stats_list[droit]=droit.user_set.all().annotate(num=Count('revision'),last=Max('revision__date_created'))
return render(request, 'logs/stats_droits.html', {'stats_list': stats_list})

View file

@ -23,11 +23,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %} {% endcomment %}
{% load acl %} {% load acl %}
<div class="table-responsive">
{% if machines_list.paginator %} {% if machines_list.paginator %}
{% include "pagination.html" with list=machines_list %} {% include "pagination.html" with list=machines_list %}
{% endif %} {% endif %}
<table class="table" id="machines_table"> <table class="table" id="machines_table">
<colgroup> <colgroup>
<col> <col>
@ -175,6 +176,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</tbody> </tbody>
</table> </table>
<script> <script>
$("#machines_table").ready( function() { $("#machines_table").ready( function() {
var alias_div = [{% for machine in machines_list %}{% for interface in machine.interface_set.all %}{% if interface.domain.related_domain.all %}$("#collapseDomain_{{interface.id}}"), {% endif %}{% endfor %}{% endfor %}]; var alias_div = [{% for machine in machines_list %}{% for interface in machine.interface_set.all %}{% if interface.domain.related_domain.all %}$("#collapseDomain_{{interface.id}}"), {% endif %}{% endfor %}{% endfor %}];
@ -193,3 +195,4 @@ $("#machines_table").ready( function() {
{% if machines_list.paginator %} {% if machines_list.paginator %}
{% include "pagination.html" with list=machines_list %} {% include "pagination.html" with list=machines_list %}
{% endif %} {% endif %}
</div>

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-03-23 01:18
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0030_merge_20180320_1419'),
]
operations = [
migrations.AlterField(
model_name='generaloption',
name='email_from',
field=models.EmailField(default='www-data@example.com', max_length=254),
),
]

View file

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-03-24 19:22
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0070_auto_20180324_1906'),
('preferences', '0031_auto_20180323_0218'),
]
operations = [
migrations.AddField(
model_name='optionaluser',
name='shell_default',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.ListShell'),
),
]

View file

@ -89,6 +89,12 @@ class OptionalUser(PreferencesModel):
default=False, default=False,
help_text="Un nouvel utilisateur peut se créer son compte sur re2o" help_text="Un nouvel utilisateur peut se créer son compte sur re2o"
) )
shell_default = models.OneToOneField(
'users.ListShell',
on_delete=models.PROTECT,
blank=True,
null=True
)
class Meta: class Meta:
permissions = ( permissions = (

View file

@ -68,7 +68,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<tr> <tr>
<th>Auto inscription</th> <th>Auto inscription</th>
<td>{{ useroptions.self_adhesion }}</td> <td>{{ useroptions.self_adhesion }}</td>
</tr> <th>Shell par défaut des utilisateurs</th>
<td>{{ useroptions.shell_default }}</td>
</tr>
</table> </table>
<h4>Préférences machines</h4> <h4>Préférences machines</h4>
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'OptionalMachine' %}"> <a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'OptionalMachine' %}">

View file

@ -37,8 +37,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% for service in service_list %} {% for service in service_list %}
<div class="col-12"> <div class="col-12">
<div class="thumbnail"> <div class="thumbnail">
<img src="{% static service.image %}" alt="{{ service.name }}"> <a href="{{ service.url }}"><img src="{% static service.image %}" alt="{{ service.name }}"></a>
<div class="caption"> <div class="caption">
<h3>{{ service.name }}</h3> <h3>{{ service.name }}</h3>
<p>{{ service.description }}</p> <p>{{ service.description }}</p>
<p><a href="{{ service.url }}" class="btn btn-primary" role="button">Accéder</a></p> <p><a href="{{ service.url }}" class="btn btn-primary" role="button">Accéder</a></p>

View file

@ -122,6 +122,7 @@ MODEL_NAME = {
# topologie # topologie
'Stack' : topologie.models.Stack, 'Stack' : topologie.models.Stack,
'Switch' : topologie.models.Switch, 'Switch' : topologie.models.Switch,
'AccessPoint' : topologie.models.AccessPoint,
'ModelSwitch' : topologie.models.ModelSwitch, 'ModelSwitch' : topologie.models.ModelSwitch,
'ConstructorSwitch' : topologie.models.ConstructorSwitch, 'ConstructorSwitch' : topologie.models.ConstructorSwitch,
'Port' : topologie.models.Port, 'Port' : topologie.models.Port,
@ -133,6 +134,7 @@ MODEL_NAME = {
'ServiceUser' : users.models.ServiceUser, 'ServiceUser' : users.models.ServiceUser,
'School' : users.models.School, 'School' : users.models.School,
'ListRight' : users.models.ListRight, 'ListRight' : users.models.ListRight,
'ListShell' : users.models.ListShell,
'Ban' : users.models.Ban, 'Ban' : users.models.Ban,
'Whitelist' : users.models.Whitelist, 'Whitelist' : users.models.Whitelist,
} }

View file

@ -213,8 +213,8 @@ class SortTable:
'default': ['-date'] 'default': ['-date']
} }
TOPOLOGIE_INDEX = { TOPOLOGIE_INDEX = {
'switch_dns': ['switch_interface__domain__name'], 'switch_dns': ['interface__domain__name'],
'switch_ip': ['switch_interface__ipv4__ipv4'], 'switch_ip': ['interface__ipv4__ipv4'],
'switch_loc': ['location'], 'switch_loc': ['location'],
'switch_ports': ['number'], 'switch_ports': ['number'],
'switch_stack': ['stack__name'], 'switch_stack': ['stack__name'],
@ -233,6 +233,12 @@ class SortTable:
'room_name': ['name'], 'room_name': ['name'],
'default': ['name'] 'default': ['name']
} }
TOPOLOGIE_INDEX_BORNE = {
'ap_name': ['interface__domain__name'],
'ap_ip': ['interface__ipv4__ipv4'],
'ap_mac': ['interface__mac_address'],
'default': ['interface__domain__name']
}
TOPOLOGIE_INDEX_STACK = { TOPOLOGIE_INDEX_STACK = {
'stack_name': ['name'], 'stack_name': ['name'],
'stack_id': ['stack_id'], 'stack_id': ['stack_id'],

View file

@ -65,6 +65,7 @@ HISTORY_BIND = {
'school' : users.models.School, 'school' : users.models.School,
'listright' : users.models.ListRight, 'listright' : users.models.ListRight,
'serviceuser' : users.models.ServiceUser, 'serviceuser' : users.models.ServiceUser,
'shell' : users.models.ListShell,
}, },
'preferences' : { 'preferences' : {
'service' : preferences.models.Service, 'service' : preferences.models.Service,
@ -82,6 +83,7 @@ HISTORY_BIND = {
'stack' : topologie.models.Stack, 'stack' : topologie.models.Stack,
'model_switch' : topologie.models.ModelSwitch, 'model_switch' : topologie.models.ModelSwitch,
'constructor_switch' : topologie.models.ConstructorSwitch, 'constructor_switch' : topologie.models.ConstructorSwitch,
'ap' : topologie.models.AccessPoint,
}, },
'machines' : { 'machines' : {
'machine' : machines.models.Machine, 'machine' : machines.models.Machine,

View file

@ -260,7 +260,7 @@ def search_single_word(word, filters, user,
) | Q( ) | Q(
machine_interface__domain__name__icontains=word machine_interface__domain__name__icontains=word
) | Q( ) | Q(
related__switch__switch_interface__domain__name__icontains=word related__switch__domain__name__icontains=word
) | Q( ) | Q(
radius__icontains=word radius__icontains=word
) | Q( ) | Q(
@ -277,9 +277,9 @@ def search_single_word(word, filters, user,
# Switches # Switches
if '7' in aff and Switch.can_view_all(user): if '7' in aff and Switch.can_view_all(user):
filter_switches = Q( filter_switches = Q(
switch_interface__domain__name__icontains=word domain__name__icontains=word
) | Q( ) | Q(
switch_interface__ipv4__ipv4__icontains=word ipv4__ipv4__icontains=word
) | Q( ) | Q(
location__icontains=word location__icontains=word
) | Q( ) | Q(

View file

@ -45,7 +45,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<p><form method="post" action="{% url 'login' %}"> <p><form method="post" action="{% url 'login' %}">
{% csrf_token %} {% csrf_token %}
{% bootstrap_form form %} {% bootstrap_form form %}
{% bootstrap_button "Login" button_type="submit" icon="log-in" %} <button class="btn btn-success" type="submit"><span class="glyphicon glyphicon-log-in"></span> Login</button>
<input type="hidden" name="next" value="{{ next }}" /> <input type="hidden" name="next" value="{{ next }}" />
</form></p> </form></p>
<p><a class="btn btn-warning btn-sm" role="button" href="{% url 'users:reset-password' %}"> Mot de passe oublié ?</a></p> <p><a class="btn btn-warning btn-sm" role="button" href="{% url 'users:reset-password' %}"> Mot de passe oublié ?</a></p>

View file

@ -29,7 +29,15 @@ from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from reversion.admin import VersionAdmin from reversion.admin import VersionAdmin
from .models import Port, Room, Switch, Stack, ModelSwitch, ConstructorSwitch from .models import (
Port,
Room,
Switch,
Stack,
ModelSwitch,
ConstructorSwitch,
AccessPoint
)
class StackAdmin(VersionAdmin): class StackAdmin(VersionAdmin):
@ -47,6 +55,11 @@ class PortAdmin(VersionAdmin):
pass pass
class AccessPointAdmin(VersionAdmin):
"""Administration d'une borne"""
pass
class RoomAdmin(VersionAdmin): class RoomAdmin(VersionAdmin):
"""Administration d'un chambre""" """Administration d'un chambre"""
pass pass
@ -63,6 +76,7 @@ class ConstructorSwitchAdmin(VersionAdmin):
admin.site.register(Port, PortAdmin) admin.site.register(Port, PortAdmin)
admin.site.register(AccessPoint, AccessPointAdmin)
admin.site.register(Room, RoomAdmin) admin.site.register(Room, RoomAdmin)
admin.site.register(Switch, SwitchAdmin) admin.site.register(Switch, SwitchAdmin)
admin.site.register(Stack, StackAdmin) admin.site.register(Stack, StackAdmin)

View file

@ -33,9 +33,23 @@ NewSwitchForm)
from __future__ import unicode_literals from __future__ import unicode_literals
from machines.models import Interface from machines.models import Interface
from machines.forms import (
EditInterfaceForm,
EditMachineForm,
NewMachineForm
)
from django import forms from django import forms
from django.forms import ModelForm, Form from django.forms import ModelForm, Form
from .models import Port, Switch, Room, Stack, ModelSwitch, ConstructorSwitch from django.db.models import Prefetch
from .models import (
Port,
Switch,
Room,
Stack,
ModelSwitch,
ConstructorSwitch,
AccessPoint
)
class PortForm(ModelForm): class PortForm(ModelForm):
@ -67,10 +81,12 @@ class EditPortForm(ModelForm):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(EditPortForm, self).__init__(*args, prefix=prefix, **kwargs) super(EditPortForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['machine_interface'].queryset = Interface.objects.all()\ self.fields['machine_interface'].queryset = Interface.objects.all()\
.select_related('domain__extension') .select_related('domain__extension')
self.fields['related'].queryset = Port.objects.all()\ self.fields['related'].queryset = Port.objects.all()\
.select_related('switch__switch_interface__domain__extension')\ .prefetch_related(Prefetch(
.order_by('switch', 'port') 'switch__interface_set',
queryset=Interface.objects.select_related('ipv4__ip_type__extension').select_related('domain__extension')
))
class AddPortForm(ModelForm): class AddPortForm(ModelForm):
@ -84,10 +100,12 @@ class AddPortForm(ModelForm):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(AddPortForm, self).__init__(*args, prefix=prefix, **kwargs) super(AddPortForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['machine_interface'].queryset = Interface.objects.all()\ self.fields['machine_interface'].queryset = Interface.objects.all()\
.select_related('domain__extension') .select_related('domain__extension')
self.fields['related'].queryset = Port.objects.all()\ self.fields['related'].queryset = Port.objects.all()\
.select_related('switch__switch_interface__domain__extension')\ .prefetch_related(Prefetch(
.order_by('switch', 'port') 'switch__interface_set',
queryset=Interface.objects.select_related('ipv4__ip_type__extension').select_related('domain__extension')
))
class StackForm(ModelForm): class StackForm(ModelForm):
@ -102,32 +120,34 @@ class StackForm(ModelForm):
super(StackForm, self).__init__(*args, prefix=prefix, **kwargs) super(StackForm, self).__init__(*args, prefix=prefix, **kwargs)
class EditSwitchForm(ModelForm): class AddAccessPointForm(NewMachineForm):
"""Formulaire pour la création d'une borne
Relié directement au modèle borne"""
class Meta:
model = AccessPoint
fields = ['location', 'name']
class EditAccessPointForm(EditMachineForm):
"""Edition d'une borne. Edition complète"""
class Meta:
model = AccessPoint
fields = '__all__'
class EditSwitchForm(EditMachineForm):
"""Permet d'éditer un switch : nom et nombre de ports""" """Permet d'éditer un switch : nom et nombre de ports"""
class Meta: class Meta:
model = Switch model = Switch
fields = '__all__' fields = '__all__'
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(EditSwitchForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['switch_interface'].queryset = Interface.objects.all()\
.select_related('domain__extension')
self.fields['location'].label = 'Localisation'
self.fields['number'].label = 'Nombre de ports'
class NewSwitchForm(NewMachineForm):
class NewSwitchForm(ModelForm):
"""Permet de créer un switch : emplacement, paramètres machine, """Permet de créer un switch : emplacement, paramètres machine,
membre d'un stack (option), nombre de ports (number)""" membre d'un stack (option), nombre de ports (number)"""
class Meta(EditSwitchForm.Meta): class Meta(EditSwitchForm.Meta):
fields = ['location', 'number', 'details', 'stack', 'stack_member_id'] fields = ['name', 'location', 'number', 'stack', 'stack_member_id']
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(NewSwitchForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['location'].label = 'Localisation'
self.fields['number'].label = 'Nombre de ports'
class EditRoomForm(ModelForm): class EditRoomForm(ModelForm):
"""Permet d'éediter le nom et commentaire d'une prise murale""" """Permet d'éediter le nom et commentaire d'une prise murale"""

View file

@ -0,0 +1,47 @@
# ⁻*- 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 © 2018 Gabriel Detraz
#
# 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.
from django.core.management.base import BaseCommand, CommandError
from pymongo import MongoClient
from topologie.models import Borne
class Command(BaseCommand):
help = 'Ce script donne un nom aux bornes dans le controleur unifi.
A lancer sur le serveur en local se trouve le controleur'
def handle(self, *args, **options):
# Connexion mongodb
client = MongoClient("mongodb://localhost:27117")
db = client.ace
device = db['device']
bornes = Borne.objects.all()
def set_bornes_names(liste_bornes):
"""Met à jour les noms des bornes dans la bdd du controleur"""
for borne in liste_bornes:
if borne.ipv4 and borne.domain:
device.find_one_and_update({'ip': str(borne.ipv4)}, {'$set': {'name': str(borne.domain.name)}})
return
set_bornes_names(bornes)
self.stdout.write(self.style.SUCCESS('Mise à jour de la base de donnée unifi avec succès'))

View file

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-03-23 01:18
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('machines', '0076_auto_20180130_1623'),
('topologie', '0033_auto_20171231_1743'),
]
operations = [
migrations.CreateModel(
name='Borne',
fields=[
('interface_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='machines.Interface')),
('location', models.CharField(help_text="Détails sur la localisation de l'AP", max_length=255)),
],
options={
'permissions': (('view_borne', 'Peut voir une borne'),),
},
bases=('machines.interface',),
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-03-23 23:23
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('topologie', '0034_borne'),
]
operations = [
migrations.AlterField(
model_name='borne',
name='location',
field=models.CharField(blank=True, help_text="Détails sur la localisation de l'AP", max_length=255, null=True),
),
]

View file

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 19:53
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('topologie', '0035_auto_20180324_0023'),
]
def transfer_bornes(apps, schema_editor):
db_alias = schema_editor.connection.alias
machinetype = apps.get_model("machines", "MachineType")
borne = apps.get_model("topologie", "Borne")
interface = apps.get_model("machines", "Interface")
bornes_list = machinetype.objects.using(db_alias).filter(type__icontains='borne')
if bornes_list:
for inter in interface.objects.using(db_alias).filter(type=bornes_list.first()):
borne_object = borne()
borne_object.interface_ptr_id = inter.pk
borne_object.__dict__.update(inter.__dict__)
borne_object.save()
def untransfer_bornes(apps, schema_editor):
return
operations = [
migrations.RunPython(transfer_bornes, untransfer_bornes),
]

View file

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-03-25 00:27
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('machines', '0076_auto_20180130_1623'),
('topologie', '0036_transferborne'),
]
operations = [
migrations.CreateModel(
name='NewSwitch',
fields=[
('interface_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='machines.Interface')),
('location', models.CharField(max_length=255)),
('number', models.PositiveIntegerField()),
('stack_member_id', models.PositiveIntegerField(blank=True, null=True)),
('model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='topologie.ModelSwitch')),
('stack', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='topologie.Stack')),
],
bases=('machines.interface',),
),
migrations.AlterUniqueTogether(
name='newswitch',
unique_together=set([('stack', 'stack_member_id')]),
),
]

View file

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 19:53
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('topologie', '0037_auto_20180325_0127'),
]
def transfer_sw(apps, schema_editor):
db_alias = schema_editor.connection.alias
newswitch = apps.get_model("topologie", "NewSwitch")
switch = apps.get_model("topologie", "Switch")
interface = apps.get_model("machines", "Interface")
sw_list = switch.objects.using(db_alias).all()
for sw in sw_list:
new_sw = newswitch()
new_sw.location = sw.location
new_sw.number = sw.number
new_sw.details = sw.details
new_sw.stack = sw.stack
new_sw.stack_member_id = sw.stack_member_id
new_sw.model = sw.model
new_sw.interface_ptr_id = sw.switch_interface.pk
new_sw.__dict__.update(sw.switch_interface.__dict__)
new_sw.save()
def untransfer_sw(apps, schema_editor):
return
operations = [
migrations.RunPython(transfer_sw, untransfer_sw),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-03-25 00:52
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('topologie', '0038_transfersw'),
]
operations = [
migrations.AddField(
model_name='port',
name='new_switch',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ports', to='topologie.NewSwitch'),
),
]

View file

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 19:53
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('topologie', '0039_port_new_switch'),
]
def transfer_port(apps, schema_editor):
db_alias = schema_editor.connection.alias
port = apps.get_model("topologie", "Port")
switch = apps.get_model("topologie", "NewSwitch")
port_list = port.objects.using(db_alias).all()
for p in port_list:
p.new_switch = switch.objects.filter(interface_ptr=p.switch.switch_interface).first()
p.save()
def untransfer_port(apps, schema_editor):
return
operations = [
migrations.RunPython(transfer_port, untransfer_port),
]

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 19:53
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('topologie', '0040_transferports'),
]
operations = [
migrations.AlterUniqueTogether(
name='port',
unique_together=set([]),
),
migrations.RemoveField(
model_name='port',
name='switch',
),
migrations.RenameField('Port', 'new_switch', 'switch')
]

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 19:53
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('topologie', '0041_transferportsw'),
]
operations = [
migrations.DeleteModel(
name='Switch',
),
]

View file

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 19:53
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('topologie', '0042_transferswitch'),
]
operations = [
migrations.RenameModel(old_name='NewSwitch', new_name='Switch'),
]

View file

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-03-25 22:02
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('topologie', '0043_renamenewswitch'),
]
operations = [
migrations.RenameModel(
old_name='Borne',
new_name='AccessPoint',
),
migrations.AlterModelOptions(
name='accesspoint',
options={'permissions': (('view_ap', 'Peut voir une borne'),)},
),
migrations.AlterModelOptions(
name='switch',
options={'permissions': (('view_switch', 'Peut voir un objet switch'),)},
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-03-25 23:23
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('topologie', '0044_auto_20180326_0002'),
]
operations = [
migrations.AlterField(
model_name='port',
name='switch',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ports', to='topologie.Switch'),
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-03-25 23:29
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('topologie', '0045_auto_20180326_0123'),
]
operations = [
migrations.AlterUniqueTogether(
name='port',
unique_together=set([('switch', 'port')]),
),
]

View file

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-03-23 01:18
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('topologie', '0046_auto_20180326_0129'),
]
operations = [
migrations.CreateModel(
name='NewAccessPoint',
fields=[
('machine_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='machines.Machine')),
('location', models.CharField(help_text="Détails sur la localisation de l'AP", max_length=255, null=True, blank=True)),
],
bases=('machines.machine',),
),
]

View file

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-03-23 01:18
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('topologie', '0047_ap_machine'),
]
def transfer_ap(apps, schema_editor):
db_alias = schema_editor.connection.alias
ap = apps.get_model("topologie", "AccessPoint")
new_ap = apps.get_model("topologie", "NewAccessPoint")
ap_list = ap.objects.using(db_alias).all()
for borne in ap_list:
new_borne = new_ap()
new_borne.machine_ptr_id = borne.machine.pk
new_borne.__dict__.update(borne.machine.__dict__)
new_borne.location = borne.location
new_borne.save()
def untransfer_ap(apps, schema_editor):
return
operations = [
migrations.RunPython(transfer_ap, untransfer_ap),
migrations.DeleteModel(
name='AccessPoint',
),
migrations.RenameModel(
old_name='NewAccessPoint',
new_name='AccessPoint',
),
]

View file

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-03-23 01:18
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('topologie', '0048_ap_machine'),
]
operations = [
migrations.CreateModel(
name='NewSw',
fields=[
('machine_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='machines.Machine')),
('location', models.CharField(max_length=255)),
('number', models.PositiveIntegerField()),
('stack_member_id', models.PositiveIntegerField(blank=True, null=True)),
('model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='topologie.ModelSwitch')),
('stack', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='topologie.Stack')),
],
bases=('machines.machine',),
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-03-25 00:52
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('topologie', '0049_switchs_machine'),
]
operations = [
migrations.AddField(
model_name='port',
name='new_sw',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ports', to='topologie.NewSw'),
),
]

View file

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-03-23 01:18
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('topologie', '0050_port_new_switch'),
]
def transfer_sw(apps, schema_editor):
db_alias = schema_editor.connection.alias
newswitch = apps.get_model("topologie", "NewSw")
switch = apps.get_model("topologie", "Switch")
machine = apps.get_model("machines", "Machine")
sw_list = switch.objects.using(db_alias).all()
for sw in sw_list:
new_sw = newswitch()
new_sw.location = sw.location
new_sw.number = sw.number
new_sw.details = sw.details
new_sw.stack = sw.stack
new_sw.stack_member_id = sw.stack_member_id
new_sw.model = sw.model
new_sw.machine_ptr_id = sw.interface_ptr.machine.pk
new_sw.__dict__.update(sw.interface_ptr.machine.__dict__)
new_sw.save()
def untransfer_sw(apps, schema_editor):
return
operations = [
migrations.RunPython(transfer_sw, untransfer_sw),
]

View file

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 19:53
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('topologie', '0051_switchs_machine'),
]
def transfer_port(apps, schema_editor):
db_alias = schema_editor.connection.alias
port = apps.get_model("topologie", "Port")
switch = apps.get_model("topologie", "NewSw")
port_list = port.objects.using(db_alias).all()
for p in port_list:
p.new_sw = switch.objects.filter(machine_ptr=p.switch.machine).first()
p.save()
def untransfer_port(apps, schema_editor):
return
operations = [
migrations.AlterUniqueTogether(
name='port',
unique_together=set([]),
),
migrations.RunPython(transfer_port, untransfer_port),
migrations.RemoveField(
model_name='port',
name='switch',
),
migrations.RenameField('Port', 'new_sw', 'switch')
]

View file

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-03-23 01:18
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('topologie', '0052_transferports'),
]
operations = [
migrations.DeleteModel(
name='Switch',
),
migrations.RenameModel(
old_name='NewSw',
new_name='Switch',
),
]

View file

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-03-26 15:42
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('topologie', '0053_finalsw'),
]
operations = [
migrations.AlterModelOptions(
name='accesspoint',
options={'permissions': (('view_ap', 'Peut voir une borne'),)},
),
migrations.AlterModelOptions(
name='switch',
options={'permissions': (('view_switch', 'Peut voir un objet switch'),)},
),
migrations.AlterField(
model_name='port',
name='switch',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ports', to='topologie.Switch'),
preserve_default=False,
),
migrations.AlterUniqueTogether(
name='port',
unique_together=set([('switch', 'port')]),
),
migrations.AlterUniqueTogether(
name='switch',
unique_together=set([('stack', 'stack_member_id')]),
),
]

View file

@ -47,6 +47,7 @@ from django.db import IntegrityError
from django.db import transaction from django.db import transaction
from reversion import revisions as reversion from reversion import revisions as reversion
from machines.models import Machine, Interface
class Stack(models.Model): class Stack(models.Model):
"""Un objet stack. Regrouppe des switchs en foreign key """Un objet stack. Regrouppe des switchs en foreign key
@ -108,7 +109,54 @@ class Stack(models.Model):
inférieure à l'id minimale"}) inférieure à l'id minimale"})
class Switch(models.Model): class AccessPoint(Machine):
"""Define a wireless AP. Inherit from machines.interfaces
Definition pour une borne wifi , hérite de machines.interfaces
"""
PRETTY_NAME = "Borne WiFi"
location = models.CharField(
max_length=255,
help_text="Détails sur la localisation de l'AP",
blank=True,
null=True
)
class Meta:
permissions = (
("view_ap", "Peut voir une borne"),
)
def get_instance(ap_id, *args, **kwargs):
return AccessPoint.objects.get(pk=ap_id)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('topologie.add_ap') , u"Vous n'avez pas le droit\
de créer une borne"
def can_edit(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.change_ap'):
return False, u"Vous n'avez pas le droit d'éditer des bornes"
return True, None
def can_delete(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.delete_ap'):
return False, u"Vous n'avez pas le droit de supprimer une borne"
return True, None
def can_view_all(user_request, *args, **kwargs):
if not user_request.has_perm('topologie.view_ap'):
return False, u"Vous n'avez pas le droit de voir les bornes"
return True, None
def can_view(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.view_ap'):
return False, u"Vous n'avez pas le droit de voir les bornes"
return True, None
class Switch(Machine):
""" Definition d'un switch. Contient un nombre de ports (number), """ Definition d'un switch. Contient un nombre de ports (number),
un emplacement (location), un stack parent (optionnel, stack) un emplacement (location), un stack parent (optionnel, stack)
et un id de membre dans le stack (stack_member_id) et un id de membre dans le stack (stack_member_id)
@ -122,13 +170,9 @@ class Switch(models.Model):
id_max de la stack parente""" id_max de la stack parente"""
PRETTY_NAME = "Switch / Commutateur" PRETTY_NAME = "Switch / Commutateur"
switch_interface = models.OneToOneField(
'machines.Interface',
on_delete=models.CASCADE
)
location = models.CharField(max_length=255) location = models.CharField(max_length=255)
number = models.PositiveIntegerField() number = models.PositiveIntegerField()
details = models.CharField(max_length=255, blank=True)
stack = models.ForeignKey( stack = models.ForeignKey(
'topologie.Stack', 'topologie.Stack',
blank=True, blank=True,
@ -176,11 +220,10 @@ class Switch(models.Model):
return False, u"Vous n'avez pas le droit de voir les switch" return False, u"Vous n'avez pas le droit de voir les switch"
return True, None return True, None
def __str__(self):
return self.location + ' ' + str(self.switch_interface)
def clean(self): def clean(self):
""" Verifie que l'id stack est dans le bon range""" """ Verifie que l'id stack est dans le bon range
Appelle également le clean de la classe parente"""
super(Switch, self).clean()
if self.stack is not None: if self.stack is not None:
if self.stack_member_id is not None: if self.stack_member_id is not None:
if (self.stack_member_id > self.stack.member_id_max) or\ if (self.stack_member_id > self.stack.member_id_max) or\
@ -192,6 +235,7 @@ class Switch(models.Model):
else: else:
raise ValidationError({'stack_member_id': "L'id dans la stack\ raise ValidationError({'stack_member_id': "L'id dans la stack\
ne peut être nul"}) ne peut être nul"})
def create_ports(self, begin, end): def create_ports(self, begin, end):
""" Crée les ports de begin à end si les valeurs données sont cohérentes. """ """ Crée les ports de begin à end si les valeurs données sont cohérentes. """
@ -219,6 +263,9 @@ class Switch(models.Model):
except IntegrityError: except IntegrityError:
ValidationError("Création d'un port existant.") ValidationError("Création d'un port existant.")
def __str__(self):
return str(self.interface_set.first())
class ModelSwitch(models.Model): class ModelSwitch(models.Model):
"""Un modèle (au sens constructeur) de switch""" """Un modèle (au sens constructeur) de switch"""
@ -372,11 +419,11 @@ class Port(models.Model):
def get_instance(port_id, *args, **kwargs): def get_instance(port_id, *args, **kwargs):
return Port.objects\ return Port.objects\
.select_related('switch__switch_interface__domain__extension')\
.select_related('machine_interface__domain__extension')\ .select_related('machine_interface__domain__extension')\
.select_related('machine_interface__switch')\ .select_related('machine_interface__machine__switch')\
.select_related('room')\ .select_related('room')\
.select_related('related')\ .select_related('related')\
.prefetch_related('switch__interface_set__domain__extension')\
.get(pk=port_id) .get(pk=port_id)
def can_create(user_request, *args, **kwargs): def can_create(user_request, *args, **kwargs):

View file

@ -0,0 +1,74 @@
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<div class="table-responsive">
{% if ap_list.paginator %}
{% include "pagination.html" with list=ap_list %}
{% endif %}
<table class="table table-striped">
<thead>
<tr>
<th>{% include "buttons/sort.html" with prefix='ap' col='name' text='Borne' %}</th>
<th>{% include "buttons/sort.html" with prefix='ap' col='mac' text='Addresse mac' %}</th>
<th>{% include "buttons/sort.html" with prefix='ap' col='ip' text='Ipv4' %}</th>
<th>Commentaire</th>
<th>Localisation</th>
<th></th>
</tr>
</thead>
{% for ap in ap_list %}
<tr>
<td>{{ap.interface_set.first}}</td>
<td>{{ap.interface_set.first.mac_address}}</td>
<td>{{ap.interface_set.first.ipv4}}</td>
<td>{{ap.interface_set.first.details}}</td>
<td>{{ap.location}}</td>
<td class="text-right">
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'ap' ap.pk %}">
<i class="fa fa-history"></i>
</a>
{% can_edit ap %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-ap' ap.id %}">
<i class="fa fa-edit"></i>
</a>
{% acl_end %}
{% can_delete ap %}
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'machines:del-machine' ap.id %}">
<i class="fa fa-trash"></i>
</a>
{% acl_end %}
</td>
</tr>
{% endfor %}
</table>
{% if ap_list.paginator %}
{% include "pagination.html" with list=ap_list %}
{% endif %}
</div>

View file

@ -46,23 +46,23 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<tr> <tr>
<td> <td>
<a title="Configuer" href="{% url 'topologie:index-port' switch.pk %}"> <a title="Configuer" href="{% url 'topologie:index-port' switch.pk %}">
{{switch.switch_interface}} {{switch}}
</a> </a>
</td> </td>
<td>{{switch.switch_interface.ipv4}}</td> <td>{{switch.interface_set.first.ipv4}}</td>
<td>{{switch.location}}</td> <td>{{switch.location}}</td>
<td>{{switch.number}}</td> <td>{{switch.number}}</td>
<td>{{switch.stack.name}}</td> <td>{{switch.stack.name}}</td>
<td>{{switch.stack_member_id}}</td> <td>{{switch.stack_member_id}}</td>
<td>{{switch.model}}</td> <td>{{switch.model}}</td>
<td>{{switch.details}}</td> <td>{{switch.interface_set.first.details}}</td>
<td class="text-right"> <td class="text-right">
{% include 'buttons/history.html' with href='topologie:history' name='switch' id=switch.pk%} {% include 'buttons/history.html' with href='topologie:history' name='switch' id=switch.pk%}
{% can_edit switch %} {% can_edit switch %}
{% include 'buttons/edit.html' with href='topologie:edit-switch' id=switch.pk %} {% include 'buttons/edit.html' with href='topologie:edit-switch' id=switch.pk %}
{% acl_end %} {% acl_end %}
{% can_delete switch %} {% can_delete switch %}
{% include 'buttons/suppr.html' with href='machines:del-interface' id=switch.switch_interface.id %} {% include 'buttons/suppr.html' with href='machines:del-machine' id=switch.id %}
{% acl_end %} {% acl_end %}
{% can_create Port %} {% can_create Port %}
{% include 'buttons/add.html' with href='topologie:create-ports' id=switch.pk desc='Création de ports'%} {% include 'buttons/add.html' with href='topologie:create-ports' id=switch.pk desc='Création de ports'%}

View file

@ -0,0 +1,41 @@
{% extends "topologie/sidebar.html" %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% block title %}Bornes WiFi{% endblock %}
{% block content %}
<h2>Points d'accès WiFi</h2>
{% can_create AccessPoint %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:new-ap' %}"><i class="fa fa-plus"></i> Ajouter une borne</a>
<hr>
{% acl_end %}
{% include "topologie/aff_ap.html" with ap_list=ap_list %}
<br />
<br />
<br />
{% endblock %}

View file

@ -26,12 +26,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% block sidebar %} {% block sidebar %}
<a class="list-group-item list-group-item-info" href="{% url "topologie:index-room" %}"> <a class="list-group-item list-group-item-info" href="{% url "topologie:index-room" %}">
<i class="fa fa-list-ul"></i> <i class="fa fa-home"></i>
Chambres Chambres et locaux
</a> </a>
<a class="list-group-item list-group-item-info" href="{% url "topologie:index" %}"> <a class="list-group-item list-group-item-info" href="{% url "topologie:index" %}">
<i class="fa fa-list-ul"></i> <i class="fa fa-microchip"></i>
Switchs Switchs
</a>
<a class="list-group-item list-group-item-info" href="{% url "topologie:index-ap" %}">
<i class="fa fa-wifi"></i>
Bornes WiFi
</a> </a>
<a class="list-group-item list-group-item-info" href="{% url "topologie:index-stack" %}"> <a class="list-group-item list-group-item-info" href="{% url "topologie:index-stack" %}">
<i class="fa fa-list-ul"></i> <i class="fa fa-list-ul"></i>

View file

@ -32,44 +32,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% if topoform %} {% if topoform %}
{% bootstrap_form_errors topoform %} {% bootstrap_form_errors topoform %}
{% endif %} {% endif %}
{% if machineform %}
{% bootstrap_form_errors machineform %}
{% endif %}
{% if interfaceform %}
{% bootstrap_form_errors interfaceform %}
{% endif %}
{% if domainform %}
{% bootstrap_form_errors domainform %}
{% endif %}
{% if id_switch %}
<a class="btn btn-primary" href="{% url "topologie:index-port" id_switch %}" role="button">{% bootstrap_icon "list" %} Aller à la liste des ports</a> <a class="btn btn-primary" href="{% url "topologie:index-port" id_switch %}" role="button">{% bootstrap_icon "list" %} Aller à la liste des ports</a>
{% endif %}
<form class="form" method="post"> <form class="form" method="post">
{% csrf_token %} {% csrf_token %}
{% if topoform %} {% if topoform %}
<h3>Réglage spécifiques du switch</h3> <h3>Réglage spécifiques du switch</h3>
{% massive_bootstrap_form topoform 'switch_interface' %} {% massive_bootstrap_form topoform 'switch_interface' %}
{% endif %} {% endif %}
{% if machineform %} {% bootstrap_button "Créer" button_type="submit" icon="ok" %}
<h3>Réglages généraux de la machine associée au switch</h3>
{% massive_bootstrap_form machineform 'user' %}
{% endif %}
{% if interfaceform %}
<h3>Réglages généraux de l'interface associée au switch</h3>
{% if i_mbf_param %}
{% massive_bootstrap_form interfaceform 'ipv4,machine' mbf_param=i_mbf_param %}
{% else %}
{% massive_bootstrap_form interfaceform 'ipv4,machine' %}
{% endif %}
{% endif %}
{% if domainform %}
<h3>Nom de la machine</h3>
{% bootstrap_form domainform %}
{% endif %}
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="ok" %}
</form> </form>
<br /> <br />
<br /> <br />

View file

@ -0,0 +1,63 @@
{% extends "topologie/sidebar.html" %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load massive_bootstrap_form %}
{% block title %}Création et modification d'un objet topologie{% endblock %}
{% block content %}
{% if topoform %}
{% bootstrap_form_errors topoform %}
{% endif %}
{% if machineform %}
{% bootstrap_form_errors machineform %}
{% endif %}
{% if domainform %}
{% bootstrap_form_errors domainform %}
{% endif %}
<form class="form" method="post">
{% csrf_token %}
{% if topoform %}
<h3>Réglage spécifiques du {{ device }}</h3>
{% massive_bootstrap_form topoform 'ipv4,machine' mbf_param=i_mbf_param%}
{% endif %}
{% if machineform %}
<h3>Réglages généraux de la machine associée au {{ device }}</h3>
{% massive_bootstrap_form machineform 'user' %}
{% endif %}
{% if domainform %}
<h3>Nom de la machine</h3>
{% bootstrap_form domainform %}
{% endif %}
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="ok" %}
</form>
<br />
<br />
<br />
{% endblock %}

View file

@ -35,7 +35,11 @@ from . import views
urlpatterns = [ urlpatterns = [
url(r'^$', views.index, name='index'), url(r'^$', views.index, name='index'),
url(r'^new_switch/$', views.new_switch, name='new-switch'), url(r'^index_ap/$', views.index_ap, name='index-ap'),
url(r'^new_ap/$', views.new_ap, name='new-ap'),
url(r'^edit_ap/(?P<ap_id>[0-9]+)$',
views.edit_ap,
name='edit-ap'),
url(r'^create_ports/(?P<switch_id>[0-9]+)$', url(r'^create_ports/(?P<switch_id>[0-9]+)$',
views.create_ports, views.create_ports,
name='create-ports'), name='create-ports'),
@ -43,6 +47,7 @@ urlpatterns = [
url(r'^new_room/$', views.new_room, name='new-room'), url(r'^new_room/$', views.new_room, name='new-room'),
url(r'^edit_room/(?P<room_id>[0-9]+)$', views.edit_room, name='edit-room'), url(r'^edit_room/(?P<room_id>[0-9]+)$', views.edit_room, name='edit-room'),
url(r'^del_room/(?P<room_id>[0-9]+)$', views.del_room, name='del-room'), url(r'^del_room/(?P<room_id>[0-9]+)$', views.del_room, name='del-room'),
url(r'^new_switch/$', views.new_switch, name='new-switch'),
url(r'^switch/(?P<switch_id>[0-9]+)$', url(r'^switch/(?P<switch_id>[0-9]+)$',
views.index_port, views.index_port,
name='index-port'), name='index-port'),

View file

@ -41,7 +41,7 @@ from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.db import IntegrityError from django.db import IntegrityError
from django.db import transaction from django.db import transaction
from django.db.models import ProtectedError from django.db.models import ProtectedError, Prefetch
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from reversion import revisions as reversion from reversion import revisions as reversion
@ -53,7 +53,8 @@ from topologie.models import (
Room, Room,
Stack, Stack,
ModelSwitch, ModelSwitch,
ConstructorSwitch ConstructorSwitch,
AccessPoint
) )
from topologie.forms import EditPortForm, NewSwitchForm, EditSwitchForm from topologie.forms import EditPortForm, NewSwitchForm, EditSwitchForm
from topologie.forms import ( from topologie.forms import (
@ -62,7 +63,9 @@ from topologie.forms import (
StackForm, StackForm,
EditModelSwitchForm, EditModelSwitchForm,
EditConstructorSwitchForm, EditConstructorSwitchForm,
CreatePortsForm CreatePortsForm,
AddAccessPointForm,
EditAccessPointForm
) )
from users.views import form from users.views import form
from re2o.utils import SortTable from re2o.utils import SortTable
@ -81,6 +84,7 @@ from machines.forms import (
AddInterfaceForm AddInterfaceForm
) )
from machines.views import generate_ipv4_mbf_param from machines.views import generate_ipv4_mbf_param
from machines.models import Interface
from preferences.models import AssoOption, GeneralOption from preferences.models import AssoOption, GeneralOption
@ -89,9 +93,10 @@ from preferences.models import AssoOption, GeneralOption
def index(request): def index(request):
""" Vue d'affichage de tous les swicthes""" """ Vue d'affichage de tous les swicthes"""
switch_list = Switch.objects\ switch_list = Switch.objects\
.select_related('switch_interface__domain__extension')\ .prefetch_related(Prefetch(
.select_related('switch_interface__ipv4')\ 'interface_set',
.select_related('switch_interface__domain')\ queryset=Interface.objects.select_related('ipv4__ip_type__extension').select_related('domain__extension')
))\
.select_related('stack') .select_related('stack')
switch_list = SortTable.sort( switch_list = SortTable.sort(
switch_list, switch_list,
@ -124,9 +129,11 @@ def index_port(request, switch, switch_id):
.select_related('room')\ .select_related('room')\
.select_related('machine_interface__domain__extension')\ .select_related('machine_interface__domain__extension')\
.select_related('machine_interface__machine__user')\ .select_related('machine_interface__machine__user')\
.select_related( .select_related('related__switch')\
'related__switch__switch_interface__domain__extension' .prefetch_related(Prefetch(
)\ 'related__switch__interface_set',
queryset=Interface.objects.select_related('domain__extension')
))\
.select_related('switch') .select_related('switch')
port_list = SortTable.sort( port_list = SortTable.sort(
port_list, port_list,
@ -168,12 +175,43 @@ def index_room(request):
}) })
@login_required
@can_view_all(AccessPoint)
def index_ap(request):
""" Affichage de l'ensemble des bornes"""
ap_list = AccessPoint.objects\
.prefetch_related(Prefetch(
'interface_set',
queryset=Interface.objects.select_related('ipv4__ip_type__extension').select_related('domain__extension')
))
ap_list = SortTable.sort(
ap_list,
request.GET.get('col'),
request.GET.get('order'),
SortTable.TOPOLOGIE_INDEX_BORNE
)
pagination_number = GeneralOption.get_cached_value('pagination_number')
paginator = Paginator(ap_list, pagination_number)
page = request.GET.get('page')
try:
ap_list = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
ap_list = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
ap_list = paginator.page(paginator.num_pages)
return render(request, 'topologie/index_ap.html', {
'ap_list': ap_list
})
@login_required @login_required
@can_view_all(Stack) @can_view_all(Stack)
def index_stack(request): def index_stack(request):
"""Affichage de la liste des stacks (affiche l'ensemble des switches)""" """Affichage de la liste des stacks (affiche l'ensemble des switches)"""
stack_list = Stack.objects\ stack_list = Stack.objects\
.prefetch_related('switch_set__switch_interface__domain__extension') .prefetch_related('switch_set__domain__extension')
stack_list = SortTable.sort( stack_list = SortTable.sort(
stack_list, stack_list,
request.GET.get('col'), request.GET.get('col'),
@ -351,58 +389,53 @@ def new_switch(request):
""" Creation d'un switch. Cree en meme temps l'interface et la machine """ Creation d'un switch. Cree en meme temps l'interface et la machine
associée. Vue complexe. Appelle successivement les 4 models forms associée. Vue complexe. Appelle successivement les 4 models forms
adaptés : machine, interface, domain et switch""" adaptés : machine, interface, domain et switch"""
switch = NewSwitchForm(request.POST or None) switch = NewSwitchForm(
machine = NewMachineForm(
request.POST or None, request.POST or None,
user=request.user user=request.user
) )
interface = AddInterfaceForm( interface = AddInterfaceForm(
request.POST or None, request.POST or None,
user=request.user user=request.user
) )
domain = DomainForm( domain = DomainForm(
request.POST or None, request.POST or None,
) )
if switch.is_valid() and machine.is_valid() and interface.is_valid(): if switch.is_valid() and interface.is_valid():
user = AssoOption.get_cached_value('utilisateur_asso') user = AssoOption.get_cached_value('utilisateur_asso')
if not user: if not user:
messages.error(request, "L'user association n'existe pas encore,\ messages.error(request, "L'user association n'existe pas encore,\
veuillez le créer ou le linker dans preferences") veuillez le créer ou le linker dans preferences")
return redirect(reverse('topologie:index')) return redirect(reverse('topologie:index'))
new_machine = machine.save(commit=False) new_switch = switch.save(commit=False)
new_machine.user = user new_switch.user = user
new_interface = interface.save(commit=False) new_interface_instance = interface.save(commit=False)
new_switch_instance = switch.save(commit=False) domain.instance.interface_parent = new_interface_instance
new_domain_instance = domain.save(commit=False) if domain.is_valid():
with transaction.atomic(), reversion.create_revision(): new_domain_instance = domain.save(commit=False)
new_machine.save() with transaction.atomic(), reversion.create_revision():
reversion.set_user(request.user) new_switch.save()
reversion.set_comment("Création") reversion.set_user(request.user)
new_interface.machine = new_machine reversion.set_comment("Création")
with transaction.atomic(), reversion.create_revision(): new_interface_instance.machine = new_switch
new_interface.save() with transaction.atomic(), reversion.create_revision():
reversion.set_user(request.user) new_interface_instance.save()
reversion.set_comment("Création") reversion.set_user(request.user)
new_domain_instance.interface_parent = new_interface reversion.set_comment("Création")
with transaction.atomic(), reversion.create_revision(): new_domain_instance.interface_parent = new_interface_instance
new_domain_instance.save() with transaction.atomic(), reversion.create_revision():
reversion.set_user(request.user) new_domain_instance.save()
reversion.set_comment("Création") reversion.set_user(request.user)
new_switch_instance.switch_interface = new_interface reversion.set_comment("Création")
with transaction.atomic(), reversion.create_revision(): messages.success(request, "Le switch a été créé")
new_switch_instance.save() return redirect(reverse('topologie:index'))
reversion.set_user(request.user) i_mbf_param = generate_ipv4_mbf_param(interface, False)
reversion.set_comment("Création")
messages.success(request, "Le switch a été créé")
return redirect(reverse('topologie:index'))
i_mbf_param = generate_ipv4_mbf_param( interface, False)
return form({ return form({
'topoform': switch, 'topoform': interface,
'machineform': machine, 'machineform': switch,
'interfaceform': interface,
'domainform': domain, 'domainform': domain,
'i_mbf_param': i_mbf_param 'i_mbf_param': i_mbf_param,
}, 'topologie/switch.html', request) 'device' : 'switch',
}, 'topologie/topo_more.html', request)
@login_required @login_required
@ -440,7 +473,6 @@ def create_ports(request, switch_id):
'topologie:index-port', 'topologie:index-port',
kwargs={'switch_id':switch_id} kwargs={'switch_id':switch_id}
)) ))
return form({'id_switch': switch_id, 'topoform': port_form}, 'topologie/switch.html', request) return form({'id_switch': switch_id, 'topoform': port_form}, 'topologie/switch.html', request)
@ -450,37 +482,34 @@ def edit_switch(request, switch, switch_id):
""" Edition d'un switch. Permet de chambre nombre de ports, """ Edition d'un switch. Permet de chambre nombre de ports,
place dans le stack, interface et machine associée""" place dans le stack, interface et machine associée"""
switch_form = EditSwitchForm(request.POST or None, instance=switch) switch_form = EditSwitchForm(
machine_form = EditMachineForm(
request.POST or None, request.POST or None,
instance=switch.switch_interface.machine, instance=switch,
user=request.user user=request.user
) )
interface_form = EditInterfaceForm( interface_form = EditInterfaceForm(
request.POST or None, request.POST or None,
instance=switch.switch_interface, instance=switch.interface_set.first(),
user=request.user user=request.user
) )
domain_form = DomainForm( domain_form = DomainForm(
request.POST or None, request.POST or None,
instance=switch.switch_interface.domain instance=switch.interface_set.first().domain
) )
if switch_form.is_valid() and machine_form.is_valid()\ if switch_form.is_valid() and interface_form.is_valid():
and interface_form.is_valid(): new_switch = switch_form.save(commit=False)
new_interface = interface_form.save(commit=False) new_interface_instance = interface_form.save(commit=False)
new_machine = machine_form.save(commit=False)
new_switch_instance = switch_form.save(commit=False)
new_domain = domain_form.save(commit=False) new_domain = domain_form.save(commit=False)
with transaction.atomic(), reversion.create_revision(): with transaction.atomic(), reversion.create_revision():
new_machine.save() new_switch.save()
reversion.set_user(request.user) reversion.set_user(request.user)
reversion.set_comment( reversion.set_comment(
"Champs modifié(s) : %s" % ', '.join( "Champs modifié(s) : %s" % ', '.join(
field for field in machine_form.changed_data field for field in switch_form.changed_data
) )
) )
with transaction.atomic(), reversion.create_revision(): with transaction.atomic(), reversion.create_revision():
new_interface.save() new_interface_instance.save()
reversion.set_user(request.user) reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join( reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in interface_form.changed_data) field for field in interface_form.changed_data)
@ -491,25 +520,134 @@ def edit_switch(request, switch, switch_id):
reversion.set_comment("Champs modifié(s) : %s" % ', '.join( reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in domain_form.changed_data) field for field in domain_form.changed_data)
) )
with transaction.atomic(), reversion.create_revision():
new_switch_instance.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in switch_form.changed_data)
)
messages.success(request, "Le switch a bien été modifié") messages.success(request, "Le switch a bien été modifié")
return redirect(reverse('topologie:index')) return redirect(reverse('topologie:index'))
i_mbf_param = generate_ipv4_mbf_param( interface_form, False ) i_mbf_param = generate_ipv4_mbf_param(interface_form, False )
return form({ return form({
'id_switch': switch_id, 'id_switch': switch_id,
'topoform': switch_form, 'topoform': interface_form,
'machineform': machine_form, 'machineform': switch_form,
'interfaceform': interface_form,
'domainform': domain_form, 'domainform': domain_form,
'i_mbf_param': i_mbf_param 'i_mbf_param': i_mbf_param,
}, 'topologie/switch.html', request) 'device' : 'switch',
}, 'topologie/topo_more.html', request)
@login_required
@can_create(AccessPoint)
def new_ap(request):
""" Creation d'une ap. Cree en meme temps l'interface et la machine
associée. Vue complexe. Appelle successivement les 3 models forms
adaptés : machine, interface, domain et switch"""
ap = AddAccessPointForm(
request.POST or None,
user=request.user
)
interface = AddInterfaceForm(
request.POST or None,
user=request.user
)
domain = DomainForm(
request.POST or None,
)
if ap.is_valid() and interface.is_valid():
user = AssoOption.get_cached_value('utilisateur_asso')
if not user:
messages.error(request, "L'user association n'existe pas encore,\
veuillez le créer ou le linker dans preferences")
return redirect(reverse('topologie:index'))
new_ap = ap.save(commit=False)
new_ap.user = user
new_interface = interface.save(commit=False)
domain.instance.interface_parent = new_interface
if domain.is_valid():
new_domain_instance = domain.save(commit=False)
with transaction.atomic(), reversion.create_revision():
new_ap.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
new_interface.machine = new_ap
with transaction.atomic(), reversion.create_revision():
new_interface.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
new_domain_instance.interface_parent = new_interface
with transaction.atomic(), reversion.create_revision():
new_domain_instance.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "La borne a été créé")
return redirect(reverse('topologie:index-ap'))
i_mbf_param = generate_ipv4_mbf_param(interface, False)
return form({
'topoform': interface,
'machineform': ap,
'domainform': domain,
'i_mbf_param': i_mbf_param,
'device' : 'wifi ap',
}, 'topologie/topo_more.html', request)
@login_required
@can_edit(AccessPoint)
def edit_ap(request, ap, ap_id):
""" Edition d'un switch. Permet de chambre nombre de ports,
place dans le stack, interface et machine associée"""
interface_form = EditInterfaceForm(
request.POST or None,
user=request.user,
instance=ap.interface_set.first()
)
ap_form = EditAccessPointForm(
request.POST or None,
user=request.user,
instance=ap
)
domain_form = DomainForm(
request.POST or None,
instance=ap.interface_set.first().domain
)
if ap_form.is_valid() and interface_form.is_valid():
user = AssoOption.get_cached_value('utilisateur_asso')
if not user:
messages.error(request, "L'user association n'existe pas encore,\
veuillez le créer ou le linker dans preferences")
return redirect(reverse('topologie:index-ap'))
new_ap = ap_form.save(commit=False)
new_interface = interface_form.save(commit=False)
new_domain = domain_form.save(commit=False)
with transaction.atomic(), reversion.create_revision():
new_ap.save()
reversion.set_user(request.user)
reversion.set_comment(
"Champs modifié(s) : %s" % ', '.join(
field for field in ap_form.changed_data)
)
with transaction.atomic(), reversion.create_revision():
new_interface.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in interface_form.changed_data)
)
reversion.set_comment("Création")
with transaction.atomic(), reversion.create_revision():
new_domain.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in domain_form.changed_data)
)
messages.success(request, "La borne a été modifiée")
return redirect(reverse('topologie:index-ap'))
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)
@login_required @login_required
@can_create(Room) @can_create(Room)
def new_room(request): def new_room(request):

View file

@ -41,8 +41,17 @@ from django.utils import timezone
from django.contrib.auth.models import Group, Permission from django.contrib.auth.models import Group, Permission
from preferences.models import OptionalUser from preferences.models import OptionalUser
from .models import User, ServiceUser, School, ListRight, Whitelist from .models import (
from .models import Ban, Adherent, Club User,
ServiceUser,
School,
ListRight,
Whitelist,
ListShell,
Ban,
Adherent,
Club
)
from re2o.utils import remove_user_room from re2o.utils import remove_user_room
from re2o.field_permissions import FieldPermissionFormMixin from re2o.field_permissions import FieldPermissionFormMixin
@ -460,6 +469,18 @@ class SchoolForm(ModelForm):
self.fields['name'].label = 'Établissement' self.fields['name'].label = 'Établissement'
class ShellForm(ModelForm):
"""Edition, creation d'un école"""
class Meta:
model = ListShell
fields = ['shell']
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(ShellForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['shell'].label = 'Nom du shell'
class ListRightForm(ModelForm): class ListRightForm(ModelForm):
"""Edition, d'un groupe , équivalent à un droit """Edition, d'un groupe , équivalent à un droit
Ne peremet pas d'editer le gid, car il sert de primary key""" Ne peremet pas d'editer le gid, car il sert de primary key"""

View file

@ -54,7 +54,11 @@ class Command(BaseCommand):
raise CommandError(msg) raise CommandError(msg)
shells = ListShell.objects.all() shells = ListShell.objects.all()
self.stdout.write("Choisissez un shell pour l'utilisateur %s :" % target_user.pseudo)
current_shell = "inconnu"
if target_user.shell:
current_shell = target_user.shell.get_pretty_name()
self.stdout.write("Choisissez un shell pour l'utilisateur %s (le shell actuel est %s) :" % (target_user.pseudo, current_shell))
for shell in shells: for shell in shells:
self.stdout.write("%d - %s (%s)" % (shell.id, shell.get_pretty_name(), shell.shell)) self.stdout.write("%d - %s (%s)" % (shell.id, shell.get_pretty_name(), shell.shell))
shell_id = input("Entrez un nombre : ") shell_id = input("Entrez un nombre : ")

View file

@ -0,0 +1,80 @@
# ⁻*- 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 © 2018 Benjamin Graillot
#
# Copyright © 2013-2015 Raphaël-David Lasseri <lasseri@crans.org>
#
# 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.
import sys
import re
from datetime import datetime
from django.core.management.base import BaseCommand, CommandError
from django.utils.timezone import make_aware
from users.models import User
# Une liste d'expressions régulières à chercher dans les logs.
# Elles doivent contenir un groupe 'date' et un groupe 'user'.
# Pour le CAS on prend comme entrée cat ~/cas.log | grep -B 2 -A 2 "ACTION: AUTHENTICATION_SUCCESS"| grep 'WHEN\|WHO'|sed 'N;s/\n/ /'
COMPILED_REGEX = map(re.compile, [
r'^(?P<date>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}).*(?:'r'dovecot.*Login: user=<|'r'sshd.*Accepted.*for 'r')(?P<user>[^ >]+).*$',
r'^(?P<date>.*) LOGIN INFO User logged in : (?P<user>.*)',
r'WHO: \[username: (?P<user>.*)\] WHEN: (?P<date>.* CET .*)',
r'WHO: \[username: (?P<user>.*)\] WHEN: (?P<date>.* CEST .*)'
])
# Les formats de date en strftime associés aux expressions ci-dessus.
DATE_FORMATS = [
"%Y-%m-%dT%H:%M:%S",
"%d/%b/%Y:%H:%M:%S",
"%a %b %d CET %H:%M:%S%Y",
"%a %b %d CEST %H:%M:%S%Y"
]
class Command(BaseCommand):
help = 'Update the time of the latest connection for users by matching stdin against a set of regular expressions'
def handle(self, *args, **options):
def parse_logs(logfile):
"""
Parse les logs sur l'entrée standard et rempli un dictionnaire
ayant pour clef le pseudo de l'adherent
"""
global COMPILED_REGEX, DATE_FORMATS
parsed_log = {}
for line in logfile:
for i, regex in enumerate(COMPILED_REGEX):
m = regex.match(line)
if m:
parsed_log[m.group('user')] = make_aware(datetime.strptime(m.group('date'), DATE_FORMATS[i]))
return parsed_log
parsed_log = parse_logs(sys.stdin)
for pseudo in parsed_log:
for user in User.objects.filter(pseudo=pseudo):
last_login = parsed_log.get(user.pseudo, user.last_login)
if not user.last_login:
user.last_login = last_login
elif last_login > user.last_login:
user.last_login = last_login
user.save()

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-03-24 18:06
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('users', '0069_club_mailing'),
]
operations = [
migrations.AlterModelOptions(
name='listshell',
options={'permissions': (('view_listshell', "Peut voir un objet shell quelqu'il soit"),)},
),
]

View file

@ -300,6 +300,12 @@ class User(FieldPermissionModelMixin, AbstractBaseUser, PermissionsMixin):
""" Renvoie seulement le nom""" """ Renvoie seulement le nom"""
return self.surname return self.surname
@property
def get_shell(self):
""" A utiliser de préférence, prend le shell par défaut
si il n'est pas défini"""
return self.shell or OptionalUser.get_cached_value('shell_default')
def end_adhesion(self): def end_adhesion(self):
""" Renvoie la date de fin d'adhésion d'un user. Examine les objets """ Renvoie la date de fin d'adhésion d'un user. Examine les objets
cotisation""" cotisation"""
@ -502,8 +508,8 @@ class User(FieldPermissionModelMixin, AbstractBaseUser, PermissionsMixin):
user_ldap.gid = LDAP['user_gid'] user_ldap.gid = LDAP['user_gid']
user_ldap.user_password = self.password[:6] + self.password[7:] user_ldap.user_password = self.password[:6] + self.password[7:]
user_ldap.sambat_nt_password = self.pwd_ntlm.upper() user_ldap.sambat_nt_password = self.pwd_ntlm.upper()
if self.shell: if self.get_shell:
user_ldap.login_shell = self.shell.shell user_ldap.login_shell = str(self.get_shell)
if self.state == self.STATE_DISABLED: if self.state == self.STATE_DISABLED:
user_ldap.shadowexpire = str(0) user_ldap.shadowexpire = str(0)
else: else:
@ -1248,10 +1254,67 @@ class ListShell(models.Model):
shell = models.CharField(max_length=255, unique=True) shell = models.CharField(max_length=255, unique=True)
class Meta:
permissions = (
("view_listshell", "Peut voir un objet shell quelqu'il soit"),
)
def get_instance(shellid, *args, **kwargs):
return ListShell.objects.get(pk=shellid)
def get_pretty_name(self): def get_pretty_name(self):
"""Return the canonical name of the shell""" """Return the canonical name of the shell"""
return self.shell.split("/")[-1] return self.shell.split("/")[-1]
def can_create(user_request, *args, **kwargs):
"""Check if an user can create a ListShell object.
:param user_request: The user who wants to create a user object.
:return: a message and a boolean which is True if the user can create.
"""
return user_request.has_perm('users.add_listshell'), u"Vous n'avez pas le\
droit de créer des shells"
def can_edit(self, user_request, *args, **kwargs):
"""Check if an user can edit a ListShell object.
:param self: The Shell which is to be edited.
:param user_request: The user who requests to edit self.
:return: a message and a boolean which is True if edition is granted.
"""
return user_request.has_perm('users.change_listshell'), u"Vous n'avez pas le\
droit d'éditer des shells"
def can_delete(self, user_request, *args, **kwargs):
"""Check if an user can delete a ListShell object.
:param self: The Shell which is to be deleted.
:param user_request: The user who requests deletion.
:return: True if deletion is granted, and a message.
"""
return user_request.has_perm('users.delete_listshell'), u"Vous n'avez pas le\
droit de supprimer des shells"
def can_view_all(user_request, *args, **kwargs):
"""Check if an user can access to the list of every ListShell objects
:param user_request: The user who wants to view the list.
:return: True if the user can view the list and an explanation message.
"""
return user_request.has_perm('users.view_listshell'), u"Vous n'avez pas le\
droit de voir les shells"
def can_view(self, user_request, *args, **kwargs):
"""Check if an user can view a ListShell object.
:param self: The targeted ListShell instance.
:param user_request: The user who ask for viewing the target.
:return: A boolean telling if the acces is granted and an explanation
text
"""
return user_request.has_perm('users.view_listshell'), u"Vous n'avez pas le\
droit de voir les shells"
def __str__(self): def __str__(self):
return self.shell return self.shell

View file

@ -0,0 +1,47 @@
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
<th>Shell</th>
<th></th>
</tr>
</thead>
{% for shell in shell_list %}
<tr>
<td>{{ shell.shell }}</td>
<td class="text-right">
{% can_delete shell %}
{% include 'buttons/suppr.html' with href='users:del-shell' id=shell.id %}
{% acl_end %}
{% can_edit shell %}
{% include 'buttons/edit.html' with href='users:edit-shell' id=shell.id %}
{% acl_end %}
{% include 'buttons/history.html' with href='users:history' name='shell' id=shell.id %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -21,7 +21,7 @@ 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., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %} {% endcomment %}
<div class="table-responsive">
{% if users_list.paginator %} {% if users_list.paginator %}
{% include "pagination.html" with list=users_list %} {% include "pagination.html" with list=users_list %}
{% endif %} {% endif %}
@ -60,3 +60,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% if users_list.paginator %} {% if users_list.paginator %}
{% include "pagination.html" with list=users_list %} {% include "pagination.html" with list=users_list %}
{% endif %} {% endif %}
</div>

View file

@ -0,0 +1,41 @@
{% extends "users/sidebar.html" %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% block title %}Utilisateurs{% endblock %}
{% block content %}
<h2>Liste des Shells</h2>
{% can_create ListShell %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'users:add-shell' %}"><i class="fa fa-plus"></i> Ajouter un shell</a>
{% acl_end %}
{% include "users/aff_shell.html" with shell_list=shell_list %}
<br />
<br />
<br />
{% endblock %}

View file

@ -69,6 +69,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Établissements Établissements
</a> </a>
{% acl_end %} {% acl_end %}
{% can_view_all ListShell %}
<a class="list-group-item list-group-item-info" href="{% url "users:index-shell" %}">
<i class="fa fa-list-ul"></i>
Liste des shells
</a>
{% acl_end %}
{% can_view_all ListRight %} {% can_view_all ListRight %}
<a class="list-group-item list-group-item-info" href="{% url "users:index-listright" %}"> <a class="list-group-item list-group-item-info" href="{% url "users:index-listright" %}">
<i class="fa fa-list-ul"></i> <i class="fa fa-list-ul"></i>

View file

@ -80,10 +80,22 @@ urlpatterns = [
name='edit-listright' name='edit-listright'
), ),
url(r'^del_listright/$', views.del_listright, name='del-listright'), url(r'^del_listright/$', views.del_listright, name='del-listright'),
url(r'^add_shell/$', views.add_shell, name='add-shell'),
url(
r'^edit_shell/(?P<shellid>[0-9]+)$',
views.edit_shell,
name='edit-shell'
),
url(
r'^del_shell/(?P<shellid>[0-9]+)$',
views.del_shell,
name='del-shell'
),
url(r'^profil/(?P<userid>[0-9]+)$', views.profil, name='profil'), url(r'^profil/(?P<userid>[0-9]+)$', views.profil, name='profil'),
url(r'^index_ban/$', views.index_ban, name='index-ban'), url(r'^index_ban/$', views.index_ban, name='index-ban'),
url(r'^index_white/$', views.index_white, name='index-white'), url(r'^index_white/$', views.index_white, name='index-white'),
url(r'^index_school/$', views.index_school, name='index-school'), url(r'^index_school/$', views.index_school, name='index-school'),
url(r'^index_shell/$', views.index_shell, name='index-shell'),
url(r'^index_listright/$', views.index_listright, name='index-listright'), url(r'^index_listright/$', views.index_listright, name='index-listright'),
url( url(
r'^index_serviceusers/$', r'^index_serviceusers/$',

View file

@ -45,8 +45,10 @@ from django.db import IntegrityError
from django.utils import timezone from django.utils import timezone
from django.db import transaction from django.db import transaction
from django.http import HttpResponse from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer from rest_framework.renderers import JSONRenderer
@ -63,6 +65,7 @@ from users.models import (
ServiceUser, ServiceUser,
Adherent, Adherent,
Club, Club,
ListShell,
) )
from users.forms import ( from users.forms import (
BanForm, BanForm,
@ -72,6 +75,7 @@ from users.forms import (
NewListRightForm, NewListRightForm,
StateForm, StateForm,
SchoolForm, SchoolForm,
ShellForm,
EditServiceUserForm, EditServiceUserForm,
ServiceUserForm, ServiceUserForm,
ListRightForm, ListRightForm,
@ -273,8 +277,10 @@ def del_group(request, user, userid, listrightid):
with transaction.atomic(), reversion.create_revision(): with transaction.atomic(), reversion.create_revision():
user.groups.remove(ListRight.objects.get(id=listrightid)) user.groups.remove(ListRight.objects.get(id=listrightid))
user.save() user.save()
reversion.set_user(request.user)
reversion.set_comment("Suppression de droit")
messages.success(request, "Droit supprimé à %s" % user) messages.success(request, "Droit supprimé à %s" % user)
return redirect(reverse('users:index-listright')) return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
@login_required @login_required
@ -492,6 +498,55 @@ def del_school(request, instances):
return form({'userform': school, 'action_name': 'Supprimer'}, 'users/user.html', request) return form({'userform': school, 'action_name': 'Supprimer'}, 'users/user.html', request)
@login_required
@can_create(ListShell)
def add_shell(request):
""" Ajouter un shell à la base de donnée"""
shell = ShellForm(request.POST or None)
if shell.is_valid():
with transaction.atomic(), reversion.create_revision():
shell.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Le shell a été ajouté")
return redirect(reverse('users:index-shell'))
return form({'userform': shell, 'action_name':'Ajouter'}, 'users/user.html', request)
@login_required
@can_edit(ListShell)
def edit_shell(request, shell_instance, shellid):
""" Editer un shell à partir du shellid"""
shell = ShellForm(request.POST or None, instance=shell_instance)
if shell.is_valid():
with transaction.atomic(), reversion.create_revision():
shell.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in shell.changed_data
))
messages.success(request, "Le shell a été modifié")
return redirect(reverse('users:index-shell'))
return form({'userform': shell, 'action_name':'Editer'}, 'users/user.html', request)
@login_required
@can_delete(ListShell)
def del_shell(request, shell, shellid):
"""Destruction d'un shell"""
if request.method == "POST":
with transaction.atomic(), reversion.create_revision():
shell.delete()
reversion.set_user(request.user)
messages.success(request, "Le shell a été détruit")
return redirect(reverse('users:index-shell'))
return form(
{'objet': shell, 'objet_name': 'shell'},
'users/delete.html',
request
)
@login_required @login_required
@can_create(ListRight) @can_create(ListRight)
def add_listright(request): def add_listright(request):
@ -690,7 +745,7 @@ def index_white(request):
@login_required @login_required
@can_view_all(School) @can_view_all(School)
def index_school(request): def index_school(request):
""" Affiche l'ensemble des établissement, need droit cableur """ """ Affiche l'ensemble des établissement"""
school_list = School.objects.order_by('name') school_list = School.objects.order_by('name')
return render( return render(
request, request,
@ -699,10 +754,22 @@ def index_school(request):
) )
@login_required
@can_view_all(ListShell)
def index_shell(request):
""" Affiche l'ensemble des shells"""
shell_list = ListShell.objects.order_by('shell')
return render(
request,
'users/index_shell.html',
{'shell_list': shell_list}
)
@login_required @login_required
@can_view_all(ListRight) @can_view_all(ListRight)
def index_listright(request): def index_listright(request):
""" Affiche l'ensemble des droits , need droit cableur """ """ Affiche l'ensemble des droits"""
listright_list = ListRight.objects.order_by('unix_name')\ listright_list = ListRight.objects.order_by('unix_name')\
.prefetch_related('permissions').prefetch_related('user_set') .prefetch_related('permissions').prefetch_related('user_set')
return render( return render(