diff --git a/cotisations/views.py b/cotisations/views.py index 6982f590..1dfc6f7b 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -58,7 +58,7 @@ def create_cotis(vente, user, duration, date_start=False): if date_start: end_adhesion = Cotisation.objects.filter(vente__in=Vente.objects.filter(facture__in=Facture.objects.filter(user=user).exclude(valid=False))).filter(date_start__lt=date_start).aggregate(Max('date_end'))['date_end__max'] else: - end_adhesion = user.end_adhesion() + end_adhesion = user.end_adhesion date_start = date_start or timezone.now() end_adhesion = end_adhesion or date_start date_max = max(end_adhesion, date_start) @@ -102,7 +102,7 @@ def new_facture(request, userid): if art_item.cleaned_data['article'].iscotisation: create_cotis(new_vente, user, art_item.cleaned_data['article'].duration*art_item.cleaned_data['quantity']) if any(art_item.cleaned_data['article'].iscotisation for art_item in articles if art_item.cleaned_data): - messages.success(request, "La cotisation a été prolongée pour l'adhérent %s jusqu'au %s" % (user.name, user.end_adhesion()) ) + messages.success(request, "La cotisation a été prolongée pour l'adhérent %s jusqu'au %s" % (user.name, user.end_adhesion) ) else: messages.success(request, "La facture a été crée") return redirect("/users/profil/" + userid) diff --git a/freeradius_utils/authenticate_filaire.py b/freeradius_utils/authenticate_filaire.py index ad33b6f5..40c38ef2 100755 --- a/freeradius_utils/authenticate_filaire.py +++ b/freeradius_utils/authenticate_filaire.py @@ -73,7 +73,7 @@ def decide_vlan(switch_id, port_number, mac_address): room_user = User.objects.filter(room=Room.objects.filter(name=port.room)) if not room_user: return (sw_name, 'Chambre non cotisante', VLAN_NOK) - elif not room_user[0].has_access(): + elif not room_user[0].has_access: return (sw_name, 'Chambre resident desactive', VLAN_NOK) # else: user OK, on passe à la verif MAC @@ -82,7 +82,7 @@ def decide_vlan(switch_id, port_number, mac_address): interface = Interface.objects.filter(mac_address=mac_address) if not interface: return (sw_name, 'Machine inconnue', VLAN_NOK) - elif not interface[0].is_active(): + elif not interface[0].is_active: return (sw_name, 'Machine non active / adherent non cotisant', VLAN_NOK) else: return (sw_name, 'Machine OK', VLAN_OK) diff --git a/logs/templates/logs/aff_stats_general.html b/logs/templates/logs/aff_stats_general.html new file mode 100644 index 00000000..49d067d0 --- /dev/null +++ b/logs/templates/logs/aff_stats_general.html @@ -0,0 +1,42 @@ +{% 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 %} + + {% for stats in stats_list %} + + + + {% for element in stats.0 %} + + {% endfor %} + + + {% for key, stat in stats.1.items %} + + {% for item in stat %} + + {% endfor %} + + {% endfor %} +
{{ element }}
{{ item }}
+ {% endfor %} diff --git a/logs/templates/logs/stats_general.html b/logs/templates/logs/stats_general.html new file mode 100644 index 00000000..b8590df1 --- /dev/null +++ b/logs/templates/logs/stats_general.html @@ -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 générales{% endblock %} + +{% block content %} +

Statistiques générales

+ {% include "logs/aff_stats_general.html" with stats_list=stats_list %} +
+
+
+ {% endblock %} diff --git a/logs/views.py b/logs/views.py index 61e6f6e3..26e9d5c5 100644 --- a/logs/views.py +++ b/logs/views.py @@ -40,6 +40,7 @@ from reversion.models import Revision from reversion.models import Version 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 topologie.models import Switch, Port, Room @@ -107,10 +108,10 @@ def stats_general(request): '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", len([user for user in all_active_users if user.is_adherent()])], - 'connexion_users' : ["Utilisateurs bénéficiant d'une connexion", len([user for user in all_active_users if user.has_access()])], - 'ban_users' : ["Utilisateurs bannis", len([user for user in all_active_users if user.is_ban()])], - 'whitelisted_user' : ["Utilisateurs bénéficiant d'une connexion gracieuse", len([user for user in all_active_users if user.is_whitelisted()])], + '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()], }], [["Range d'ip", "Nombre d'ip totales", "Nombre d'ip utilisées", "Nombre d'ip libres"] ,ip] ] diff --git a/machines/models.py b/machines/models.py index b5775ca6..cf3baf88 100644 --- a/machines/models.py +++ b/machines/models.py @@ -27,6 +27,7 @@ from django.forms import ValidationError from macaddress.fields import MACAddressField from netaddr import mac_bare, EUI from django.core.validators import MinValueValidator,MaxValueValidator +from django.utils.functional import cached_property from re2o.settings import MAIN_EXTENSION @@ -101,11 +102,12 @@ class Interface(models.Model): type = models.ForeignKey('MachineType', on_delete=models.PROTECT) details = models.CharField(max_length=255, blank=True) + @cached_property def is_active(self): """ Renvoie si une interface doit avoir accès ou non """ machine = self.machine user = self.machine.user - return machine.active and user.has_access() + return machine.active and user.has_access def mac_bare(self): return str(EUI(self.mac_address, dialect=mac_bare)).lower() diff --git a/machines/views.py b/machines/views.py index e5f21e2a..36766e24 100644 --- a/machines/views.py +++ b/machines/views.py @@ -728,7 +728,7 @@ class JSONResponse(HttpResponse): def mac_ip_list(request): interf = Interface.objects.select_related('ipv4').select_related('domain__extension').all() interfaces = list(filter( - lambda interface: interface.ipv4 and interface.is_active(), + lambda interface: interface.ipv4 and interface.is_active, interf )) seria = InterfaceSerializer(interfaces, many=True) diff --git a/topologie/forms.py b/topologie/forms.py index 277725bc..1ac21907 100644 --- a/topologie/forms.py +++ b/topologie/forms.py @@ -30,11 +30,11 @@ class PortForm(ModelForm): class EditPortForm(ModelForm): class Meta(PortForm.Meta): - fields = ['room', 'machine_interface', 'related', 'radius', 'details'] + fields = ['room', 'related', 'radius', 'details'] - def __init__(self, *args, **kwargs): - super(EditPortForm, self).__init__(*args, **kwargs) - self.fields['related'].queryset = Port.objects.all().order_by('switch', 'port') +# def __init__(self, *args, **kwargs): +# super(EditPortForm, self).__init__(*args, **kwargs) +# self.fields['related'].queryset = Port.objects.all().order_by('switch', 'port') class AddPortForm(ModelForm): class Meta(PortForm.Meta): diff --git a/topologie/views.py b/topologie/views.py index b9b94919..b6c8611e 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -126,7 +126,7 @@ def new_port(request, switch_id): @permission_required('infra') def edit_port(request, port_id): try: - port_object = Port.objects.get(pk=port_id) + port_object = Port.objects.select_related('switch__switch_interface__domain__extension').select_related('machine_interface').select_related('room').select_related('related').get(pk=port_id) except Port.DoesNotExist: messages.error(request, u"Port inexistant") return redirect("/topologie/") diff --git a/users/models.py b/users/models.py index 6b5f4082..ee4394df 100644 --- a/users/models.py +++ b/users/models.py @@ -26,6 +26,7 @@ from django.forms import ModelForm, Form from django import forms from django.db.models.signals import post_save, post_delete from django.dispatch import receiver +from django.utils.functional import cached_property import ldapdb.models import ldapdb.models.fields @@ -88,6 +89,17 @@ def get_admin_right(): admin_right.save() return admin_right +def all_adherent(): + return User.objects.filter(facture__in=Facture.objects.filter(vente__in=Vente.objects.filter(cotisation__in=Cotisation.objects.filter(vente__in=Vente.objects.filter(facture__in=Facture.objects.all().exclude(valid=False))).filter(date_end__gt=timezone.now())))).distinct() + +def all_baned(): + return User.objects.filter(ban__in=Ban.objects.filter(date_end__gt=timezone.now())).distinct() + +def all_whitelisted(): + return User.objects.filter(whitelist__in=Whitelist.objects.filter(date_end__gt=timezone.now())).distinct() + +def all_has_access(): + return User.objects.filter(Q(state=User.STATE_ACTIVE) & ~Q(ban__in=Ban.objects.filter(date_end__gt=timezone.now())) & (Q(whitelist__in=Whitelist.objects.filter(date_end__gt=timezone.now())) | Q(facture__in=Facture.objects.filter(vente__in=Vente.objects.filter(cotisation__in=Cotisation.objects.filter(vente__in=Vente.objects.filter(facture__in=Facture.objects.all().exclude(valid=False))).filter(date_end__gt=timezone.now())))))).distinct() class UserManager(BaseUserManager): def _create_user(self, pseudo, name, surname, email, password=None, su=False): @@ -204,12 +216,14 @@ class User(AbstractBaseUser): def has_perm(self, perm, obj=None): return True + @cached_property def end_adhesion(self): date_max = Cotisation.objects.filter(vente__in=Vente.objects.filter(facture__in=Facture.objects.filter(user=self).exclude(valid=False))).aggregate(models.Max('date_end'))['date_end__max'] return date_max + @cached_property def is_adherent(self): - end = self.end_adhesion() + end = self.end_adhesion if not end: return False elif end < timezone.now(): @@ -217,19 +231,22 @@ class User(AbstractBaseUser): else: return True + @cached_property def end_ban(self): """ Renvoie la date de fin de ban d'un user, False sinon """ date_max = Ban.objects.filter(user=self).aggregate(models.Max('date_end'))['date_end__max'] return date_max + @cached_property def end_whitelist(self): """ Renvoie la date de fin de whitelist d'un user, False sinon """ date_max = Whitelist.objects.filter(user=self).aggregate(models.Max('date_end'))['date_end__max'] return date_max + @cached_property def is_ban(self): """ Renvoie si un user est banni ou non """ - end = self.end_ban() + end = self.end_ban if not end: return False elif end < timezone.now(): @@ -237,9 +254,10 @@ class User(AbstractBaseUser): else: return True + @cached_property def is_whitelisted(self): """ Renvoie si un user est whitelisté ou non """ - end = self.end_whitelist() + end = self.end_whitelist if not end: return False elif end < timezone.now(): @@ -247,23 +265,25 @@ class User(AbstractBaseUser): else: return True + @cached_property def has_access(self): """ Renvoie si un utilisateur a accès à internet """ return self.state == User.STATE_ACTIVE \ - and not self.is_ban() and (self.is_adherent() or self.is_whitelisted()) + and not self.is_ban and (self.is_adherent or self.is_whitelisted) + @cached_property def end_access(self): """ Renvoie la date de fin normale d'accès (adhésion ou whiteliste)""" - if not self.end_adhesion(): - if not self.end_whitelist(): + if not self.end_adhesion: + if not self.end_whitelist: return None else: - return self.end_whitelist() + return self.end_whitelist else: - if not self.end_whitelist(): - return self.end_adhesion() + if not self.end_whitelist: + return self.end_adhesion else: - return max(self.end_adhesion(), self.end_whitelist()) + return max(self.end_adhesion, self.end_whitelist) def user_interfaces(self): return Interface.objects.filter(machine__in=Machine.objects.filter(user=self, active=True)) @@ -293,7 +313,7 @@ class User(AbstractBaseUser): if base: user_ldap.name = self.pseudo user_ldap.sn = self.pseudo - user_ldap.dialupAccess = str(self.has_access()) + user_ldap.dialupAccess = str(self.has_access) user_ldap.home_directory = '/home/' + self.pseudo user_ldap.mail = self.email user_ldap.given_name = str(self.surname).lower() + '_' + str(self.name).lower()[:3] @@ -303,7 +323,7 @@ class User(AbstractBaseUser): if self.shell: user_ldap.login_shell = self.shell.shell if access_refresh: - user_ldap.dialupAccess = str(self.has_access()) + user_ldap.dialupAccess = str(self.has_access) if mac_refresh: user_ldap.macs = [inter.mac_bare() for inter in Interface.objects.filter(machine__in=Machine.objects.filter(user=self))] user_ldap.save() diff --git a/users/views.py b/users/views.py index 505828ba..11edd4bc 100644 --- a/users/views.py +++ b/users/views.py @@ -272,7 +272,7 @@ def add_ban(request, userid): reversion.set_comment("Création") messages.success(request, "Bannissement ajouté") return redirect("/users/profil/" + userid) - if user.is_ban(): + if user.is_ban: messages.error( request, "Attention, cet utilisateur a deja un bannissement actif" @@ -318,7 +318,7 @@ def add_whitelist(request, userid): reversion.set_comment("Création") messages.success(request, "Accès à titre gracieux accordé") return redirect("/users/profil/" + userid) - if user.is_whitelisted(): + if user.is_whitelisted: messages.error( request, "Attention, cet utilisateur a deja un accès gracieux actif" @@ -463,7 +463,7 @@ def mass_archive(request): to_archive_list = [] if to_archive_date.is_valid(): date = to_archive_date.cleaned_data['date'] - to_archive_list = [user for user in User.objects.exclude(state=User.STATE_ARCHIVE) if not user.end_access() or user.end_access() < date] + to_archive_list = [user for user in User.objects.exclude(state=User.STATE_ARCHIVE) if not user.end_access or user.end_access < date] if "valider" in request.POST: for user in to_archive_list: archive(user)