Commit b89fb3f5 authored by Maël Kervella's avatar Maël Kervella
Browse files

Merge branch 'master' into massive_use_bft_tag

parents e51b4847 12637841
......@@ -19,7 +19,10 @@
# 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.
"""
Urls de l'application logs, pointe vers les fonctions de views.
Inclu dans le re2o.urls
"""
from __future__ import unicode_literals
from django.conf.urls import url
......@@ -29,7 +32,9 @@ from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^stats_logs$', views.stats_logs, name='stats-logs'),
url(r'^revert_action/(?P<revision_id>[0-9]+)$', views.revert_action, name='revert-action'),
url(r'^revert_action/(?P<revision_id>[0-9]+)$',
views.revert_action,
name='revert-action'),
url(r'^stats_general/$', views.stats_general, name='stats-general'),
url(r'^stats_models/$', views.stats_models, name='stats-models'),
url(r'^stats_users/$', views.stats_users, name='stats-users'),
......
......@@ -23,62 +23,67 @@
# App de gestion des statistiques pour re2o
# Gabriel Détraz
# Gplv2
"""
Vues des logs et statistiques générales.
La vue index générale affiche une selection des dernières actions,
classées selon l'importance, avec date, et user formatés.
Stats_logs renvoie l'ensemble des logs.
Les autres vues sont thématiques, ensemble des statistiques et du
nombre d'objets par models, nombre d'actions par user, etc
"""
from __future__ import unicode_literals
from django.http import HttpResponse
from django.shortcuts import render, redirect
from django.shortcuts import get_object_or_404
from django.template.context_processors import csrf
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.template import Context, RequestContext, loader
from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required
from django.db.models import ProtectedError
from django.forms import ValidationError
from django.db import transaction
from django.db.models import Count
from reversion.models import Revision
from reversion.models import Version, ContentType
from users.models import User, ServiceUser, Right, School, ListRight, ListShell, Ban, Whitelist
from users.models import all_has_access, all_whitelisted, all_baned, all_adherent
from cotisations.models import Facture, Vente, Article, Banque, Paiement, Cotisation
from machines.models import Machine, MachineType, IpType, Extension, Interface, Domain, IpList
from machines.views import all_active_assigned_interfaces_count, all_active_interfaces_count
from users.models import User, ServiceUser, Right, School, ListRight, ListShell
from users.models import Ban, Whitelist
from cotisations.models import Facture, Vente, Article, Banque, Paiement
from cotisations.models import Cotisation
from machines.models import Machine, MachineType, IpType, Extension, Interface
from machines.models import Domain, IpList
from topologie.models import Switch, Port, Room
from preferences.models import GeneralOption
from django.utils import timezone
from dateutil.relativedelta import relativedelta
from re2o.views import form
from re2o.utils import all_whitelisted, all_baned, all_has_access, all_adherent
from re2o.utils import all_active_assigned_interfaces_count
from re2o.utils import all_active_interfaces_count
STATS_DICT = {
0 : ["Tout", 36],
1 : ["1 mois", 1],
2 : ["2 mois", 2],
3 : ["6 mois", 6],
4 : ["1 an", 12],
5 : ["2 an", 24],
0: ["Tout", 36],
1: ["1 mois", 1],
2: ["2 mois", 2],
3: ["6 mois", 6],
4: ["1 an", 12],
5: ["2 an", 24],
}
def form(ctx, template, request):
c = ctx
c.update(csrf(request))
return render(request, template, c)
@login_required
@permission_required('cableur')
def index(request):
options, created = GeneralOption.objects.get_or_create()
"""Affiche les logs affinés, date reformatées, selectionne
les event importants (ajout de droits, ajout de ban/whitelist)"""
options, _created = GeneralOption.objects.get_or_create()
pagination_number = options.pagination_number
# The types of content kept for display
content_type_filter = ['ban', 'whitelist', 'vente', 'interface', 'user']
content_type_filter = ['ban', 'whitelist', 'vente', 'interface', 'user']
# Select only wanted versions
versions = Version.objects.filter(content_type__in=ContentType.objects.filter(model__in=content_type_filter)).order_by('revision__date_created').reverse().select_related('revision')
versions = Version.objects.filter(
content_type__in=ContentType.objects.filter(
model__in=content_type_filter
)
).order_by('revision__date_created').reverse().select_related('revision')
paginator = Paginator(versions, pagination_number)
page = request.GET.get('page')
try:
......@@ -87,7 +92,7 @@ def index(request):
# If page is not an integer, deliver first page.
versions = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
# If page is out of range (e.g. 9999), deliver last page of results.
versions = paginator.page(paginator.num_pages)
# Force to have a list instead of QuerySet
......@@ -95,30 +100,38 @@ def index(request):
# Items to remove later because invalid
to_remove = []
# Parse every item (max = pagination_number)
for i in range( len( versions.object_list ) ):
if versions.object_list[i].object :
v = versions.object_list[i]
for i in range(len(versions.object_list)):
if versions.object_list[i].object:
version = versions.object_list[i]
versions.object_list[i] = {
'rev_id' : v.revision.id,
'comment': v.revision.comment,
'datetime': v.revision.date_created.strftime('%d/%m/%y %H:%M:%S'),
'username': v.revision.user.get_username() if v.revision.user else '?',
'user_id': v.revision.user_id,
'version': v }
else :
to_remove.insert(0,i)
'rev_id': version.revision.id,
'comment': version.revision.comment,
'datetime': version.revision.date_created.strftime(
'%d/%m/%y %H:%M:%S'
),
'username':
version.revision.user.get_username()
if version.revision.user else '?',
'user_id': version.revision.user_id,
'version': version}
else:
to_remove.insert(0, i)
# Remove all tagged invalid items
for i in to_remove :
for i in to_remove:
versions.object_list.pop(i)
return render(request, 'logs/index.html', {'versions_list': versions})
@login_required
@permission_required('cableur')
def stats_logs(request):
options, created = GeneralOption.objects.get_or_create()
"""Affiche l'ensemble des logs et des modifications sur les objets,
classés par date croissante, en vrac"""
options, _created = GeneralOption.objects.get_or_create()
pagination_number = options.pagination_number
revisions = Revision.objects.all().order_by('date_created').reverse().select_related('user').prefetch_related('version_set__object')
revisions = Revision.objects.all().order_by('date_created')\
.reverse().select_related('user')\
.prefetch_related('version_set__object')
paginator = Paginator(revisions, pagination_number)
page = request.GET.get('page')
try:
......@@ -127,9 +140,12 @@ def stats_logs(request):
# If page is not an integer, deliver first page.
revisions = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
# If page is out of range (e.g. 9999), deliver last page of results.
revisions = paginator.page(paginator.num_pages)
return render(request, 'logs/stats_logs.html', {'revisions_list': revisions})
return render(request, 'logs/stats_logs.html', {
'revisions_list': revisions
})
@login_required
@permission_required('bureau')
......@@ -138,121 +154,182 @@ def revert_action(request, revision_id):
try:
revision = Revision.objects.get(id=revision_id)
except Revision.DoesNotExist:
messages.error(request, u"Revision inexistante" )
messages.error(request, u"Revision inexistante")
if request.method == "POST":
revision.revert()
messages.success(request, "L'action a été supprimée")
return redirect("/logs/")
return form({'objet': revision, 'objet_name': revision.__class__.__name__ }, 'logs/delete.html', request)
return form({
'objet': revision,
'objet_name': revision.__class__.__name__
}, 'logs/delete.html', request)
@login_required
@permission_required('cableur')
def stats_general(request):
all_active_users = User.objects.filter(state=User.STATE_ACTIVE)
ip = dict()
"""Statistiques générales affinées sur les ip, activées, utilisées par
range, et les statistiques générales sur les users : users actifs,
cotisants, activés, archivés, etc"""
ip_dict = dict()
for ip_range in IpType.objects.all():
all_ip = IpList.objects.filter(ip_type=ip_range)
used_ip = Interface.objects.filter(ipv4__in=all_ip).count()
active_ip = all_active_assigned_interfaces_count().filter(ipv4__in=IpList.objects.filter(ip_type=ip_range)).count()
ip[ip_range] = [ip_range, all_ip.count(), used_ip, active_ip, all_ip.count()-used_ip]
active_ip = all_active_assigned_interfaces_count().filter(
ipv4__in=IpList.objects.filter(ip_type=ip_range)
).count()
ip_dict[ip_range] = [ip_range, all_ip.count(),
used_ip, active_ip, all_ip.count()-used_ip]
stats = [
[["Categorie", "Nombre d'utilisateurs"], {
'active_users' : ["Users actifs", User.objects.filter(state=User.STATE_ACTIVE).count()],
'inactive_users' : ["Users désactivés", User.objects.filter(state=User.STATE_DISABLED).count()],
'archive_users' : ["Users archivés", User.objects.filter(state=User.STATE_ARCHIVE).count()],
'adherent_users' : ["Adhérents à l'association", all_adherent().count()],
'connexion_users' : ["Utilisateurs bénéficiant d'une connexion", all_has_access().count()],
'ban_users' : ["Utilisateurs bannis", all_baned().count()],
'whitelisted_user' : ["Utilisateurs bénéficiant d'une connexion gracieuse", all_whitelisted().count()],
'actives_interfaces' : ["Interfaces actives (ayant accès au reseau)", all_active_interfaces_count().count()],
'actives_assigned_interfaces' : ["Interfaces actives et assignées ipv4", all_active_assigned_interfaces_count().count()]
}],
[["Range d'ip", "Nombre d'ip totales", "Ip assignées", "Ip assignées à une machine active", "Ip non assignées"] ,ip]
]
[["Categorie", "Nombre d'utilisateurs"], {
'active_users': [
"Users actifs",
User.objects.filter(state=User.STATE_ACTIVE).count()],
'inactive_users': [
"Users désactivés",
User.objects.filter(state=User.STATE_DISABLED).count()],
'archive_users': [
"Users archivés",
User.objects.filter(state=User.STATE_ARCHIVE).count()],
'adherent_users': [
"Adhérents à l'association",
all_adherent().count()],
'connexion_users': [
"Utilisateurs bénéficiant d'une connexion",
all_has_access().count()],
'ban_users': [
"Utilisateurs bannis",
all_baned().count()],
'whitelisted_user': [
"Utilisateurs bénéficiant d'une connexion gracieuse",
all_whitelisted().count()],
'actives_interfaces': [
"Interfaces actives (ayant accès au reseau)",
all_active_interfaces_count().count()],
'actives_assigned_interfaces': [
"Interfaces actives et assignées ipv4",
all_active_assigned_interfaces_count().count()]
}],
[["Range d'ip", "Nombre d'ip totales", "Ip assignées",
"Ip assignées à une machine active", "Ip non assignées"], ip_dict]
]
return render(request, 'logs/stats_general.html', {'stats_list': stats})
@login_required
@permission_required('cableur')
def stats_models(request):
all_active_users = User.objects.filter(state=User.STATE_ACTIVE)
"""Statistiques générales, affiche les comptages par models:
nombre d'users, d'écoles, de droits, de bannissements,
de factures, de ventes, de banque, de machines, etc"""
stats = {
'Users' : {
'users' : [User.PRETTY_NAME, User.objects.count()],
'serviceuser' : [ServiceUser.PRETTY_NAME, ServiceUser.objects.count()],
'right' : [Right.PRETTY_NAME, Right.objects.count()],
'school' : [School.PRETTY_NAME, School.objects.count()],
'listright' : [ListRight.PRETTY_NAME, ListRight.objects.count()],
'listshell' : [ListShell.PRETTY_NAME, ListShell.objects.count()],
'ban' : [Ban.PRETTY_NAME, Ban.objects.count()],
'whitelist' : [Whitelist.PRETTY_NAME, Whitelist.objects.count()]
},
'Cotisations' : {
'factures' : [Facture.PRETTY_NAME, Facture.objects.count()],
'vente' : [Vente.PRETTY_NAME, Vente.objects.count()],
'cotisation' : [Cotisation.PRETTY_NAME, Cotisation.objects.count()],
'article' : [Article.PRETTY_NAME, Article.objects.count()],
'banque' : [Banque.PRETTY_NAME, Banque.objects.count()],
'cotisation' : [Cotisation.PRETTY_NAME, Cotisation.objects.count()],
},
'Machines' : {
'machine' : [Machine.PRETTY_NAME, Machine.objects.count()],
'typemachine' : [MachineType.PRETTY_NAME, MachineType.objects.count()],
'typeip' : [IpType.PRETTY_NAME, IpType.objects.count()],
'extension' : [Extension.PRETTY_NAME, Extension.objects.count()],
'interface' : [Interface.PRETTY_NAME, Interface.objects.count()],
'alias' : [Domain.PRETTY_NAME, Domain.objects.exclude(cname=None).count()],
'iplist' : [IpList.PRETTY_NAME, IpList.objects.count()],
},
'Topologie' : {
'switch' : [Switch.PRETTY_NAME, Switch.objects.count()],
'port' : [Port.PRETTY_NAME, Port.objects.count()],
'chambre' : [Room.PRETTY_NAME, Room.objects.count()],
},
'Actions effectuées sur la base' :
{
'revision' : ["Nombre d'actions", Revision.objects.count()],
},
'Users': {
'users': [User.PRETTY_NAME, User.objects.count()],
'serviceuser': [ServiceUser.PRETTY_NAME,
ServiceUser.objects.count()],
'right': [Right.PRETTY_NAME, Right.objects.count()],
'school': [School.PRETTY_NAME, School.objects.count()],
'listright': [ListRight.PRETTY_NAME, ListRight.objects.count()],
'listshell': [ListShell.PRETTY_NAME, ListShell.objects.count()],
'ban': [Ban.PRETTY_NAME, Ban.objects.count()],
'whitelist': [Whitelist.PRETTY_NAME, Whitelist.objects.count()]
},
'Cotisations': {
'factures': [Facture.PRETTY_NAME, Facture.objects.count()],
'vente': [Vente.PRETTY_NAME, Vente.objects.count()],
'cotisation': [Cotisation.PRETTY_NAME, Cotisation.objects.count()],
'article': [Article.PRETTY_NAME, Article.objects.count()],
'banque': [Banque.PRETTY_NAME, Banque.objects.count()],
},
'Machines': {
'machine': [Machine.PRETTY_NAME, Machine.objects.count()],
'typemachine': [MachineType.PRETTY_NAME,
MachineType.objects.count()],
'typeip': [IpType.PRETTY_NAME, IpType.objects.count()],
'extension': [Extension.PRETTY_NAME, Extension.objects.count()],
'interface': [Interface.PRETTY_NAME, Interface.objects.count()],
'alias': [Domain.PRETTY_NAME,
Domain.objects.exclude(cname=None).count()],
'iplist': [IpList.PRETTY_NAME, IpList.objects.count()],
},
'Topologie': {
'switch': [Switch.PRETTY_NAME, Switch.objects.count()],
'port': [Port.PRETTY_NAME, Port.objects.count()],
'chambre': [Room.PRETTY_NAME, Room.objects.count()],
},
'Actions effectuées sur la base':
{
'revision': ["Nombre d'actions", Revision.objects.count()],
},
}
return render(request, 'logs/stats_models.html', {'stats_list': stats})
return render(request, 'logs/stats_models.html', {'stats_list': stats})
@login_required
@permission_required('cableur')
def stats_users(request):
"""Affiche les statistiques base de données aggrégées par user :
nombre de machines par user, d'etablissements par user,
de moyens de paiements par user, de banque par user,
de bannissement par user, etc"""
onglet = request.GET.get('onglet')
try:
search_field = STATS_DICT[onglet]
except:
search_field = STATS_DICT[0]
_search_field = STATS_DICT[onglet]
except KeyError:
_search_field = STATS_DICT[0]
onglet = 0
start_date = timezone.now() + relativedelta(months=-search_field[1])
stats = {
'Utilisateur' : {
'Machines' : User.objects.annotate(num=Count('machine')).order_by('-num')[:10],
'Facture' : User.objects.annotate(num=Count('facture')).order_by('-num')[:10],
'Bannissement' : User.objects.annotate(num=Count('ban')).order_by('-num')[:10],
'Accès gracieux' : User.objects.annotate(num=Count('whitelist')).order_by('-num')[:10],
'Droits' : User.objects.annotate(num=Count('right')).order_by('-num')[:10],
},
'Etablissement' : {
'Utilisateur' : School.objects.annotate(num=Count('user')).order_by('-num')[:10],
},
'Moyen de paiement' : {
'Utilisateur' : Paiement.objects.annotate(num=Count('facture')).order_by('-num')[:10],
},
'Banque' : {
'Utilisateur' : Banque.objects.annotate(num=Count('facture')).order_by('-num')[:10],
},
'Utilisateur': {
'Machines': User.objects.annotate(
num=Count('machine')
).order_by('-num')[:10],
'Facture': User.objects.annotate(
num=Count('facture')
).order_by('-num')[:10],
'Bannissement': User.objects.annotate(
num=Count('ban')
).order_by('-num')[:10],
'Accès gracieux': User.objects.annotate(
num=Count('whitelist')
).order_by('-num')[:10],
'Droits': User.objects.annotate(
num=Count('right')
).order_by('-num')[:10],
},
'Etablissement': {
'Utilisateur': School.objects.annotate(
num=Count('user')
).order_by('-num')[:10],
},
'Moyen de paiement': {
'Utilisateur': Paiement.objects.annotate(
num=Count('facture')
).order_by('-num')[:10],
},
'Banque': {
'Utilisateur': Banque.objects.annotate(
num=Count('facture')
).order_by('-num')[:10],
},
}
return render(request, 'logs/stats_users.html', {'stats_list': stats, 'stats_dict' : STATS_DICT, 'active_field': onglet})
return render(request, 'logs/stats_users.html', {
'stats_list': stats,
'stats_dict': STATS_DICT,
'active_field': onglet
})
@login_required
@permission_required('cableur')
def stats_actions(request):
onglet = request.GET.get('onglet')
"""Vue qui affiche les statistiques de modifications d'objets par
utilisateurs.
Affiche le nombre de modifications aggrégées par utilisateurs"""
stats = {
'Utilisateur' : {
'Action' : User.objects.annotate(num=Count('revision')).order_by('-num')[:40],
},
'Utilisateur': {
'Action': User.objects.annotate(
num=Count('revision')
).order_by('-num')[:40],
},
}
return render(request, 'logs/stats_users.html', {'stats_list': stats})
......@@ -53,30 +53,10 @@ from .forms import EditIpTypeForm, IpTypeForm, DelIpTypeForm, DomainForm, AliasF
from .forms import EditOuverturePortListForm, EditOuverturePortConfigForm
from .models import IpType, Machine, Interface, IpList, MachineType, Extension, Mx, Ns, Domain, Service, Service_link, Vlan, Nas, Text, OuverturePortList, OuverturePort
from users.models import User
from users.models import all_has_access
from preferences.models import GeneralOption, OptionalMachine
from re2o.templatetags.massive_bootstrap_form import hidden_id, input_id
def all_active_interfaces():
"""Renvoie l'ensemble des machines autorisées à sortir sur internet """
return Interface.objects.filter(machine__in=Machine.objects.filter(user__in=all_has_access()).filter(active=True)).select_related('domain').select_related('machine').select_related('type').select_related('ipv4').select_related('domain__extension').select_related('ipv4__ip_type').distinct()
def all_active_assigned_interfaces():
""" Renvoie l'ensemble des machines qui ont une ipv4 assignées et disposant de l'accès internet"""
return all_active_interfaces().filter(ipv4__isnull=False)
def all_active_interfaces_count():
""" Version light seulement pour compter"""
return Interface.objects.filter(machine__in=Machine.objects.filter(user__in=all_has_access()).filter(active=True))
def all_active_assigned_interfaces_count():
""" Version light seulement pour compter"""
return all_active_interfaces_count().filter(ipv4__isnull=False)
def form(ctx, template, request):
c = ctx
c.update(csrf(request))
return render(request, template, c)
from re2o.utils import all_active_assigned_interfaces, all_has_access
from re2o.views import form
def f_type_id( is_type_tt ):
""" The id that will be used in HTML to store the value of the field
......
......@@ -20,35 +20,53 @@
# 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.
"""
Classes admin pour les models de preferences
"""
from __future__ import unicode_literals
from django.contrib import admin
from reversion.admin import VersionAdmin
from .models import OptionalUser, OptionalMachine, OptionalTopologie, GeneralOption, Service, AssoOption, MailMessageOption
from .models import OptionalUser, OptionalMachine, OptionalTopologie
from .models import GeneralOption, Service, AssoOption, MailMessageOption
class OptionalUserAdmin(VersionAdmin):
"""Class admin options user"""
pass
class OptionalTopologieAdmin(VersionAdmin):
"""Class admin options topologie"""
pass
class OptionalMachineAdmin(VersionAdmin):
"""Class admin options machines"""
pass
class GeneralOptionAdmin(VersionAdmin):
"""Class admin options générales"""
pass
class ServiceAdmin(VersionAdmin):
"""Class admin gestion des services de la page d'accueil"""
pass
class AssoOptionAdmin(VersionAdmin):
"""Class admin options de l'asso"""
pass
class MailMessageOptionAdmin(VersionAdmin):
"""Class admin options mail"""
pass
admin.site.register(OptionalUser, OptionalUserAdmin)
admin.site.register(OptionalMachine, OptionalMachineAdmin)
admin.site.register(OptionalTopologie, OptionalTopologieAdmin)
......
......@@ -19,71 +19,116 @@
# 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.
"""
Formulaire d'edition des réglages : user, machine, topologie, asso...
"""
from __future__ import unicode_literals
from django.forms import ModelForm, Form, ValidationError
from django.forms import ModelForm, Form
from django import forms
from .models import OptionalUser, OptionalMachine, OptionalTopologie, GeneralOption, AssoOption, MailMessageOption, Service
from django.db.models import Q
from .models import OptionalUser, OptionalMachine, OptionalTopologie
from .models import GeneralOption, AssoOption, MailMessageOption, Service
class EditOptionalUserForm(ModelForm):
"""Formulaire d'édition des options de l'user. (solde, telephone..)"""
class Meta:
model = OptionalUser
fields = '__all__'
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(EditOptionalUserForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['is_tel_mandatory'].label = 'Exiger un numéro de téléphone'
self.fields['user_solde'].label = 'Activation du solde pour les utilisateurs'
super(EditOptionalUserForm, self).__init__(
*args,
prefix=prefix,
**kwargs
)
self.fields['is_tel_mandatory'].label = 'Exiger un numéro de\
téléphone'
self.fields['user_solde'].label = 'Activation du solde pour\
les utilisateurs'
class EditOptionalMachineForm(ModelForm):
"""Options machines (max de machines, etc)"""
class Meta:
model = OptionalMachine
fields = '__all__'
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(EditOptionalMachineForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['password_machine'].label = "Possibilité d'attribuer un mot de passe par interface"