8
0
Fork 0
mirror of https://gitlab.federez.net/re2o/re2o synced 2024-07-08 07:04:06 +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 %}
{% load acl %}
<div class="table-responsive">
{% if facture_list.paginator %}
{% include "pagination.html" with list=facture_list %}
{% endif %}
@ -85,4 +85,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% if facture_list.paginator %}
{% include "pagination.html" with list=facture_list %}
{% endif %}
</div>

View file

@ -192,19 +192,21 @@ def post_auth(data):
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
if hasattr(nas_instance, 'switch'):
if hasattr(nas_machine, 'switch'):
port = data.get('NAS-Port-Id', data.get('NAS-Port', None))
#Pour les infrastructures possédant des switchs 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:
# Si c'est le cas, on resélectionne le bon switch dans la stack
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
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
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", '')
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.
Plusieurs modes :
- 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
extra_log = ""
# 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)
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
if not port:
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>
Utilisateurs
</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 %}
{% 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_users/$', views.stats_users, name='stats-users'),
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.contrib import messages
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 Version, ContentType
from time import time
from users.models import (
User,
ServiceUser,
@ -446,3 +448,15 @@ def stats_actions(request):
},
}
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 %}
{% load acl %}
<div class="table-responsive">
{% if machines_list.paginator %}
{% include "pagination.html" with list=machines_list %}
{% endif %}
<table class="table" id="machines_table">
<colgroup>
<col>
@ -175,6 +176,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</tbody>
</table>
<script>
$("#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 %}];
@ -193,3 +195,4 @@ $("#machines_table").ready( function() {
{% if machines_list.paginator %}
{% include "pagination.html" with list=machines_list %}
{% 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,
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:
permissions = (

View file

@ -68,7 +68,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<tr>
<th>Auto inscription</th>
<td>{{ useroptions.self_adhesion }}</td>
</tr>
<th>Shell par défaut des utilisateurs</th>
<td>{{ useroptions.shell_default }}</td>
</tr>
</table>
<h4>Préférences machines</h4>
<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 %}
<div class="col-12">
<div class="thumbnail">
<img src="{% static service.image %}" alt="{{ service.name }}">
<div class="caption">
<a href="{{ service.url }}"><img src="{% static service.image %}" alt="{{ service.name }}"></a>
<div class="caption">
<h3>{{ service.name }}</h3>
<p>{{ service.description }}</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
'Stack' : topologie.models.Stack,
'Switch' : topologie.models.Switch,
'AccessPoint' : topologie.models.AccessPoint,
'ModelSwitch' : topologie.models.ModelSwitch,
'ConstructorSwitch' : topologie.models.ConstructorSwitch,
'Port' : topologie.models.Port,
@ -133,6 +134,7 @@ MODEL_NAME = {
'ServiceUser' : users.models.ServiceUser,
'School' : users.models.School,
'ListRight' : users.models.ListRight,
'ListShell' : users.models.ListShell,
'Ban' : users.models.Ban,
'Whitelist' : users.models.Whitelist,
}

View file

@ -213,8 +213,8 @@ class SortTable:
'default': ['-date']
}
TOPOLOGIE_INDEX = {
'switch_dns': ['switch_interface__domain__name'],
'switch_ip': ['switch_interface__ipv4__ipv4'],
'switch_dns': ['interface__domain__name'],
'switch_ip': ['interface__ipv4__ipv4'],
'switch_loc': ['location'],
'switch_ports': ['number'],
'switch_stack': ['stack__name'],
@ -233,6 +233,12 @@ class SortTable:
'room_name': ['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 = {
'stack_name': ['name'],
'stack_id': ['stack_id'],

View file

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

View file

@ -260,7 +260,7 @@ def search_single_word(word, filters, user,
) | Q(
machine_interface__domain__name__icontains=word
) | Q(
related__switch__switch_interface__domain__name__icontains=word
related__switch__domain__name__icontains=word
) | Q(
radius__icontains=word
) | Q(
@ -277,9 +277,9 @@ def search_single_word(word, filters, user,
# Switches
if '7' in aff and Switch.can_view_all(user):
filter_switches = Q(
switch_interface__domain__name__icontains=word
domain__name__icontains=word
) | Q(
switch_interface__ipv4__ipv4__icontains=word
ipv4__ipv4__icontains=word
) | Q(
location__icontains=word
) | 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' %}">
{% csrf_token %}
{% 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 }}" />
</form></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 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):
@ -47,6 +55,11 @@ class PortAdmin(VersionAdmin):
pass
class AccessPointAdmin(VersionAdmin):
"""Administration d'une borne"""
pass
class RoomAdmin(VersionAdmin):
"""Administration d'un chambre"""
pass
@ -63,6 +76,7 @@ class ConstructorSwitchAdmin(VersionAdmin):
admin.site.register(Port, PortAdmin)
admin.site.register(AccessPoint, AccessPointAdmin)
admin.site.register(Room, RoomAdmin)
admin.site.register(Switch, SwitchAdmin)
admin.site.register(Stack, StackAdmin)

View file

@ -33,9 +33,23 @@ NewSwitchForm)
from __future__ import unicode_literals
from machines.models import Interface
from machines.forms import (
EditInterfaceForm,
EditMachineForm,
NewMachineForm
)
from django import forms
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):
@ -67,10 +81,12 @@ class EditPortForm(ModelForm):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(EditPortForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['machine_interface'].queryset = Interface.objects.all()\
.select_related('domain__extension')
.select_related('domain__extension')
self.fields['related'].queryset = Port.objects.all()\
.select_related('switch__switch_interface__domain__extension')\
.order_by('switch', 'port')
.prefetch_related(Prefetch(
'switch__interface_set',
queryset=Interface.objects.select_related('ipv4__ip_type__extension').select_related('domain__extension')
))
class AddPortForm(ModelForm):
@ -84,10 +100,12 @@ class AddPortForm(ModelForm):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(AddPortForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['machine_interface'].queryset = Interface.objects.all()\
.select_related('domain__extension')
.select_related('domain__extension')
self.fields['related'].queryset = Port.objects.all()\
.select_related('switch__switch_interface__domain__extension')\
.order_by('switch', 'port')
.prefetch_related(Prefetch(
'switch__interface_set',
queryset=Interface.objects.select_related('ipv4__ip_type__extension').select_related('domain__extension')
))
class StackForm(ModelForm):
@ -102,32 +120,34 @@ class StackForm(ModelForm):
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"""
class Meta:
model = Switch
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(ModelForm):
class NewSwitchForm(NewMachineForm):
"""Permet de créer un switch : emplacement, paramètres machine,
membre d'un stack (option), nombre de ports (number)"""
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):
"""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 reversion import revisions as reversion
from machines.models import Machine, Interface
class Stack(models.Model):
"""Un objet stack. Regrouppe des switchs en foreign key
@ -108,7 +109,54 @@ class Stack(models.Model):
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),
un emplacement (location), un stack parent (optionnel, stack)
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"""
PRETTY_NAME = "Switch / Commutateur"
switch_interface = models.OneToOneField(
'machines.Interface',
on_delete=models.CASCADE
)
location = models.CharField(max_length=255)
number = models.PositiveIntegerField()
details = models.CharField(max_length=255, blank=True)
stack = models.ForeignKey(
'topologie.Stack',
blank=True,
@ -176,11 +220,10 @@ class Switch(models.Model):
return False, u"Vous n'avez pas le droit de voir les switch"
return True, None
def __str__(self):
return self.location + ' ' + str(self.switch_interface)
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_member_id is not None:
if (self.stack_member_id > self.stack.member_id_max) or\
@ -192,6 +235,7 @@ class Switch(models.Model):
else:
raise ValidationError({'stack_member_id': "L'id dans la stack\
ne peut être nul"})
def create_ports(self, begin, end):
""" 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:
ValidationError("Création d'un port existant.")
def __str__(self):
return str(self.interface_set.first())
class ModelSwitch(models.Model):
"""Un modèle (au sens constructeur) de switch"""
@ -372,11 +419,11 @@ class Port(models.Model):
def get_instance(port_id, *args, **kwargs):
return Port.objects\
.select_related('switch__switch_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('related')\
.prefetch_related('switch__interface_set__domain__extension')\
.get(pk=port_id)
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>
<td>
<a title="Configuer" href="{% url 'topologie:index-port' switch.pk %}">
{{switch.switch_interface}}
{{switch}}
</a>
</td>
<td>{{switch.switch_interface.ipv4}}</td>
<td>{{switch.interface_set.first.ipv4}}</td>
<td>{{switch.location}}</td>
<td>{{switch.number}}</td>
<td>{{switch.stack.name}}</td>
<td>{{switch.stack_member_id}}</td>
<td>{{switch.model}}</td>
<td>{{switch.details}}</td>
<td>{{switch.interface_set.first.details}}</td>
<td class="text-right">
{% include 'buttons/history.html' with href='topologie:history' name='switch' id=switch.pk%}
{% can_edit switch %}
{% include 'buttons/edit.html' with href='topologie:edit-switch' id=switch.pk %}
{% acl_end %}
{% 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 %}
{% can_create Port %}
{% 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 %}
<a class="list-group-item list-group-item-info" href="{% url "topologie:index-room" %}">
<i class="fa fa-list-ul"></i>
Chambres
<i class="fa fa-home"></i>
Chambres et locaux
</a>
<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
</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 class="list-group-item list-group-item-info" href="{% url "topologie:index-stack" %}">
<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 %}
{% bootstrap_form_errors topoform %}
{% 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>
{% endif %}
<form class="form" method="post">
{% csrf_token %}
{% if topoform %}
<h3>Réglage spécifiques du switch</h3>
{% massive_bootstrap_form topoform 'switch_interface' %}
{% endif %}
{% if machineform %}
<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" %}
{% bootstrap_button "Créer" button_type="submit" icon="ok" %}
</form>
<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 = [
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]+)$',
views.create_ports,
name='create-ports'),
@ -43,6 +47,7 @@ urlpatterns = [
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'^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]+)$',
views.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.db import IntegrityError
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.paginator import Paginator, EmptyPage, PageNotAnInteger
from reversion import revisions as reversion
@ -53,7 +53,8 @@ from topologie.models import (
Room,
Stack,
ModelSwitch,
ConstructorSwitch
ConstructorSwitch,
AccessPoint
)
from topologie.forms import EditPortForm, NewSwitchForm, EditSwitchForm
from topologie.forms import (
@ -62,7 +63,9 @@ from topologie.forms import (
StackForm,
EditModelSwitchForm,
EditConstructorSwitchForm,
CreatePortsForm
CreatePortsForm,
AddAccessPointForm,
EditAccessPointForm
)
from users.views import form
from re2o.utils import SortTable
@ -81,6 +84,7 @@ from machines.forms import (
AddInterfaceForm
)
from machines.views import generate_ipv4_mbf_param
from machines.models import Interface
from preferences.models import AssoOption, GeneralOption
@ -89,9 +93,10 @@ from preferences.models import AssoOption, GeneralOption
def index(request):
""" Vue d'affichage de tous les swicthes"""
switch_list = Switch.objects\
.select_related('switch_interface__domain__extension')\
.select_related('switch_interface__ipv4')\
.select_related('switch_interface__domain')\
.prefetch_related(Prefetch(
'interface_set',
queryset=Interface.objects.select_related('ipv4__ip_type__extension').select_related('domain__extension')
))\
.select_related('stack')
switch_list = SortTable.sort(
switch_list,
@ -124,9 +129,11 @@ def index_port(request, switch, switch_id):
.select_related('room')\
.select_related('machine_interface__domain__extension')\
.select_related('machine_interface__machine__user')\
.select_related(
'related__switch__switch_interface__domain__extension'
)\
.select_related('related__switch')\
.prefetch_related(Prefetch(
'related__switch__interface_set',
queryset=Interface.objects.select_related('domain__extension')
))\
.select_related('switch')
port_list = SortTable.sort(
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
@can_view_all(Stack)
def index_stack(request):
"""Affichage de la liste des stacks (affiche l'ensemble des switches)"""
stack_list = Stack.objects\
.prefetch_related('switch_set__switch_interface__domain__extension')
.prefetch_related('switch_set__domain__extension')
stack_list = SortTable.sort(
stack_list,
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
associée. Vue complexe. Appelle successivement les 4 models forms
adaptés : machine, interface, domain et switch"""
switch = NewSwitchForm(request.POST or None)
machine = NewMachineForm(
switch = NewSwitchForm(
request.POST or None,
user=request.user
)
interface = AddInterfaceForm(
request.POST or None,
user=request.user
)
)
domain = DomainForm(
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')
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_machine = machine.save(commit=False)
new_machine.user = user
new_interface = interface.save(commit=False)
new_switch_instance = switch.save(commit=False)
new_domain_instance = domain.save(commit=False)
with transaction.atomic(), reversion.create_revision():
new_machine.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
new_interface.machine = new_machine
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")
new_switch_instance.switch_interface = new_interface
with transaction.atomic(), reversion.create_revision():
new_switch_instance.save()
reversion.set_user(request.user)
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)
new_switch = switch.save(commit=False)
new_switch.user = user
new_interface_instance = interface.save(commit=False)
domain.instance.interface_parent = new_interface_instance
if domain.is_valid():
new_domain_instance = domain.save(commit=False)
with transaction.atomic(), reversion.create_revision():
new_switch.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
new_interface_instance.machine = new_switch
with transaction.atomic(), reversion.create_revision():
new_interface_instance.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
new_domain_instance.interface_parent = new_interface_instance
with transaction.atomic(), reversion.create_revision():
new_domain_instance.save()
reversion.set_user(request.user)
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({
'topoform': switch,
'machineform': machine,
'interfaceform': interface,
'topoform': interface,
'machineform': switch,
'domainform': domain,
'i_mbf_param': i_mbf_param
}, 'topologie/switch.html', request)
'i_mbf_param': i_mbf_param,
'device' : 'switch',
}, 'topologie/topo_more.html', request)
@login_required
@ -440,7 +473,6 @@ def create_ports(request, switch_id):
'topologie:index-port',
kwargs={'switch_id':switch_id}
))
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,
place dans le stack, interface et machine associée"""
switch_form = EditSwitchForm(request.POST or None, instance=switch)
machine_form = EditMachineForm(
switch_form = EditSwitchForm(
request.POST or None,
instance=switch.switch_interface.machine,
instance=switch,
user=request.user
)
interface_form = EditInterfaceForm(
request.POST or None,
instance=switch.switch_interface,
instance=switch.interface_set.first(),
user=request.user
)
domain_form = DomainForm(
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()\
and interface_form.is_valid():
new_interface = interface_form.save(commit=False)
new_machine = machine_form.save(commit=False)
new_switch_instance = switch_form.save(commit=False)
if switch_form.is_valid() and interface_form.is_valid():
new_switch = switch_form.save(commit=False)
new_interface_instance = interface_form.save(commit=False)
new_domain = domain_form.save(commit=False)
with transaction.atomic(), reversion.create_revision():
new_machine.save()
new_switch.save()
reversion.set_user(request.user)
reversion.set_comment(
"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():
new_interface.save()
new_interface_instance.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
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(
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é")
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({
'id_switch': switch_id,
'topoform': switch_form,
'machineform': machine_form,
'interfaceform': interface_form,
'topoform': interface_form,
'machineform': switch_form,
'domainform': domain_form,
'i_mbf_param': i_mbf_param
}, 'topologie/switch.html', request)
'i_mbf_param': i_mbf_param,
'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
@can_create(Room)
def new_room(request):

View file

@ -41,8 +41,17 @@ from django.utils import timezone
from django.contrib.auth.models import Group, Permission
from preferences.models import OptionalUser
from .models import User, ServiceUser, School, ListRight, Whitelist
from .models import Ban, Adherent, Club
from .models import (
User,
ServiceUser,
School,
ListRight,
Whitelist,
ListShell,
Ban,
Adherent,
Club
)
from re2o.utils import remove_user_room
from re2o.field_permissions import FieldPermissionFormMixin
@ -460,6 +469,18 @@ class SchoolForm(ModelForm):
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):
"""Edition, d'un groupe , équivalent à un droit
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)
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:
self.stdout.write("%d - %s (%s)" % (shell.id, shell.get_pretty_name(), shell.shell))
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"""
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):
""" Renvoie la date de fin d'adhésion d'un user. Examine les objets
cotisation"""
@ -502,8 +508,8 @@ class User(FieldPermissionModelMixin, AbstractBaseUser, PermissionsMixin):
user_ldap.gid = LDAP['user_gid']
user_ldap.user_password = self.password[:6] + self.password[7:]
user_ldap.sambat_nt_password = self.pwd_ntlm.upper()
if self.shell:
user_ldap.login_shell = self.shell.shell
if self.get_shell:
user_ldap.login_shell = str(self.get_shell)
if self.state == self.STATE_DISABLED:
user_ldap.shadowexpire = str(0)
else:
@ -1248,10 +1254,67 @@ class ListShell(models.Model):
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):
"""Return the canonical name of the shell"""
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):
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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
<div class="table-responsive">
{% if users_list.paginator %}
{% include "pagination.html" with list=users_list %}
{% endif %}
@ -60,3 +60,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% if users_list.paginator %}
{% include "pagination.html" with list=users_list %}
{% 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
</a>
{% 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 %}
<a class="list-group-item list-group-item-info" href="{% url "users:index-listright" %}">
<i class="fa fa-list-ul"></i>

View file

@ -80,10 +80,22 @@ urlpatterns = [
name='edit-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'^index_ban/$', views.index_ban, name='index-ban'),
url(r'^index_white/$', views.index_white, name='index-white'),
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_serviceusers/$',

View file

@ -45,8 +45,10 @@ from django.db import IntegrityError
from django.utils import timezone
from django.db import transaction
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
@ -63,6 +65,7 @@ from users.models import (
ServiceUser,
Adherent,
Club,
ListShell,
)
from users.forms import (
BanForm,
@ -72,6 +75,7 @@ from users.forms import (
NewListRightForm,
StateForm,
SchoolForm,
ShellForm,
EditServiceUserForm,
ServiceUserForm,
ListRightForm,
@ -273,8 +277,10 @@ def del_group(request, user, userid, listrightid):
with transaction.atomic(), reversion.create_revision():
user.groups.remove(ListRight.objects.get(id=listrightid))
user.save()
reversion.set_user(request.user)
reversion.set_comment("Suppression de droit")
messages.success(request, "Droit supprimé à %s" % user)
return redirect(reverse('users:index-listright'))
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
@login_required
@ -492,6 +498,55 @@ def del_school(request, instances):
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
@can_create(ListRight)
def add_listright(request):
@ -690,7 +745,7 @@ def index_white(request):
@login_required
@can_view_all(School)
def index_school(request):
""" Affiche l'ensemble des établissement, need droit cableur """
""" Affiche l'ensemble des établissement"""
school_list = School.objects.order_by('name')
return render(
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
@can_view_all(ListRight)
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')\
.prefetch_related('permissions').prefetch_related('user_set')
return render(