diff --git a/logs/urls.py b/logs/urls.py index 3bb41c4a..11009835 100644 --- a/logs/urls.py +++ b/logs/urls.py @@ -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[0-9]+)$', views.revert_action, name='revert-action'), + url(r'^revert_action/(?P[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'), diff --git a/logs/views.py b/logs/views.py index d84a2f43..13879c86 100644 --- a/logs/views.py +++ b/logs/views.py @@ -23,62 +23,68 @@ # 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, all_has_access +from users.models import all_whitelisted, all_baned, all_adherent +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 machines.views import all_active_assigned_interfaces_count +from machines.views import all_active_interfaces_count from topologie.models import Switch, Port, Room from preferences.models import GeneralOption +from re2o.views import form -from django.utils import timezone -from dateutil.relativedelta import relativedelta 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 +93,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 +101,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 +141,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 +155,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})