From ca497d753df9aec3296f825f816db467ecf0d012 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sat, 14 Oct 2017 01:56:14 +0200 Subject: [PATCH 1/9] Fix pep8 --- topologie/views.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/topologie/views.py b/topologie/views.py index ceb06f0a..12732422 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -50,7 +50,8 @@ from topologie.forms import EditPortForm, NewSwitchForm, EditSwitchForm from topologie.forms import AddPortForm, EditRoomForm, StackForm from users.views import form -from machines.forms import AliasForm, NewMachineForm, EditMachineForm, EditInterfaceForm, AddInterfaceForm +from machines.forms import AliasForm, NewMachineForm, EditMachineForm +from machines.forms import EditInterfaceForm, AddInterfaceForm from machines.views import generate_ipv4_bft_param from preferences.models import AssoOption, GeneralOption @@ -381,8 +382,15 @@ def new_switch(request): reversion.set_comment("Création") messages.success(request, "Le switch a été créé") return redirect("/topologie/") - i_bft_param = generate_ipv4_bft_param( interface, False ) - return form({'topoform':switch, 'machineform': machine, 'interfaceform': interface, 'domainform': domain, 'i_bft_param': i_bft_param}, 'topologie/switch.html', request) + i_bft_param = generate_ipv4_bft_param(interface, False) + return form({ + 'topoform': switch, + 'machineform': machine, + 'interfaceform': interface, + 'domainform': domain, + 'i_bft_param': i_bft_param + }, 'topologie/switch.html', request) + @login_required @permission_required('infra') @@ -442,8 +450,15 @@ def edit_switch(request, switch_id): ) messages.success(request, "Le switch a bien été modifié") return redirect("/topologie/") - i_bft_param = generate_ipv4_bft_param( interface_form, False ) - return form({'topoform':switch_form, 'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form, 'i_bft_param': i_bft_param}, 'topologie/switch.html', request) + i_bft_param = generate_ipv4_bft_param(interface_form, False) + return form({ + 'topoform': switch_form, + 'machineform': machine_form, + 'interfaceform': interface_form, + 'domainform': domain_form, + 'i_bft_param': i_bft_param + }, 'topologie/switch.html', request) + @login_required @permission_required('infra') From d8479f97b3a5a5545db5277287912e29f49ac1b8 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sat, 14 Oct 2017 04:17:42 +0200 Subject: [PATCH 2/9] Docstrings et pep8 sur logs --- logs/urls.py | 9 +- logs/views.py | 338 +++++++++++++++++++++++++++++++------------------- 2 files changed, 215 insertions(+), 132 deletions(-) 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}) From af26948adf5b9256b22d139d557107839677f3a3 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sat, 14 Oct 2017 04:48:43 +0200 Subject: [PATCH 3/9] Pylintage again --- re2o/context_processors.py | 14 +++++++++----- re2o/urls.py | 10 ++++++++-- re2o/views.py | 11 +++++++---- re2o/wsgi.py | 5 +++-- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/re2o/context_processors.py b/re2o/context_processors.py index ed4769b5..e562a347 100644 --- a/re2o/context_processors.py +++ b/re2o/context_processors.py @@ -19,15 +19,19 @@ # 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. +"""Fonction de context, variables renvoyées à toutes les vues""" + from __future__ import unicode_literals -from machines.models import Interface, Machine from preferences.models import GeneralOption, OptionalMachine + def context_user(request): - general_options, created = GeneralOption.objects.get_or_create() - machine_options, created = OptionalMachine.objects.get_or_create() + """Fonction de context lorsqu'un user est logué (ou non), + renvoie les infos sur l'user, la liste de ses droits, ses machines""" + general_options, _created = GeneralOption.objects.get_or_create() + machine_options, _created = OptionalMachine.objects.get_or_create() user = request.user if user.is_authenticated(): interfaces = user.user_interfaces() @@ -52,8 +56,8 @@ def context_user(request): 'is_bofh': is_bofh, 'is_trez': is_trez, 'is_infra': is_infra, - 'is_admin' : is_admin, + 'is_admin': is_admin, 'interfaces': interfaces, 'site_name': general_options.site_name, - 'ipv6_enabled' : machine_options.ipv6, + 'ipv6_enabled': machine_options.ipv6, } diff --git a/re2o/urls.py b/re2o/urls.py index 5fd45f85..775b87ec 100644 --- a/re2o/urls.py +++ b/re2o/urls.py @@ -49,10 +49,16 @@ urlpatterns = [ url(r'^admin/', include(admin.site.urls)), url(r'^users/', include('users.urls', namespace='users')), url(r'^search/', include('search.urls', namespace='search')), - url(r'^cotisations/', include('cotisations.urls', namespace='cotisations')), + url( + r'^cotisations/', + include('cotisations.urls', namespace='cotisations') + ), url(r'^machines/', include('machines.urls', namespace='machines')), url(r'^topologie/', include('topologie.urls', namespace='topologie')), url(r'^logs/', include('logs.urls', namespace='logs')), - url(r'^preferences/', include('preferences.urls', namespace='preferences')), + url( + r'^preferences/', + include('preferences.urls', namespace='preferences') + ), ] diff --git a/re2o/views.py b/re2o/views.py index 77a36418..9cab6273 100644 --- a/re2o/views.py +++ b/re2o/views.py @@ -19,25 +19,28 @@ # 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. +""" +Fonctions de la page d'accueil et diverses fonctions utiles pour tous +les views +""" from __future__ import unicode_literals from django.shortcuts import render -from django.shortcuts import get_object_or_404 from django.template.context_processors import csrf -from django.template import Context, RequestContext, loader from preferences.models import Service + def form(ctx, template, request): + """Form générique, raccourci importé par les fonctions views du site""" context = ctx context.update(csrf(request)) return render(request, template, context) def index(request): - i = 0 + """Affiche la liste des services sur la page d'accueil de re2o""" services = [[], [], []] for indice, serv in enumerate(Service.objects.all()): services[indice % 3].append(serv) - return form({'services_urls': services}, 're2o/index.html', request) diff --git a/re2o/wsgi.py b/re2o/wsgi.py index 70108566..deb6b330 100644 --- a/re2o/wsgi.py +++ b/re2o/wsgi.py @@ -32,9 +32,10 @@ https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ from __future__ import unicode_literals import os -from django.core.wsgi import get_wsgi_application -from os.path import dirname import sys +from os.path import dirname +from django.core.wsgi import get_wsgi_application + sys.path.append(dirname(dirname(__file__))) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "re2o.settings") From 51a8b66cd4425aadb475fa4a8bdcad405406946f Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sat, 14 Oct 2017 06:03:53 +0200 Subject: [PATCH 4/9] Pep8 et nettoyage, et doc pour l'app preferences --- preferences/admin.py | 22 +++++++- preferences/forms.py | 113 +++++++++++++++++++++++++++++--------- preferences/models.py | 74 ++++++++++++++++++++----- preferences/urls.py | 51 +++++++++++++++--- preferences/views.py | 123 ++++++++++++++++++++++++++++-------------- 5 files changed, 295 insertions(+), 88 deletions(-) diff --git a/preferences/admin.py b/preferences/admin.py index a8ce9335..96b4d9e1 100644 --- a/preferences/admin.py +++ b/preferences/admin.py @@ -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) diff --git a/preferences/forms.py b/preferences/forms.py index 887d768d..51cbb885 100644 --- a/preferences/forms.py +++ b/preferences/forms.py @@ -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" - self.fields['max_lambdauser_interfaces'].label = "Maximum d'interfaces autorisées pour un user normal" - self.fields['max_lambdauser_aliases'].label = "Maximum d'alias dns autorisés pour un user normal" + super(EditOptionalMachineForm, self).__init__( + *args, + prefix=prefix, + **kwargs + ) + self.fields['password_machine'].label = "Possibilité d'attribuer\ + un mot de passe par interface" + self.fields['max_lambdauser_interfaces'].label = "Maximum\ + d'interfaces autorisées pour un user normal" + self.fields['max_lambdauser_aliases'].label = "Maximum d'alias\ + dns autorisés pour un user normal" + class EditOptionalTopologieForm(ModelForm): + """Options de topologie, formulaire d'edition (vlan par default etc)""" class Meta: model = OptionalTopologie fields = '__all__' def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) - super(EditOptionalTopologieForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['vlan_decision_ok'].label = "Vlan où placer les machines après acceptation RADIUS" - self.fields['vlan_decision_nok'].label = "Vlan où placer les machines après rejet RADIUS" + super(EditOptionalTopologieForm, self).__init__( + *args, + prefix=prefix, + **kwargs + ) + self.fields['vlan_decision_ok'].label = "Vlan où placer les\ + machines après acceptation RADIUS" + self.fields['vlan_decision_nok'].label = "Vlan où placer les\ + machines après rejet RADIUS" + class EditGeneralOptionForm(ModelForm): + """Options générales (affichages de résultats de recherche, etc)""" class Meta: model = GeneralOption fields = '__all__' def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) - super(EditGeneralOptionForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['search_display_page'].label = 'Resultats affichés dans une recherche' - self.fields['pagination_number'].label = 'Items par page, taille normale (ex users)' - self.fields['pagination_large_number'].label = 'Items par page, taille élevée (machines)' - self.fields['req_expire_hrs'].label = 'Temps avant expiration du lien de reinitialisation de mot de passe (en heures)' + super(EditGeneralOptionForm, self).__init__( + *args, + prefix=prefix, + **kwargs + ) + self.fields['search_display_page'].label = 'Resultats\ + affichés dans une recherche' + self.fields['pagination_number'].label = 'Items par page,\ + taille normale (ex users)' + self.fields['pagination_large_number'].label = 'Items par page,\ + taille élevée (machines)' + self.fields['req_expire_hrs'].label = 'Temps avant expiration du lien\ + de reinitialisation de mot de passe (en heures)' self.fields['site_name'].label = 'Nom du site web' - self.fields['email_from'].label = 'Adresse mail d\'expedition automatique' + self.fields['email_from'].label = "Adresse mail d\ + 'expedition automatique" + class EditAssoOptionForm(ModelForm): + """Options de l'asso (addresse, telephone, etc)""" class Meta: model = AssoOption fields = '__all__' def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) - super(EditAssoOptionForm, self).__init__(*args, prefix=prefix, **kwargs) + super(EditAssoOptionForm, self).__init__( + *args, + prefix=prefix, + **kwargs + ) self.fields['name'].label = 'Nom de l\'asso' self.fields['siret'].label = 'SIRET' self.fields['adresse1'].label = 'Adresse (ligne 1)' @@ -91,20 +136,31 @@ class EditAssoOptionForm(ModelForm): self.fields['contact'].label = 'Email de contact' self.fields['telephone'].label = 'Numéro de téléphone' self.fields['pseudo'].label = 'Pseudo d\'usage' - self.fields['utilisateur_asso'].label = 'Compte utilisé pour faire les modifications depuis /admin' + self.fields['utilisateur_asso'].label = 'Compte utilisé pour\ + faire les modifications depuis /admin' + class EditMailMessageOptionForm(ModelForm): + """Formulaire d'edition des messages de bienvenue personnalisés""" class Meta: model = MailMessageOption fields = '__all__' def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) - super(EditMailMessageOptionForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['welcome_mail_fr'].label = 'Message dans le mail de bienvenue en français' - self.fields['welcome_mail_en'].label = 'Message dans le mail de bienvenue en anglais' + super(EditMailMessageOptionForm, self).__init__( + *args, + prefix=prefix, + **kwargs + ) + self.fields['welcome_mail_fr'].label = 'Message dans le\ + mail de bienvenue en français' + self.fields['welcome_mail_en'].label = 'Message dans le\ + mail de bienvenue en anglais' + class ServiceForm(ModelForm): + """Edition, ajout de services sur la page d'accueil""" class Meta: model = Service fields = '__all__' @@ -113,6 +169,11 @@ class ServiceForm(ModelForm): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(ServiceForm, self).__init__(*args, prefix=prefix, **kwargs) -class DelServiceForm(Form): - services = forms.ModelMultipleChoiceField(queryset=Service.objects.all(), label="Enregistrements service actuels", widget=forms.CheckboxSelectMultiple) +class DelServiceForm(Form): + """Suppression de services sur la page d'accueil""" + services = forms.ModelMultipleChoiceField( + queryset=Service.objects.all(), + label="Enregistrements service actuels", + widget=forms.CheckboxSelectMultiple + ) diff --git a/preferences/models.py b/preferences/models.py index 34c4c0b1..dc1412e7 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -20,26 +20,38 @@ # 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. - +""" +Reglages généraux, machines, utilisateurs, mail, general pour l'application. +""" from __future__ import unicode_literals from django.db import models from cotisations.models import Paiement -from machines.models import Vlan + class OptionalUser(models.Model): + """Options pour l'user : obligation ou nom du telephone, + activation ou non du solde, autorisation du negatif, fingerprint etc""" PRETTY_NAME = "Options utilisateur" is_tel_mandatory = models.BooleanField(default=True) user_solde = models.BooleanField(default=False) - solde_negatif = models.DecimalField(max_digits=5, decimal_places=2, default=0) + solde_negatif = models.DecimalField( + max_digits=5, + decimal_places=2, + default=0 + ) gpg_fingerprint = models.BooleanField(default=True) def clean(self): + """Creation du mode de paiement par solde""" if self.user_solde: Paiement.objects.get_or_create(moyen="Solde") + class OptionalMachine(models.Model): + """Options pour les machines : maximum de machines ou d'alias par user + sans droit, activation de l'ipv6""" PRETTY_NAME = "Options machines" password_machine = models.BooleanField(default=False) @@ -47,21 +59,43 @@ class OptionalMachine(models.Model): max_lambdauser_aliases = models.IntegerField(default=10) ipv6 = models.BooleanField(default=False) + class OptionalTopologie(models.Model): + """Reglages pour la topologie : mode d'accès radius, vlan où placer + les machines en accept ou reject""" PRETTY_NAME = "Options topologie" MACHINE = 'MACHINE' DEFINED = 'DEFINED' CHOICE_RADIUS = ( - (MACHINE, 'Sur le vlan de la plage ip machine'), - (DEFINED, 'Prédéfini dans "Vlan où placer les machines après acceptation RADIUS"'), + (MACHINE, 'Sur le vlan de la plage ip machine'), + (DEFINED, 'Prédéfini dans "Vlan où placer les machines\ + après acceptation RADIUS"'), + ) + + radius_general_policy = models.CharField( + max_length=32, + choices=CHOICE_RADIUS, + default='DEFINED' + ) + vlan_decision_ok = models.OneToOneField( + 'machines.Vlan', + on_delete=models.PROTECT, + related_name='decision_ok', + blank=True, + null=True + ) + vlan_decision_nok = models.OneToOneField( + 'machines.Vlan', + on_delete=models.PROTECT, + related_name='decision_nok', + blank=True, + null=True ) - radius_general_policy = models.CharField(max_length=32, choices=CHOICE_RADIUS, default='DEFINED') - vlan_decision_ok = models.OneToOneField('machines.Vlan', on_delete=models.PROTECT, related_name='decision_ok', blank=True, null=True) - vlan_decision_nok = models.OneToOneField('machines.Vlan', on_delete=models.PROTECT, related_name='decision_nok', blank=True, null=True) - class GeneralOption(models.Model): + """Options générales : nombre de resultats par page, nom du site, + temps où les liens sont valides""" PRETTY_NAME = "Options générales" search_display_page = models.IntegerField(default=15) @@ -71,30 +105,44 @@ class GeneralOption(models.Model): site_name = models.CharField(max_length=32, default="Re2o") email_from = models.EmailField(default="www-data@serveur.net") + class Service(models.Model): + """Liste des services affichés sur la page d'accueil : url, description, + image et nom""" name = models.CharField(max_length=32) url = models.URLField() description = models.TextField() - image = models.ImageField(upload_to='logo', blank=True) + image = models.ImageField(upload_to='logo', blank=True) def __str__(self): return str(self.name) + class AssoOption(models.Model): + """Options générales de l'asso : siret, addresse, nom, etc""" PRETTY_NAME = "Options de l'association" - name = models.CharField(default="Association réseau école machin", max_length=256) + name = models.CharField( + default="Association réseau école machin", + max_length=256 + ) siret = models.CharField(default="00000000000000", max_length=32) adresse1 = models.CharField(default="1 Rue de exemple", max_length=128) adresse2 = models.CharField(default="94230 Cachan", max_length=128) contact = models.EmailField(default="contact@example.org") telephone = models.CharField(max_length=15, default="0000000000") pseudo = models.CharField(default="Asso", max_length=32) - utilisateur_asso = models.OneToOneField('users.User', on_delete=models.PROTECT, blank=True, null=True) + utilisateur_asso = models.OneToOneField( + 'users.User', + on_delete=models.PROTECT, + blank=True, + null=True + ) + class MailMessageOption(models.Model): + """Reglages, mail de bienvenue et autre""" PRETTY_NAME = "Options de corps de mail" welcome_mail_fr = models.TextField(default="") welcome_mail_en = models.TextField(default="") - diff --git a/preferences/urls.py b/preferences/urls.py index 624d2e75..2169f83c 100644 --- a/preferences/urls.py +++ b/preferences/urls.py @@ -19,6 +19,9 @@ # 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 preferences, pointant vers les fonctions de views +""" from __future__ import unicode_literals @@ -28,15 +31,47 @@ from . import views urlpatterns = [ - url(r'^edit_options/(?P
OptionalUser)$', views.edit_options, name='edit-options'), - url(r'^edit_options/(?P
OptionalMachine)$', views.edit_options, name='edit-options'), - url(r'^edit_options/(?P
OptionalTopologie)$', views.edit_options, name='edit-options'), - url(r'^edit_options/(?P
GeneralOption)$', views.edit_options, name='edit-options'), - url(r'^edit_options/(?P
AssoOption)$', views.edit_options, name='edit-options'), - url(r'^edit_options/(?P
MailMessageOption)$', views.edit_options, name='edit-options'), + url( + r'^edit_options/(?P
OptionalUser)$', + views.edit_options, + name='edit-options' + ), + url( + r'^edit_options/(?P
OptionalMachine)$', + views.edit_options, + name='edit-options' + ), + url( + r'^edit_options/(?P
OptionalTopologie)$', + views.edit_options, + name='edit-options' + ), + url( + r'^edit_options/(?P
GeneralOption)$', + views.edit_options, + name='edit-options' + ), + url( + r'^edit_options/(?P
AssoOption)$', + views.edit_options, + name='edit-options' + ), + url( + r'^edit_options/(?P
MailMessageOption)$', + views.edit_options, + name='edit-options' + ), url(r'^add_services/$', views.add_services, name='add-services'), - url(r'^edit_services/(?P[0-9]+)$', views.edit_services, name='edit-services'), + url( + r'^edit_services/(?P[0-9]+)$', + views.edit_services, + name='edit-services' + ), url(r'^del_services/$', views.del_services, name='del-services'), - url(r'^history/(?Pservice)/(?P[0-9]+)$', views.history, name='history'), + url( + r'^history/(?Pservice)/(?P[0-9]+)$', + views.history, + name='history' + ), url(r'^$', views.display_options, name='display-options'), ] diff --git a/preferences/views.py b/preferences/views.py index 5fe1cff5..1e2c433e 100644 --- a/preferences/views.py +++ b/preferences/views.py @@ -23,48 +23,53 @@ # App de gestion des machines pour re2o # Gabriel Détraz, Augustin Lemesle # Gplv2 +""" +Vue d'affichage, et de modification des réglages (réglages machine, +topologie, users, service...) +""" from __future__ import unicode_literals -from django.shortcuts import render -from django.shortcuts import get_object_or_404, render, redirect -from django.template.context_processors import csrf +from django.shortcuts import render, redirect 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 Max, ProtectedError -from django.db import IntegrityError -from django.core.mail import send_mail -from django.utils import timezone -from django.core.urlresolvers import reverse +from django.db.models import ProtectedError from django.db import transaction from reversion.models import Version from reversion import revisions as reversion +from re2o.views import form from .forms import ServiceForm, DelServiceForm -from .models import Service, OptionalUser, OptionalMachine, AssoOption, MailMessageOption, GeneralOption, OptionalTopologie +from .models import Service, OptionalUser, OptionalMachine, AssoOption +from .models import MailMessageOption, GeneralOption, OptionalTopologie from . import models from . import forms -def form(ctx, template, request): - c = ctx - c.update(csrf(request)) - return render(request, template, c) - @login_required @permission_required('cableur') def display_options(request): - useroptions, created = OptionalUser.objects.get_or_create() - machineoptions, created = OptionalMachine.objects.get_or_create() - topologieoptions, created = OptionalTopologie.objects.get_or_create() - generaloptions, created = GeneralOption.objects.get_or_create() - assooptions, created = AssoOption.objects.get_or_create() - mailmessageoptions, created = MailMessageOption.objects.get_or_create() + """Vue pour affichage des options (en vrac) classé selon les models + correspondants dans un tableau""" + useroptions, _created = OptionalUser.objects.get_or_create() + machineoptions, _created = OptionalMachine.objects.get_or_create() + topologieoptions, _created = OptionalTopologie.objects.get_or_create() + generaloptions, _created = GeneralOption.objects.get_or_create() + assooptions, _created = AssoOption.objects.get_or_create() + mailmessageoptions, _created = MailMessageOption.objects.get_or_create() service_list = Service.objects.all() - return form({'useroptions': useroptions, 'machineoptions': machineoptions, 'topologieoptions': topologieoptions, 'generaloptions': generaloptions, 'assooptions' : assooptions, 'mailmessageoptions' : mailmessageoptions, 'service_list':service_list}, 'preferences/display_preferences.html', request) + return form({ + 'useroptions': useroptions, + 'machineoptions': machineoptions, + 'topologieoptions': topologieoptions, + 'generaloptions': generaloptions, + 'assooptions': assooptions, + 'mailmessageoptions': mailmessageoptions, + 'service_list': service_list + }, 'preferences/display_preferences.html', request) + @login_required @permission_required('admin') @@ -73,23 +78,36 @@ def edit_options(request, section): model = getattr(models, section, None) form_instance = getattr(forms, 'Edit' + section + 'Form', None) if model and form: - options_instance, created = model.objects.get_or_create() - options = form_instance(request.POST or None, instance=options_instance) + options_instance, _created = model.objects.get_or_create() + options = form_instance( + request.POST or None, + instance=options_instance + ) if options.is_valid(): with transaction.atomic(), reversion.create_revision(): options.save() reversion.set_user(request.user) - reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in options.changed_data)) + reversion.set_comment( + "Champs modifié(s) : %s" % ', '.join( + field for field in options.changed_data + ) + ) messages.success(request, "Préférences modifiées") return redirect("/preferences/") - return form({'options': options}, 'preferences/edit_preferences.html', request) + return form( + {'options': options}, + 'preferences/edit_preferences.html', + request + ) else: messages.error(request, "Objet inconnu") return redirect("/preferences/") + @login_required @permission_required('admin') def add_services(request): + """Ajout d'un service de la page d'accueil""" services = ServiceForm(request.POST or None) if services.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -98,29 +116,45 @@ def add_services(request): reversion.set_comment("Création") messages.success(request, "Cet enregistrement ns a été ajouté") return redirect("/preferences/") - return form({'preferenceform': services}, 'preferences/preferences.html', request) + return form( + {'preferenceform': services}, + 'preferences/preferences.html', + request + ) + @login_required @permission_required('admin') def edit_services(request, servicesid): + """Edition des services affichés sur la page d'accueil""" try: services_instance = Service.objects.get(pk=servicesid) except Service.DoesNotExist: - messages.error(request, u"Entrée inexistante" ) + messages.error(request, u"Entrée inexistante") return redirect("/preferences/") services = ServiceForm(request.POST or None, instance=services_instance) if services.is_valid(): with transaction.atomic(), reversion.create_revision(): services.save() reversion.set_user(request.user) - reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in services.changed_data)) + reversion.set_comment( + "Champs modifié(s) : %s" % ', '.join( + field for field in services.changed_data + ) + ) messages.success(request, "Service modifié") return redirect("/preferences/") - return form({'preferenceform': services}, 'preferences/preferences.html', request) + return form( + {'preferenceform': services}, + 'preferences/preferences.html', + request + ) + @login_required @permission_required('admin') def del_services(request): + """Suppression d'un service de la page d'accueil""" services = DelServiceForm(request.POST or None) if services.is_valid(): services_dels = services.cleaned_data['services'] @@ -131,20 +165,28 @@ def del_services(request): reversion.set_user(request.user) messages.success(request, "Le services a été supprimée") except ProtectedError: - messages.error(request, "Erreur le service suivant %s ne peut être supprimé" % services_del) + messages.error(request, "Erreur le service\ + suivant %s ne peut être supprimé" % services_del) return redirect("/preferences/") - return form({'preferenceform': services}, 'preferences/preferences.html', request) + return form( + {'preferenceform': services}, + 'preferences/preferences.html', + request + ) + @login_required @permission_required('cableur') -def history(request, object, id): - if object == 'service': +def history(request, object_name, object_id): + """Historique de creation et de modification d'un service affiché sur + la page d'accueil""" + if object_name == 'service': try: - object_instance = Service.objects.get(pk=id) + object_instance = Service.objects.get(pk=object_id) except Service.DoesNotExist: - messages.error(request, "Service inexistant") - return redirect("/preferences/") - options, created = GeneralOption.objects.get_or_create() + messages.error(request, "Service inexistant") + return redirect("/preferences/") + options, _created = GeneralOption.objects.get_or_create() pagination_number = options.pagination_number reversions = Version.objects.get_for_object(object_instance) paginator = Paginator(reversions, pagination_number) @@ -157,4 +199,7 @@ def history(request, object, id): except EmptyPage: # If page is out of range (e.g. 9999), deliver last page of results. reversions = paginator.page(paginator.num_pages) - return render(request, 're2o/history.html', {'reversions': reversions, 'object': object_instance}) + return render(request, 're2o/history.html', { + 'reversions': reversions, + 'object': object_instance + }) From e9855c78673da6805f97f9674a7540d7ead474f7 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sat, 14 Oct 2017 20:18:12 +0200 Subject: [PATCH 5/9] Doc et grosse review pep8 --- users/forms.py | 215 +++++++++++++--- users/models.py | 650 ++++++++++++++++++++++++++++++++++++------------ 2 files changed, 665 insertions(+), 200 deletions(-) diff --git a/users/forms.py b/users/forms.py index a8b1a219..fd81b426 100644 --- a/users/forms.py +++ b/users/forms.py @@ -20,8 +20,16 @@ # 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. +""" +Definition des forms pour l'application users. -# -*- coding: utf-8 -*- +Modification, creation de : + - un user (informations personnelles) + - un bannissement + - le mot de passe d'un user + - une whiteliste + - un user de service +""" from __future__ import unicode_literals @@ -29,17 +37,34 @@ from django import forms from django.forms import ModelForm, Form from django.contrib.auth.forms import ReadOnlyPasswordHashField from django.core.validators import MinLengthValidator -from preferences.models import OptionalUser from django.utils import timezone -from .models import User, ServiceUser, Right, School, ListRight, Whitelist, Ban, Request, remove_user_room -from .models import get_admin_right +from preferences.models import OptionalUser +from .models import User, ServiceUser, Right, School, ListRight, Whitelist +from .models import Ban, remove_user_room + +NOW = timezone.now() + class PassForm(forms.Form): - passwd1 = forms.CharField(label=u'Nouveau mot de passe', max_length=255, validators=[MinLengthValidator(8)], widget=forms.PasswordInput) - passwd2 = forms.CharField(label=u'Saisir à nouveau le mot de passe', max_length=255, validators=[MinLengthValidator(8)], widget=forms.PasswordInput) + """Formulaire de changement de mot de passe. Verifie que les 2 + nouveaux mots de passe renseignés sont identiques et respectent + une norme""" + passwd1 = forms.CharField( + label=u'Nouveau mot de passe', + max_length=255, + validators=[MinLengthValidator(8)], + widget=forms.PasswordInput + ) + passwd2 = forms.CharField( + label=u'Saisir à nouveau le mot de passe', + max_length=255, + validators=[MinLengthValidator(8)], + widget=forms.PasswordInput + ) def clean_passwd2(self): + """Verifie que passwd1 et 2 sont identiques""" # Check that the two password entries match password1 = self.cleaned_data.get("passwd1") password2 = self.cleaned_data.get("passwd2") @@ -47,11 +72,26 @@ class PassForm(forms.Form): raise forms.ValidationError("Passwords don't match") return password2 + class UserCreationForm(forms.ModelForm): """A form for creating new users. Includes all the required - fields, plus a repeated password.""" - password1 = forms.CharField(label='Password', widget=forms.PasswordInput, validators=[MinLengthValidator(8)], max_length=255) - password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput, validators=[MinLengthValidator(8)], max_length=255) + fields, plus a repeated password. + + Formulaire pour la création d'un user. N'est utilisé que pour + l'admin, lors de la creation d'un user par admin. Inclu tous les + champs obligatoires""" + password1 = forms.CharField( + label='Password', + widget=forms.PasswordInput, + validators=[MinLengthValidator(8)], + max_length=255 + ) + password2 = forms.CharField( + label='Password confirmation', + widget=forms.PasswordInput, + validators=[MinLengthValidator(8)], + max_length=255 + ) is_admin = forms.BooleanField(label='is admin') def __init__(self, *args, **kwargs): @@ -63,6 +103,7 @@ class UserCreationForm(forms.ModelForm): fields = ('pseudo', 'name', 'surname', 'email') def clean_password2(self): + """Verifie que password1 et 2 sont identiques""" # Check that the two password entries match password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") @@ -78,21 +119,40 @@ class UserCreationForm(forms.ModelForm): user.is_admin = self.cleaned_data.get("is_admin") return user + class ServiceUserCreationForm(forms.ModelForm): """A form for creating new users. Includes all the required - fields, plus a repeated password.""" - password1 = forms.CharField(label='Password', widget=forms.PasswordInput, min_length=8, max_length=255) - password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput, min_length=8, max_length=255) + fields, plus a repeated password. + + Formulaire pour la creation de nouveaux serviceusers. + Requiert seulement un mot de passe; et un pseudo""" + password1 = forms.CharField( + label='Password', + widget=forms.PasswordInput, + min_length=8, + max_length=255 + ) + password2 = forms.CharField( + label='Password confirmation', + widget=forms.PasswordInput, + min_length=8, + max_length=255 + ) def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) - super(ServiceUserCreationForm, self).__init__(*args, prefix=prefix, **kwargs) + super(ServiceUserCreationForm, self).__init__( + *args, + prefix=prefix, + **kwargs + ) class Meta: model = ServiceUser fields = ('pseudo',) def clean_password2(self): + """Verifie que password1 et 2 sont indentiques""" # Check that the two password entries match password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") @@ -107,10 +167,13 @@ class ServiceUserCreationForm(forms.ModelForm): user.save() return user + class UserChangeForm(forms.ModelForm): """A form for updating users. Includes all the fields on the user, but replaces the password field with admin's password hash display field. + + Formulaire pour la modification d'un user coté admin """ password = ReadOnlyPasswordHashField() is_admin = forms.BooleanField(label='is admin', required=False) @@ -126,6 +189,7 @@ class UserChangeForm(forms.ModelForm): self.initial['is_admin'] = kwargs['instance'].is_admin def clean_password(self): + """Dummy fun""" # Regardless of what the user provides, return the initial value. # This is done here, rather than on the field, because the # field does not have access to the initial value @@ -139,42 +203,59 @@ class UserChangeForm(forms.ModelForm): user.save() return user + class ServiceUserChangeForm(forms.ModelForm): """A form for updating users. Includes all the fields on the user, but replaces the password field with admin's password hash display field. + + Formulaire pour l'edition des service users coté admin """ password = ReadOnlyPasswordHashField() def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) - super(ServiceUserChangeForm, self).__init__(*args, prefix=prefix, **kwargs) + super(ServiceUserChangeForm, self).__init__( + *args, + prefix=prefix, + **kwargs + ) class Meta: model = ServiceUser fields = ('pseudo',) def clean_password(self): - # Regardless of what the user provides, return the initial value. - # This is done here, rather than on the field, because the - # field does not have access to the initial value + """Dummy fun""" return self.initial["password"] + class ResetPasswordForm(forms.Form): + """Formulaire de demande de reinitialisation de mot de passe, + mdp oublié""" pseudo = forms.CharField(label=u'Pseudo', max_length=255) email = forms.EmailField(max_length=255) + class MassArchiveForm(forms.Form): + """Formulaire d'archivage des users inactif. Prend en argument + du formulaire la date de depart avant laquelle archiver les + users""" date = forms.DateTimeField(help_text='%d/%m/%y') def clean(self): - cleaned_data=super(MassArchiveForm, self).clean() + cleaned_data = super(MassArchiveForm, self).clean() date = cleaned_data.get("date") if date: - if date>timezone.now(): - raise forms.ValidationError("Impossible d'archiver des utilisateurs dont la fin d'accès se situe dans le futur !") + if date > NOW: + raise forms.ValidationError("Impossible d'archiver des\ + utilisateurs dont la fin d'accès se situe dans le futur !") + class BaseInfoForm(ModelForm): + """Formulaire de base d'edition d'un user. Formulaire de base, utilisé + pour l'edition de self par self ou un cableur. On formate les champs + avec des label plus jolis""" def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(BaseInfoForm, self).__init__(*args, prefix=prefix, **kwargs) @@ -200,13 +281,21 @@ class BaseInfoForm(ModelForm): ] def clean_telephone(self): + """Verifie que le tel est présent si 'option est validée + dans preferences""" telephone = self.cleaned_data['telephone'] - preferences, created = OptionalUser.objects.get_or_create() + preferences, _created = OptionalUser.objects.get_or_create() if not telephone and preferences.is_tel_mandatory: - raise forms.ValidationError("Un numéro de téléphone valide est requis") + raise forms.ValidationError( + "Un numéro de téléphone valide est requis" + ) return telephone + class EditInfoForm(BaseInfoForm): + """Edition complète d'un user. Utilisé par admin, + permet d'editer normalement la chambre, ou le shell + Herite de la base""" class Meta(BaseInfoForm.Meta): fields = [ 'name', @@ -220,22 +309,33 @@ class EditInfoForm(BaseInfoForm): 'telephone', ] + class InfoForm(EditInfoForm): - """ Utile pour forcer un déménagement quand il y a déjà un user en place""" - force = forms.BooleanField(label="Forcer le déménagement ?", initial=False, required=False) + """ Utile pour forcer un déménagement quand il y a déjà un user en place + Formuaire utilisé pour la creation initiale""" + force = forms.BooleanField( + label="Forcer le déménagement ?", + initial=False, + required=False + ) def clean_force(self): + """On supprime l'ancien user de la chambre si et seulement si la + case est cochée""" if self.cleaned_data.get('force', False): remove_user_room(self.cleaned_data.get('room')) return + class UserForm(InfoForm): """ Model form general""" class Meta(InfoForm.Meta): fields = '__all__' + class PasswordForm(ModelForm): - """ Formulaire de changement brut de mot de passe. Ne pas utiliser sans traitement""" + """ Formulaire de changement brut de mot de passe. + Ne pas utiliser sans traitement""" class Meta: model = User fields = ['password', 'pwd_ntlm'] @@ -244,21 +344,32 @@ class PasswordForm(ModelForm): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(PasswordForm, self).__init__(*args, prefix=prefix, **kwargs) + class ServiceUserForm(ModelForm): """ Modification d'un service user""" - password = forms.CharField(label=u'Nouveau mot de passe', max_length=255, validators=[MinLengthValidator(8)], widget=forms.PasswordInput, required=False) + password = forms.CharField( + label=u'Nouveau mot de passe', + max_length=255, + validators=[MinLengthValidator(8)], + widget=forms.PasswordInput, + required=False + ) class Meta: model = ServiceUser - fields = ('pseudo','access_group') + fields = ('pseudo', 'access_group') def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(ServiceUserForm, self).__init__(*args, prefix=prefix, **kwargs) + class EditServiceUserForm(ServiceUserForm): + """Formulaire d'edition de base d'un service user. Ne permet + d'editer que son group d'acl et son commentaire""" class Meta(ServiceUserForm.Meta): - fields = ['access_group','comment'] + fields = ['access_group', 'comment'] + class StateForm(ModelForm): """ Changement de l'état d'un user""" @@ -272,6 +383,7 @@ class StateForm(ModelForm): class SchoolForm(ModelForm): + """Edition, creation d'un école""" class Meta: model = School fields = ['name'] @@ -281,7 +393,10 @@ class SchoolForm(ModelForm): super(SchoolForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields['name'].label = 'Établissement' + class ListRightForm(ModelForm): + """Edition, d'un groupe , équivalent à un droit + Ne peremet pas d'editer le gid, car il sert de primary key""" class Meta: model = ListRight fields = ['listright', 'details'] @@ -291,21 +406,38 @@ class ListRightForm(ModelForm): super(ListRightForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields['listright'].label = 'Nom du droit/groupe' + class NewListRightForm(ListRightForm): + """Ajout d'un groupe/list de droit """ class Meta(ListRightForm.Meta): fields = '__all__' def __init__(self, *args, **kwargs): super(NewListRightForm, self).__init__(*args, **kwargs) - self.fields['gid'].label = 'Gid, attention, cet attribut ne doit pas être modifié après création' + self.fields['gid'].label = 'Gid, attention, cet attribut ne doit\ + pas être modifié après création' + class DelListRightForm(Form): - listrights = forms.ModelMultipleChoiceField(queryset=ListRight.objects.all(), label="Droits actuels", widget=forms.CheckboxSelectMultiple) + """Suppression d'un ou plusieurs groupes""" + listrights = forms.ModelMultipleChoiceField( + queryset=ListRight.objects.all(), + label="Droits actuels", + widget=forms.CheckboxSelectMultiple + ) + class DelSchoolForm(Form): - schools = forms.ModelMultipleChoiceField(queryset=School.objects.all(), label="Etablissements actuels", widget=forms.CheckboxSelectMultiple) + """Suppression d'une ou plusieurs écoles""" + schools = forms.ModelMultipleChoiceField( + queryset=School.objects.all(), + label="Etablissements actuels", + widget=forms.CheckboxSelectMultiple + ) + class RightForm(ModelForm): + """Assignation d'un droit à un user""" def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(RightForm, self).__init__(*args, prefix=prefix, **kwargs) @@ -318,13 +450,19 @@ class RightForm(ModelForm): class DelRightForm(Form): - rights = forms.ModelMultipleChoiceField(queryset=Right.objects.all(), widget=forms.CheckboxSelectMultiple) + """Suppression d'un droit d'un user""" + rights = forms.ModelMultipleChoiceField( + queryset=Right.objects.all(), + widget=forms.CheckboxSelectMultiple + ) def __init__(self, right, *args, **kwargs): super(DelRightForm, self).__init__(*args, **kwargs) self.fields['rights'].queryset = Right.objects.filter(right=right) + class BanForm(ModelForm): + """Creation, edition d'un objet bannissement""" def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(BanForm, self).__init__(*args, prefix=prefix, **kwargs) @@ -335,13 +473,16 @@ class BanForm(ModelForm): exclude = ['user'] def clean_date_end(self): + """Verification que date_end est après now""" date_end = self.cleaned_data['date_end'] - if date_end < timezone.now(): - raise forms.ValidationError("Triple buse, la date de fin ne peut pas être avant maintenant... Re2o ne voyage pas dans le temps") + if date_end < NOW: + raise forms.ValidationError("Triple buse, la date de fin ne peut\ + pas être avant maintenant... Re2o ne voyage pas dans le temps") return date_end class WhitelistForm(ModelForm): + """Creation, edition d'un objet whitelist""" def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(WhitelistForm, self).__init__(*args, prefix=prefix, **kwargs) @@ -352,7 +493,9 @@ class WhitelistForm(ModelForm): exclude = ['user'] def clean_date_end(self): + """Verification que la date_end est posterieur à now""" date_end = self.cleaned_data['date_end'] - if date_end < timezone.now(): - raise forms.ValidationError("Triple buse, la date de fin ne peut pas être avant maintenant... Re2o ne voyage pas dans le temps") + if date_end < NOW: + raise forms.ValidationError("Triple buse, la date de fin ne peut pas\ + être avant maintenant... Re2o ne voyage pas dans le temps") return date_end diff --git a/users/models.py b/users/models.py index 78b76156..a0ef29f8 100644 --- a/users/models.py +++ b/users/models.py @@ -1,7 +1,7 @@ # -*- 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. +# 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 @@ -20,44 +20,66 @@ # 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. +""" +Models de l'application users. + +On défini ici des models django classiques: +- users, qui hérite de l'abstract base user de django. Permet de définit +un utilisateur du site (login, passwd, chambre, adresse, etc) +- les whiteslist +- les bannissements +- les établissements d'enseignement (school) +- les droits (right et listright) +- les utilisateurs de service (pour connexion automatique) + +On défini aussi des models qui héritent de django-ldapdb : +- ldapuser +- ldapgroup +- ldapserviceuser + +Ces utilisateurs ldap sont synchronisés à partir des objets +models sql classiques. Seuls certains champs essentiels sont +dupliqués. +""" + from __future__ import unicode_literals +import re +import uuid +import datetime + from django.db import models from django.db.models import Q 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 -from django.template import Context, RequestContext, loader +from django.template import Context, loader from django.core.mail import send_mail from django.core.urlresolvers import reverse +from django.db import transaction +from django.utils import timezone +from django.contrib.auth.models import AbstractBaseUser, BaseUserManager +from django.core.validators import RegexValidator from reversion import revisions as reversion -from django.db import transaction import ldapdb.models import ldapdb.models.fields -from re2o.settings import RIGHTS_LINK, LDAP, GID_RANGES,UID_RANGES -import re, uuid -import datetime +from re2o.settings import RIGHTS_LINK, LDAP, GID_RANGES, UID_RANGES from re2o.login import hashNT -from django.utils import timezone -from django.contrib.auth.models import AbstractBaseUser, BaseUserManager - -from django.core.validators import MinLengthValidator -from django.core.validators import RegexValidator -from topologie.models import Room from cotisations.models import Cotisation, Facture, Paiement, Vente -from machines.models import Domain, Interface, MachineType, Machine, Nas, MachineType, Extension, regen -from preferences.models import GeneralOption, AssoOption, OptionalUser, OptionalMachine, MailMessageOption +from machines.models import Domain, Interface, Machine, regen +from preferences.models import GeneralOption, AssoOption, OptionalUser +from preferences.models import OptionalMachine, MailMessageOption -now = timezone.now() +DT_NOW = timezone.now() -#### Utilitaires généraux +# Utilitaires généraux def remove_user_room(room): """ Déménage de force l'ancien locataire de la chambre """ @@ -76,33 +98,42 @@ def linux_user_check(login): def linux_user_validator(login): - """ Retourne une erreur de validation si le login ne respecte + """ Retourne une erreur de validation si le login ne respecte pas les contraintes unix (maj, min, chiffres ou tiret)""" if not linux_user_check(login): raise forms.ValidationError( - ", ce pseudo ('%(label)s') contient des carractères interdits", - params={'label': login}, + ", ce pseudo ('%(label)s') contient des carractères interdits", + params={'label': login}, ) + def get_fresh_user_uid(): """ Renvoie le plus petit uid non pris. Fonction très paresseuse """ - uids = list(range(int(min(UID_RANGES['users'])),int(max(UID_RANGES['users'])))) + uids = list(range( + int(min(UID_RANGES['users'])), + int(max(UID_RANGES['users'])) + )) try: used_uids = list(User.objects.values_list('uid_number', flat=True)) except: used_uids = [] - free_uids = [ id for id in uids if id not in used_uids] + free_uids = [id for id in uids if id not in used_uids] return min(free_uids) + def get_fresh_gid(): """ Renvoie le plus petit gid libre """ - gids = list(range(int(min(GID_RANGES['posix'])),int(max(GID_RANGES['posix'])))) + gids = list(range( + int(min(GID_RANGES['posix'])), + int(max(GID_RANGES['posix'])) + )) used_gids = list(ListRight.objects.values_list('gid', flat=True)) - free_gids = [ id for id in gids if id not in used_gids] + free_gids = [id for id in gids if id not in used_gids] return min(free_gids) + def get_admin_right(): - """ Renvoie l'instance droit admin. La crée si elle n'existe pas + """ Renvoie l'instance droit admin. La crée si elle n'existe pas Lui attribue un gid libre""" try: admin_right = ListRight.objects.get(listright="admin") @@ -112,25 +143,74 @@ def get_admin_right(): admin_right.save() return admin_right -def all_adherent(search_time=now): - """ Fonction renvoyant tous les users adherents. Optimisee pour n'est qu'une seule requete sql - Inspecte les factures de l'user et ses cotisation, regarde si elles sont posterieur à now (end_time)""" - 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=search_time)))).distinct() -def all_baned(search_time=now): +def all_adherent(search_time=DT_NOW): + """ Fonction renvoyant tous les users adherents. Optimisee pour n'est + qu'une seule requete sql + Inspecte les factures de l'user et ses cotisation, regarde si elles + sont posterieur à now (end_time)""" + 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=search_time) + ) + ) + ).distinct() + + +def all_baned(search_time=DT_NOW): """ Fonction renvoyant tous les users bannis """ - return User.objects.filter(ban__in=Ban.objects.filter(date_end__gt=search_time)).distinct() + return User.objects.filter( + ban__in=Ban.objects.filter( + date_end__gt=search_time + ) + ).distinct() -def all_whitelisted(search_time=now): + +def all_whitelisted(search_time=DT_NOW): """ Fonction renvoyant tous les users whitelistes """ - return User.objects.filter(whitelist__in=Whitelist.objects.filter(date_end__gt=search_time)).distinct() + return User.objects.filter( + whitelist__in=Whitelist.objects.filter( + date_end__gt=search_time + ) + ).distinct() + + +def all_has_access(search_time=DT_NOW): + """ Renvoie tous les users beneficiant d'une connexion + : user adherent ou whiteliste et non banni """ + return User.objects.filter( + Q(state=User.STATE_ACTIVE) & + ~Q(ban__in=Ban.objects.filter(date_end__gt=search_time)) & + (Q(whitelist__in=Whitelist.objects.filter(date_end__gt=search_time)) | + 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=search_time) + ) + ))) + ).distinct() -def all_has_access(search_time=now): - """ Renvoie tous les users beneficiant d'une connexion : user adherent ou whiteliste et non banni """ - 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=search_time)))))).distinct() class UserManager(BaseUserManager): - def _create_user(self, pseudo, name, surname, email, password=None, su=False): + """User manager basique de django""" + def _create_user( + self, + pseudo, + name, + surname, + email, + password=None, + su=False + ): if not pseudo: raise ValueError('Users must have an username') @@ -174,28 +254,53 @@ class User(AbstractBaseUser): STATE_DISABLED = 1 STATE_ARCHIVE = 2 STATES = ( - (0, 'STATE_ACTIVE'), - (1, 'STATE_DISABLED'), - (2, 'STATE_ARCHIVE'), - ) + (0, 'STATE_ACTIVE'), + (1, 'STATE_DISABLED'), + (2, 'STATE_ARCHIVE'), + ) def auto_uid(): + """Renvoie un uid libre""" return get_fresh_user_uid() name = models.CharField(max_length=255) surname = models.CharField(max_length=255) - pseudo = models.CharField(max_length=32, unique=True, help_text="Doit contenir uniquement des lettres, chiffres, ou tirets", validators=[linux_user_validator]) + pseudo = models.CharField( + max_length=32, + unique=True, + help_text="Doit contenir uniquement des lettres, chiffres, ou tirets", + validators=[linux_user_validator] + ) email = models.EmailField() - school = models.ForeignKey('School', on_delete=models.PROTECT, null=True, blank=True) - shell = models.ForeignKey('ListShell', on_delete=models.PROTECT, null=True, blank=True) - comment = models.CharField(help_text="Commentaire, promo", max_length=255, blank=True) - room = models.OneToOneField('topologie.Room', on_delete=models.PROTECT, blank=True, null=True) + school = models.ForeignKey( + 'School', + on_delete=models.PROTECT, + null=True, + blank=True + ) + shell = models.ForeignKey( + 'ListShell', + on_delete=models.PROTECT, + null=True, + blank=True + ) + comment = models.CharField( + help_text="Commentaire, promo", + max_length=255, + blank=True + ) + room = models.OneToOneField( + 'topologie.Room', + on_delete=models.PROTECT, + blank=True, + null=True + ) pwd_ntlm = models.CharField(max_length=255) state = models.IntegerField(choices=STATES, default=STATE_ACTIVE) registered = models.DateTimeField(auto_now_add=True) telephone = models.CharField(max_length=15, blank=True, null=True) uid_number = models.IntegerField(default=auto_uid, unique=True) - rezo_rez_uid = models.IntegerField(unique=True, blank=True, null=True) + rezo_rez_uid = models.IntegerField(unique=True, blank=True, null=True) USERNAME_FIELD = 'pseudo' REQUIRED_FIELDS = ['name', 'surname', 'email'] @@ -223,7 +328,8 @@ class User(AbstractBaseUser): @is_admin.setter def is_admin(self, value): - """ Change la valeur de admin à true ou false suivant la valeur de value""" + """ Change la valeur de admin à true ou false suivant la valeur de + value""" if value and not self.is_admin: self.make_admin() elif not value and self.is_admin: @@ -247,7 +353,7 @@ class User(AbstractBaseUser): for right in RIGHTS_LINK[perm]: query = query | Q(right__listright=right) if Right.objects.filter(Q(user=self) & query): - return True + return True try: Right.objects.get(user=self, right__listright=perm) except Right.DoesNotExist: @@ -255,17 +361,20 @@ class User(AbstractBaseUser): return True def has_perm(self, perm, obj=None): + """Ne sert à rien""" return True - def has_right(self, right): - """ Renvoie si un user a un right donné. Crée le right si il n'existe pas""" + """ Renvoie si un user a un right donné. Crée le right si il n'existe + pas""" try: list_right = ListRight.objects.get(listright=right) except: list_right = ListRight(listright=right, gid=get_fresh_gid()) list_right.save() - return Right.objects.filter(user=self).filter(right=list_right).exists() + return Right.objects.filter(user=self).filter( + right=list_right + ).exists() @cached_property def is_bureau(self): @@ -279,9 +388,10 @@ class User(AbstractBaseUser): @cached_property def is_cableur(self): - """ True si l'user a les droits cableur + """ True si l'user a les droits cableur (également true si bureau, infra ou bofh)""" - return self.has_right('cableur') or self.has_right('bureau') or self.has_right('infra') or self.has_right('bofh') + return self.has_right('cableur') or self.has_right('bureau') or\ + self.has_right('infra') or self.has_right('bofh') @cached_property def is_trez(self): @@ -296,15 +406,22 @@ class User(AbstractBaseUser): def end_adhesion(self): """ Renvoie la date de fin d'adhésion d'un user. Examine les objets cotisation""" - 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'] + 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 def is_adherent(self): - """ Renvoie True si l'user est adhérent : si self.end_adhesion()>now""" + """ Renvoie True si l'user est adhérent : si + self.end_adhesion()>now""" end = self.end_adhesion() if not end: return False - elif end < timezone.now(): + elif end < DT_NOW: return False else: return True @@ -312,13 +429,17 @@ class User(AbstractBaseUser): @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'] + 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'] + date_max = Whitelist.objects.filter( + user=self + ).aggregate(models.Max('date_end'))['date_end__max'] return date_max @cached_property @@ -327,7 +448,7 @@ class User(AbstractBaseUser): end = self.end_ban if not end: return False - elif end < timezone.now(): + elif end < DT_NOW: return False else: return True @@ -338,14 +459,14 @@ class User(AbstractBaseUser): end = self.end_whitelist if not end: return False - elif end < timezone.now(): + elif end < DT_NOW: return False else: return True def has_access(self): """ Renvoie si un utilisateur a accès à internet """ - return self.state == User.STATE_ACTIVE \ + return self.state == User.STATE_ACTIVE\ and not self.is_ban and (self.is_adherent() or self.is_whitelisted) def end_access(self): @@ -358,27 +479,50 @@ class User(AbstractBaseUser): else: if not self.end_whitelist: return self.end_adhesion() - else: + else: return max(self.end_adhesion(), self.end_whitelist) @cached_property def solde(self): - """ Renvoie le solde d'un user. Vérifie que l'option solde est activé, retourne 0 sinon. + """ Renvoie le solde d'un user. Vérifie que l'option solde est + activé, retourne 0 sinon. Somme les crédits de solde et retire les débit payés par solde""" - options, created = OptionalUser.objects.get_or_create() + options, _created = OptionalUser.objects.get_or_create() user_solde = options.user_solde if user_solde: - solde_object, created=Paiement.objects.get_or_create(moyen='Solde') - somme_debit = Vente.objects.filter(facture__in=Facture.objects.filter(user=self, paiement=solde_object)).aggregate(total=models.Sum(models.F('prix')*models.F('number'), output_field=models.FloatField()))['total'] or 0 - somme_credit =Vente.objects.filter(facture__in=Facture.objects.filter(user=self), name="solde").aggregate(total=models.Sum(models.F('prix')*models.F('number'), output_field=models.FloatField()))['total'] or 0 + solde_object, _created = Paiement.objects.get_or_create( + moyen='Solde' + ) + somme_debit = Vente.objects.filter( + facture__in=Facture.objects.filter( + user=self, + paiement=solde_object + ) + ).aggregate( + total=models.Sum( + models.F('prix')*models.F('number'), + output_field=models.FloatField() + ) + )['total'] or 0 + somme_credit = Vente.objects.filter( + facture__in=Facture.objects.filter(user=self), + name="solde" + ).aggregate( + total=models.Sum( + models.F('prix')*models.F('number'), + output_field=models.FloatField() + ) + )['total'] or 0 return somme_credit - somme_debit else: return 0 def user_interfaces(self, active=True): - """ Renvoie toutes les interfaces dont les machines appartiennent à self - Par defaut ne prend que les interfaces actives""" - return Interface.objects.filter(machine__in=Machine.objects.filter(user=self, active=active)).select_related('domain__extension') + """ Renvoie toutes les interfaces dont les machines appartiennent à + self. Par defaut ne prend que les interfaces actives""" + return Interface.objects.filter( + machine__in=Machine.objects.filter(user=self, active=active) + ).select_related('domain__extension') def assign_ips(self): """ Assign une ipv4 aux machines d'un user """ @@ -400,17 +544,19 @@ class User(AbstractBaseUser): interface.save() def archive(self): - """ Archive l'user : appelle unassign_ips() puis passe state à ARCHIVE""" + """ Archive l'user : appelle unassign_ips() puis passe state à + ARCHIVE""" self.unassign_ips() - self.state = User.STATE_ARCHIVE + self.state = User.STATE_ARCHIVE def unarchive(self): - """ Désarchive l'user : réassigne ses ip et le passe en state ACTIVE""" + """ Désarchive l'user : réassigne ses ip et le passe en state + ACTIVE""" self.assign_ips() self.state = User.STATE_ACTIVE def has_module_perms(self, app_label): - # Simplest version again + """True, a toutes les permissions de module""" return True def make_admin(self): @@ -419,16 +565,20 @@ class User(AbstractBaseUser): user_admin_right.save() def un_admin(self): + """Supprime les droits admin d'un user""" try: - user_right = Right.objects.get(user=self,right=get_admin_right()) + user_right = Right.objects.get(user=self, right=get_admin_right()) except Right.DoesNotExist: return user_right.delete() def ldap_sync(self, base=True, access_refresh=True, mac_refresh=True): - """ Synchronisation du ldap. Synchronise dans le ldap les attributs de self - Options : base : synchronise tous les attributs de base - nom, prenom, mail, password, shell, home - access_refresh : synchronise le dialup_access notant si l'user a accès aux services + """ Synchronisation du ldap. Synchronise dans le ldap les attributs de + self + Options : base : synchronise tous les attributs de base - nom, prenom, + mail, password, shell, home + access_refresh : synchronise le dialup_access notant si l'user a accès + aux services mac_refresh : synchronise les machines de l'user""" self.refresh_from_db() try: @@ -441,7 +591,8 @@ class User(AbstractBaseUser): user_ldap.dialupAccess = str(self.has_access()) user_ldap.home_directory = '/home/' + self.pseudo user_ldap.mail = self.email - user_ldap.given_name = self.surname.lower() + '_' + self.name.lower()[:3] + user_ldap.given_name = self.surname.lower() + '_'\ + + self.name.lower()[:3] 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() @@ -454,7 +605,10 @@ class User(AbstractBaseUser): if access_refresh: 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.macs = [inter.mac_bare() for inter in + Interface.objects.filter( + machine__in=Machine.objects.filter(user=self) + )] user_ldap.save() def ldap_del(self): @@ -467,53 +621,69 @@ class User(AbstractBaseUser): def notif_inscription(self): """ Prend en argument un objet user, envoie un mail de bienvenue """ - t = loader.get_template('users/email_welcome') - assooptions, created = AssoOption.objects.get_or_create() - mailmessageoptions, created = MailMessageOption.objects.get_or_create() - general_options, created = GeneralOption.objects.get_or_create() - c = Context({ + template = loader.get_template('users/email_welcome') + assooptions, _created = AssoOption.objects.get_or_create() + mailmessageoptions, _created = MailMessageOption\ + .objects.get_or_create() + general_options, _created = GeneralOption.objects.get_or_create() + context = Context({ 'nom': str(self.name) + ' ' + str(self.surname), 'asso_name': assooptions.name, 'asso_email': assooptions.contact, - 'welcome_mail_fr' : mailmessageoptions.welcome_mail_fr, - 'welcome_mail_en' : mailmessageoptions.welcome_mail_en, - 'pseudo':self.pseudo, + 'welcome_mail_fr': mailmessageoptions.welcome_mail_fr, + 'welcome_mail_en': mailmessageoptions.welcome_mail_en, + 'pseudo': self.pseudo, }) - send_mail('Bienvenue au %(name)s / Welcome to %(name)s' % {'name': assooptions.name }, '', - general_options.email_from, [self.email], html_message=t.render(c)) + send_mail( + 'Bienvenue au %(name)s / Welcome to %(name)s' % { + 'name': assooptions.name + }, + '', + general_options.email_from, + [self.email], + html_message=template.render(context) + ) return def reset_passwd_mail(self, request): - """ Prend en argument un request, envoie un mail de réinitialisation de mot de pass """ + """ Prend en argument un request, envoie un mail de + réinitialisation de mot de pass """ req = Request() req.type = Request.PASSWD req.user = self req.save() - t = loader.get_template('users/email_passwd_request') - options, created = AssoOption.objects.get_or_create() - general_options, created = GeneralOption.objects.get_or_create() - c = { + template = loader.get_template('users/email_passwd_request') + options, _created = AssoOption.objects.get_or_create() + general_options, _created = GeneralOption.objects.get_or_create() + context = { 'name': str(req.user.name) + ' ' + str(req.user.surname), 'asso': options.name, 'asso_mail': options.contact, 'site_name': general_options.site_name, 'url': request.build_absolute_uri( - reverse('users:process', kwargs={'token': req.token})), + reverse('users:process', kwargs={'token': req.token})), 'expire_in': str(general_options.req_expire_hrs) + ' heures', } - send_mail('Changement de mot de passe du %(name)s / Password renewal for %(name)s' % {'name': options.name }, t.render(c), - general_options.email_from, [req.user.email], fail_silently=False) + send_mail( + 'Changement de mot de passe du %(name)s / Password\ + renewal for %(name)s' % {'name': options.name}, + template.render(context), + general_options.email_from, + [req.user.email], + fail_silently=False + ) return def autoregister_machine(self, mac_address, nas_type): - """ Fonction appellée par freeradius. Enregistre la mac pour une machine inconnue - sur le compte de l'user""" + """ Fonction appellée par freeradius. Enregistre la mac pour + une machine inconnue sur le compte de l'user""" all_interfaces = self.user_interfaces(active=False) - options, created = OptionalMachine.objects.get_or_create() + options, _created = OptionalMachine.objects.get_or_create() if all_interfaces.count() > options.max_lambdauser_interfaces: return False, "Maximum de machines enregistrees atteinte" if not nas_type: - return False, "Re2o ne sait pas à quel machinetype affecter cette machine" + return False, "Re2o ne sait pas à quel machinetype affecter cette\ + machine" machine_type_cible = nas_type.machine_type try: machine_parent = Machine() @@ -533,12 +703,12 @@ class User(AbstractBaseUser): domain.interface_parent = interface_cible domain.clean() domain.save() - except Exception as e: - return False, e + except Exception as error: + return False, error return True, "Ok" def set_user_password(self, password): - """ A utiliser de préférence, set le password en hash courrant et + """ A utiliser de préférence, set le password en hash courrant et dans la version ntlm""" self.set_password(password) self.pwd_ntlm = hashNT(password) @@ -547,23 +717,28 @@ class User(AbstractBaseUser): def get_next_domain_name(self): """Look for an available name for a new interface for this user by trying "pseudo0", "pseudo1", "pseudo2", ... + + Recherche un nom disponible, pour une machine. Doit-être + unique, concatène le nom, le pseudo et le numero de machine """ def simple_pseudo(): + """Renvoie le pseudo sans underscore (compat dns)""" return self.pseudo.replace('_', '-').lower() - def composed_pseudo( n ): - return simple_pseudo() + str(n) + def composed_pseudo(name): + """Renvoie le resultat de simplepseudo et rajoute le nom""" + return simple_pseudo() + str(name) num = 0 - while Domain.objects.filter(name=composed_pseudo(num)) : + while Domain.objects.filter(name=composed_pseudo(num)): num += 1 return composed_pseudo(num) - def __str__(self): return self.pseudo + @receiver(post_save, sender=User) def user_post_save(sender, **kwargs): """ Synchronisation post_save : envoie le mail de bienvenue si creation @@ -575,29 +750,44 @@ def user_post_save(sender, **kwargs): user.ldap_sync(base=True, access_refresh=True, mac_refresh=False) regen('mailing') + @receiver(post_delete, sender=User) def user_post_delete(sender, **kwargs): + """Post delete d'un user, on supprime son instance ldap""" user = kwargs['instance'] user.ldap_del() regen('mailing') + class ServiceUser(AbstractBaseUser): """ Classe des users daemons, règle leurs accès au ldap""" readonly = 'readonly' ACCESS = ( - ('auth', 'auth'), - ('readonly', 'readonly'), - ('usermgmt', 'usermgmt'), - ) + ('auth', 'auth'), + ('readonly', 'readonly'), + ('usermgmt', 'usermgmt'), + ) PRETTY_NAME = "Utilisateurs de service" - pseudo = models.CharField(max_length=32, unique=True, help_text="Doit contenir uniquement des lettres, chiffres, ou tirets", validators=[linux_user_validator]) - access_group = models.CharField(choices=ACCESS, default=readonly, max_length=32) - comment = models.CharField(help_text="Commentaire", max_length=255, blank=True) + pseudo = models.CharField( + max_length=32, + unique=True, + help_text="Doit contenir uniquement des lettres, chiffres, ou tirets", + validators=[linux_user_validator] + ) + access_group = models.CharField( + choices=ACCESS, + default=readonly, + max_length=32 + ) + comment = models.CharField( + help_text="Commentaire", + max_length=255, + blank=True + ) USERNAME_FIELD = 'pseudo' - objects = UserManager() def ldap_sync(self): @@ -611,6 +801,7 @@ class ServiceUser(AbstractBaseUser): self.serviceuser_group_sync() def ldap_del(self): + """Suppression de l'instance ldap d'un service user""" try: user_ldap = LdapServiceUser.objects.get(name=self.pseudo) user_ldap.delete() @@ -619,30 +810,38 @@ class ServiceUser(AbstractBaseUser): self.serviceuser_group_sync() def serviceuser_group_sync(self): + """Synchronise le groupe et les droits de groupe dans le ldap""" try: group = LdapServiceUserGroup.objects.get(name=self.access_group) except: group = LdapServiceUserGroup(name=self.access_group) - group.members = list(LdapServiceUser.objects.filter(name__in=[user.pseudo for user in ServiceUser.objects.filter(access_group=self.access_group)]).values_list('dn', flat=True)) + group.members = list(LdapServiceUser.objects.filter( + name__in=[user.pseudo for user in ServiceUser.objects.filter( + access_group=self.access_group + )]).values_list('dn', flat=True)) group.save() def __str__(self): return self.pseudo + @receiver(post_save, sender=ServiceUser) def service_user_post_save(sender, **kwargs): """ Synchronise un service user ldap après modification django""" service_user = kwargs['instance'] service_user.ldap_sync() + @receiver(post_delete, sender=ServiceUser) def service_user_post_delete(sender, **kwargs): """ Supprime un service user ldap après suppression django""" service_user = kwargs['instance'] service_user.ldap_del() + class Right(models.Model): - """ Couple droit/user. Peut-être aurait-on mieux fait ici d'utiliser un manytomany + """ Couple droit/user. Peut-être aurait-on mieux fait ici d'utiliser un + manytomany Ceci dit le résultat aurait été le même avec une table intermediaire""" PRETTY_NAME = "Droits affectés à des users" @@ -655,18 +854,21 @@ class Right(models.Model): def __str__(self): return str(self.user) + @receiver(post_save, sender=Right) def right_post_save(sender, **kwargs): """ Synchronise les users ldap groups avec les groupes de droits""" right = kwargs['instance'].right right.ldap_sync() + @receiver(post_delete, sender=Right) def right_post_delete(sender, **kwargs): """ Supprime l'user du groupe""" right = kwargs['instance'].right right.ldap_sync() + class School(models.Model): """ Etablissement d'enseignement""" PRETTY_NAME = "Etablissements enregistrés" @@ -678,46 +880,69 @@ class School(models.Model): class ListRight(models.Model): - """ Ensemble des droits existants. Chaque droit crée un groupe ldap synchronisé, avec gid. + """ Ensemble des droits existants. Chaque droit crée un groupe + ldap synchronisé, avec gid. Permet de gérer facilement les accès serveurs et autres - La clef de recherche est le gid, pour cette raison là il n'est plus modifiable après creation""" + La clef de recherche est le gid, pour cette raison là + il n'est plus modifiable après creation""" PRETTY_NAME = "Liste des droits existants" - listright = models.CharField(max_length=255, unique=True, validators=[RegexValidator('^[a-z]+$', message="Les groupes unix ne peuvent contenir que des lettres minuscules")]) + listright = models.CharField( + max_length=255, + unique=True, + validators=[RegexValidator( + '^[a-z]+$', + message="Les groupes unix ne peuvent contenir\ + que des lettres minuscules" + )] + ) gid = models.IntegerField(unique=True, null=True) - details = models.CharField(help_text="Description", max_length=255, blank=True) + details = models.CharField( + help_text="Description", + max_length=255, + blank=True + ) def __str__(self): return self.listright def ldap_sync(self): + """Sychronise les groups ldap avec le model listright coté django""" try: group_ldap = LdapUserGroup.objects.get(gid=self.gid) except LdapUserGroup.DoesNotExist: group_ldap = LdapUserGroup(gid=self.gid) group_ldap.name = self.listright - group_ldap.members = [right.user.pseudo for right in Right.objects.filter(right=self)] + group_ldap.members = [right.user.pseudo for right + in Right.objects.filter(right=self)] group_ldap.save() def ldap_del(self): + """Supprime un groupe ldap""" try: group_ldap = LdapUserGroup.objects.get(gid=self.gid) group_ldap.delete() except LdapUserGroup.DoesNotExist: pass + @receiver(post_save, sender=ListRight) def listright_post_save(sender, **kwargs): """ Synchronise le droit ldap quand il est modifié""" right = kwargs['instance'] right.ldap_sync() + @receiver(post_delete, sender=ListRight) def listright_post_delete(sender, **kwargs): + """Suppression d'un groupe ldap après suppression coté django""" right = kwargs['instance'] right.ldap_del() + class ListShell(models.Model): + """Un shell possible. Pas de check si ce shell existe, les + admin sont des grands""" PRETTY_NAME = "Liste des shells disponibles" shell = models.CharField(max_length=255, unique=True) @@ -725,6 +950,7 @@ class ListShell(models.Model): def __str__(self): return self.shell + class Ban(models.Model): """ Bannissement. Actuellement a un effet tout ou rien. Gagnerait à être granulaire""" @@ -734,38 +960,45 @@ class Ban(models.Model): STATE_SOFT = 1 STATE_BRIDAGE = 2 STATES = ( - (0, 'HARD (aucun accès)'), - (1, 'SOFT (accès local seulement)'), - (2, 'BRIDAGE (bridage du débit)'), - ) + (0, 'HARD (aucun accès)'), + (1, 'SOFT (accès local seulement)'), + (2, 'BRIDAGE (bridage du débit)'), + ) user = models.ForeignKey('User', on_delete=models.PROTECT) raison = models.CharField(max_length=255) date_start = models.DateTimeField(auto_now_add=True) date_end = models.DateTimeField(help_text='%d/%m/%y %H:%M:%S') - state = models.IntegerField(choices=STATES, default=STATE_HARD) + state = models.IntegerField(choices=STATES, default=STATE_HARD) def notif_ban(self): """ Prend en argument un objet ban, envoie un mail de notification """ - general_options, created = GeneralOption.objects.get_or_create() - t = loader.get_template('users/email_ban_notif') - options, created = AssoOption.objects.get_or_create() - c = Context({ + general_options, _created = GeneralOption.objects.get_or_create() + template = loader.get_template('users/email_ban_notif') + options, _created = AssoOption.objects.get_or_create() + context = Context({ 'name': str(self.user.name) + ' ' + str(self.user.surname), 'raison': self.raison, 'date_end': self.date_end, - 'asso_name' : options.name, + 'asso_name': options.name, }) - send_mail('Deconnexion disciplinaire', t.render(c), - general_options.email_from, [self.user.email], fail_silently=False) + send_mail( + 'Deconnexion disciplinaire', + template.render(context), + general_options.email_from, + [self.user.email], + fail_silently=False + ) return def is_active(self): - return self.date_end > now + """Ce ban est-il actif?""" + return self.date_end > DT_NOW def __str__(self): return str(self.user) + ' ' + str(self.raison) + @receiver(post_save, sender=Ban) def ban_post_save(sender, **kwargs): """ Regeneration de tous les services après modification d'un ban""" @@ -782,6 +1015,7 @@ def ban_post_save(sender, **kwargs): regen('dhcp') regen('mac_ip_list') + @receiver(post_delete, sender=Ban) def ban_post_delete(sender, **kwargs): """ Regen de tous les services après suppression d'un ban""" @@ -791,7 +1025,11 @@ def ban_post_delete(sender, **kwargs): regen('dhcp') regen('mac_ip_list') + class Whitelist(models.Model): + """Accès à titre gracieux. L'utilisateur ne paye pas; se voit + accorder un accès internet pour une durée défini. Moins + fort qu'un ban quel qu'il soit""" PRETTY_NAME = "Liste des accès gracieux" user = models.ForeignKey('User', on_delete=models.PROTECT) @@ -800,13 +1038,16 @@ class Whitelist(models.Model): date_end = models.DateTimeField(help_text='%d/%m/%y %H:%M:%S') def is_active(self): - return self.date_end > now + return self.date_end > DT_NOW def __str__(self): return str(self.user) + ' ' + str(self.raison) + @receiver(post_save, sender=Whitelist) def whitelist_post_save(sender, **kwargs): + """Après modification d'une whitelist, on synchronise les services + et on lui permet d'avoir internet""" whitelist = kwargs['instance'] user = whitelist.user user.ldap_sync(base=False, access_refresh=True, mac_refresh=False) @@ -819,17 +1060,21 @@ def whitelist_post_save(sender, **kwargs): regen('dhcp') regen('mac_ip_list') + @receiver(post_delete, sender=Whitelist) def whitelist_post_delete(sender, **kwargs): + """Après suppression d'une whitelist, on supprime l'accès internet + en forçant la régénration""" user = kwargs['instance'].user user.ldap_sync(base=False, access_refresh=True, mac_refresh=False) regen('mailing') regen('dhcp') regen('mac_ip_list') + class Request(models.Model): """ Objet request, générant une url unique de validation. - Utilisé par exemple pour la generation du mot de passe et + Utilisé par exemple pour la generation du mot de passe et sa réinitialisation""" PASSWD = 'PW' EMAIL = 'EM' @@ -845,38 +1090,86 @@ class Request(models.Model): def save(self): if not self.expires_at: - options, created = GeneralOption.objects.get_or_create() - self.expires_at = timezone.now() \ + options, _created = GeneralOption.objects.get_or_create() + self.expires_at = DT_NOW \ + datetime.timedelta(hours=options.req_expire_hrs) if not self.token: self.token = str(uuid.uuid4()).replace('-', '') # remove hyphens super(Request, self).save() + class LdapUser(ldapdb.models.Model): """ Class for representing an LDAP user entry. """ # LDAP meta-data base_dn = LDAP['base_user_dn'] - object_classes = ['inetOrgPerson','top','posixAccount','sambaSamAccount','radiusprofile', 'shadowAccount'] + object_classes = ['inetOrgPerson', 'top', 'posixAccount', + 'sambaSamAccount', 'radiusprofile', + 'shadowAccount'] # attributes gid = ldapdb.models.fields.IntegerField(db_column='gidNumber') - name = ldapdb.models.fields.CharField(db_column='cn', max_length=200, primary_key=True) + name = ldapdb.models.fields.CharField( + db_column='cn', + max_length=200, + primary_key=True + ) uid = ldapdb.models.fields.CharField(db_column='uid', max_length=200) - uidNumber = ldapdb.models.fields.IntegerField(db_column='uidNumber', unique=True) + uidNumber = ldapdb.models.fields.IntegerField( + db_column='uidNumber', + unique=True + ) sn = ldapdb.models.fields.CharField(db_column='sn', max_length=200) - login_shell = ldapdb.models.fields.CharField(db_column='loginShell', max_length=200, blank=True, null=True) - mail = ldapdb.models.fields.CharField(db_column='mail', max_length=200) - given_name = ldapdb.models.fields.CharField(db_column='givenName', max_length=200) - home_directory = ldapdb.models.fields.CharField(db_column='homeDirectory', max_length=200) - display_name = ldapdb.models.fields.CharField(db_column='displayName', max_length=200, blank=True, null=True) + login_shell = ldapdb.models.fields.CharField( + db_column='loginShell', + max_length=200, + blank=True, + null=True + ) + mail = ldapdb.models.fields.CharField(db_column='mail', max_length=200) + given_name = ldapdb.models.fields.CharField( + db_column='givenName', + max_length=200 + ) + home_directory = ldapdb.models.fields.CharField( + db_column='homeDirectory', + max_length=200 + ) + display_name = ldapdb.models.fields.CharField( + db_column='displayName', + max_length=200, + blank=True, + null=True + ) dialupAccess = ldapdb.models.fields.CharField(db_column='dialupAccess') - sambaSID = ldapdb.models.fields.IntegerField(db_column='sambaSID', unique=True) - user_password = ldapdb.models.fields.CharField(db_column='userPassword', max_length=200, blank=True, null=True) - sambat_nt_password = ldapdb.models.fields.CharField(db_column='sambaNTPassword', max_length=200, blank=True, null=True) - macs = ldapdb.models.fields.ListField(db_column='radiusCallingStationId', max_length=200, blank=True, null=True) - shadowexpire = ldapdb.models.fields.CharField(db_column='shadowExpire', blank=True, null=True) + sambaSID = ldapdb.models.fields.IntegerField( + db_column='sambaSID', + unique=True + ) + user_password = ldapdb.models.fields.CharField( + db_column='userPassword', + max_length=200, + blank=True, + null=True + ) + sambat_nt_password = ldapdb.models.fields.CharField( + db_column='sambaNTPassword', + max_length=200, + blank=True, + null=True + ) + macs = ldapdb.models.fields.ListField( + db_column='radiusCallingStationId', + max_length=200, + blank=True, + null=True + ) + shadowexpire = ldapdb.models.fields.CharField( + db_column='shadowExpire', + blank=True, + null=True + ) def __str__(self): return self.name @@ -890,9 +1183,12 @@ class LdapUser(ldapdb.models.Model): self.sambaSID = self.uidNumber super(LdapUser, self).save(*args, **kwargs) + class LdapUserGroup(ldapdb.models.Model): """ - Class for representing an LDAP user entry. + Class for representing an LDAP group entry. + + Un groupe ldap """ # LDAP meta-data base_dn = LDAP['base_usergroup_dn'] @@ -901,38 +1197,64 @@ class LdapUserGroup(ldapdb.models.Model): # attributes gid = ldapdb.models.fields.IntegerField(db_column='gidNumber') members = ldapdb.models.fields.ListField(db_column='memberUid', blank=True) - name = ldapdb.models.fields.CharField(db_column='cn', max_length=200, primary_key=True) + name = ldapdb.models.fields.CharField( + db_column='cn', + max_length=200, + primary_key=True + ) def __str__(self): return self.name + class LdapServiceUser(ldapdb.models.Model): """ Class for representing an LDAP userservice entry. + + Un user de service coté ldap """ # LDAP meta-data base_dn = LDAP['base_userservice_dn'] - object_classes = ['applicationProcess','simpleSecurityObject'] + object_classes = ['applicationProcess', 'simpleSecurityObject'] # attributes - name = ldapdb.models.fields.CharField(db_column='cn', max_length=200, primary_key=True) - user_password = ldapdb.models.fields.CharField(db_column='userPassword', max_length=200, blank=True, null=True) + name = ldapdb.models.fields.CharField( + db_column='cn', + max_length=200, + primary_key=True + ) + user_password = ldapdb.models.fields.CharField( + db_column='userPassword', + max_length=200, + blank=True, + null=True + ) def __str__(self): return self.name + class LdapServiceUserGroup(ldapdb.models.Model): """ Class for representing an LDAP userservice entry. + + Un group user de service coté ldap. Dans userservicegroupdn + (voir dans settings_local.py) """ # LDAP meta-data base_dn = LDAP['base_userservicegroup_dn'] object_classes = ['groupOfNames'] # attributes - name = ldapdb.models.fields.CharField(db_column='cn', max_length=200, primary_key=True) - members = ldapdb.models.fields.ListField(db_column='member', blank=True) + name = ldapdb.models.fields.CharField( + db_column='cn', + max_length=200, + primary_key=True + ) + members = ldapdb.models.fields.ListField( + db_column='member', + blank=True + ) def __str__(self): return self.name - From bee8976ebe542195b401f67ecb81a2eb201c6457 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sat, 14 Oct 2017 22:10:07 +0200 Subject: [PATCH 6/9] Corrige les vues (docstring et autres) --- users/views.py | 377 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 255 insertions(+), 122 deletions(-) diff --git a/users/views.py b/users/views.py index aa72517b..e553f5b8 100644 --- a/users/views.py +++ b/users/views.py @@ -23,20 +23,25 @@ # App de gestion des users pour re2o # Goulven Kermarec, Gabriel Détraz, Lemesle Augustin # Gplv2 +""" +Module des views. + +On définit les vues pour l'ajout, l'edition des users : infos personnelles, +mot de passe, etc + +Permet aussi l'ajout, edition et suppression des droits, des bannissements, +des whitelist, des services users et des écoles +""" from __future__ import unicode_literals from django.shortcuts import get_object_or_404, render, redirect -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 Max, ProtectedError +from django.db.models import ProtectedError from django.db import IntegrityError -from django.core.mail import send_mail from django.utils import timezone -from django.core.urlresolvers import reverse from django.db import transaction from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt @@ -47,22 +52,21 @@ from rest_framework.renderers import JSONRenderer from reversion.models import Version from reversion import revisions as reversion from users.serializers import MailSerializer -from users.models import User, Right, Ban, Whitelist, School, ListRight, Request, ServiceUser, all_has_access -from users.forms import DelRightForm, BanForm, WhitelistForm, DelSchoolForm, DelListRightForm, NewListRightForm -from users.forms import EditInfoForm, InfoForm, BaseInfoForm, StateForm, RightForm, SchoolForm, EditServiceUserForm, ServiceUserForm, ListRightForm -from cotisations.models import Facture -from machines.models import Machine, Interface +from users.models import User, Right, Ban, Whitelist, School, ListRight +from users.models import Request, ServiceUser, all_has_access +from users.forms import DelRightForm, BanForm, WhitelistForm, DelSchoolForm +from users.forms import DelListRightForm, NewListRightForm +from users.forms import InfoForm, BaseInfoForm, StateForm +from users.forms import RightForm, SchoolForm, EditServiceUserForm +from users.forms import ServiceUserForm, ListRightForm from users.forms import MassArchiveForm, PassForm, ResetPasswordForm -from preferences.models import OptionalUser, AssoOption, GeneralOption +from cotisations.models import Facture +from machines.models import Machine +from preferences.models import OptionalUser, GeneralOption -from re2o.login import hashNT +from re2o.views import form -def form(ctx, template, request): - c = ctx - c.update(csrf(request)) - return render(request, template, c) - def password_change_action(u_form, user, request, req=False): """ Fonction qui effectue le changeemnt de mdp bdd""" user.set_user_password(u_form.cleaned_data['passwd1']) @@ -75,10 +79,12 @@ def password_change_action(u_form, user, request, req=False): return redirect("/") return redirect("/users/profil/" + str(user.id)) + @login_required @permission_required('cableur') def new_user(request): - """ Vue de création d'un nouvel utilisateur, envoie un mail pour le mot de passe""" + """ Vue de création d'un nouvel utilisateur, + envoie un mail pour le mot de passe""" user = InfoForm(request.POST or None) if user.is_valid(): user = user.save(commit=False) @@ -87,21 +93,25 @@ def new_user(request): reversion.set_user(request.user) reversion.set_comment("Création") user.reset_passwd_mail(request) - messages.success(request, "L'utilisateur %s a été crée, un mail pour l'initialisation du mot de passe a été envoyé" % user.pseudo) + messages.success(request, "L'utilisateur %s a été crée, un mail\ + pour l'initialisation du mot de passe a été envoyé" % user.pseudo) return redirect("/users/profil/" + str(user.id)) return form({'userform': user}, 'users/user.html', request) + @login_required def edit_info(request, userid): - """ Edite un utilisateur à partir de son id, - si l'id est différent de request.user, vérifie la possession du droit cableur """ + """ Edite un utilisateur à partir de son id, + si l'id est différent de request.user, vérifie la + possession du droit cableur """ try: user = User.objects.get(pk=userid) except User.DoesNotExist: messages.error(request, "Utilisateur inexistant") return redirect("/users/") if not request.user.has_perms(('cableur',)) and user != request.user: - messages.error(request, "Vous ne pouvez pas modifier un autre user que vous sans droit cableur") + messages.error(request, "Vous ne pouvez pas modifier un autre\ + user que vous sans droit cableur") return redirect("/users/profil/" + str(request.user.id)) if not request.user.has_perms(('cableur',)): user = BaseInfoForm(request.POST or None, instance=user) @@ -111,15 +121,19 @@ def edit_info(request, userid): with transaction.atomic(), reversion.create_revision(): user.save() reversion.set_user(request.user) - reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in user.changed_data)) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join( + field for field in user.changed_data + )) messages.success(request, "L'user a bien été modifié") return redirect("/users/profil/" + userid) return form({'userform': user}, 'users/user.html', request) + @login_required @permission_required('bureau') def state(request, userid): - """ Changer l'etat actif/desactivé/archivé d'un user, need droit bureau """ + """ Changer l'etat actif/desactivé/archivé d'un user, + need droit bureau """ try: user = User.objects.get(pk=userid) except User.DoesNotExist: @@ -135,12 +149,15 @@ def state(request, userid): elif state.cleaned_data['state'] == User.STATE_DISABLED: user.state = User.STATE_DISABLED reversion.set_user(request.user) - reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in state.changed_data)) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join( + field for field in state.changed_data + )) user.save() messages.success(request, "Etat changé avec succès") return redirect("/users/profil/" + userid) return form({'userform': state}, 'users/user.html', request) + @login_required def password(request, userid): """ Reinitialisation d'un mot de passe à partir de l'userid, @@ -152,16 +169,20 @@ def password(request, userid): messages.error(request, "Utilisateur inexistant") return redirect("/users/") if not request.user.has_perms(('cableur',)) and user != request.user: - messages.error(request, "Vous ne pouvez pas modifier un autre user que vous sans droit cableur") + messages.error(request, "Vous ne pouvez pas modifier un\ + autre user que vous sans droit cableur") return redirect("/users/profil/" + str(request.user.id)) - if not request.user.has_perms(('bureau',)) and user != request.user and Right.objects.filter(user=user): - messages.error(request, "Il faut les droits bureau pour modifier le mot de passe d'un membre actif") + if not request.user.has_perms(('bureau',)) and user != request.user\ + and Right.objects.filter(user=user): + messages.error(request, "Il faut les droits bureau pour modifier le\ + mot de passe d'un membre actif") return redirect("/users/profil/" + str(request.user.id)) u_form = PassForm(request.POST or None) if u_form.is_valid(): return password_change_action(u_form, user, request) return form({'userform': u_form}, 'users/user.html', request) + @login_required @permission_required('infra') def new_serviceuser(request): @@ -174,15 +195,20 @@ def new_serviceuser(request): user_object.save() reversion.set_user(request.user) reversion.set_comment("Création") - messages.success(request, "L'utilisateur %s a été crée" % user_object.pseudo) + messages.success( + request, + "L'utilisateur %s a été crée" % user_object.pseudo + ) return redirect("/users/index_serviceusers/") return form({'userform': user}, 'users/user.html', request) + @login_required @permission_required('infra') def edit_serviceuser(request, userid): - """ Edite un utilisateur à partir de son id, - si l'id est différent de request.user, vérifie la possession du droit cableur """ + """ Edite un utilisateur à partir de son id, + si l'id est différent de request.user, + vérifie la possession du droit cableur """ try: user = ServiceUser.objects.get(pk=userid) except ServiceUser.DoesNotExist: @@ -196,18 +222,22 @@ def edit_serviceuser(request, userid): user_object.set_password(user.cleaned_data['password']) user_object.save() reversion.set_user(request.user) - reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in user.changed_data)) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join( + field for field in user.changed_data + )) messages.success(request, "L'user a bien été modifié") return redirect("/users/index_serviceusers") return form({'userform': user}, 'users/user.html', request) + @login_required @permission_required('infra') def del_serviceuser(request, userid): + """Suppression d'un ou plusieurs serviceusers""" try: user = ServiceUser.objects.get(pk=userid) except ServiceUser.DoesNotExist: - messages.error(request, u"Utilisateur inexistant" ) + messages.error(request, u"Utilisateur inexistant") return redirect("/users/") if request.method == "POST": with transaction.atomic(), reversion.create_revision(): @@ -215,7 +245,12 @@ def del_serviceuser(request, userid): reversion.set_user(request.user) messages.success(request, "L'user a été détruite") return redirect("/users/index_serviceusers/") - return form({'objet': user, 'objet_name': 'serviceuser'}, 'users/delete.html', request) + return form( + {'objet': user, 'objet_name': 'serviceuser'}, + 'users/delete.html', + request + ) + @login_required @permission_required('bureau') @@ -241,28 +276,33 @@ def add_right(request, userid): return redirect("/users/profil/" + userid) return form({'userform': right}, 'users/user.html', request) + @login_required @permission_required('bureau') def del_right(request): """ Supprimer un droit à un user, need droit bureau """ user_right_list = dict() for right in ListRight.objects.all(): - user_right_list[right]= DelRightForm(right, request.POST or None) - for keys, right_item in user_right_list.items(): + user_right_list[right] = DelRightForm(right, request.POST or None) + for _keys, right_item in user_right_list.items(): if right_item.is_valid(): right_del = right_item.cleaned_data['rights'] with transaction.atomic(), reversion.create_revision(): reversion.set_user(request.user) - reversion.set_comment("Retrait des droit %s" % ','.join(str(deleted_right) for deleted_right in right_del)) + reversion.set_comment("Retrait des droit %s" % ','.join( + str(deleted_right) for deleted_right in right_del + )) right_del.delete() messages.success(request, "Droit retiré avec succès") return redirect("/users/") return form({'userform': user_right_list}, 'users/del_right.html', request) + @login_required @permission_required('bofh') def add_ban(request, userid): - """ Ajouter un banissement, nécessite au moins le droit bofh (a fortiori bureau) + """ Ajouter un banissement, nécessite au moins le droit bofh + (a fortiori bureau) Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement""" try: user = User.objects.get(pk=userid) @@ -273,7 +313,7 @@ def add_ban(request, userid): ban = BanForm(request.POST or None, instance=ban_instance) if ban.is_valid(): with transaction.atomic(), reversion.create_revision(): - ban_object = ban.save() + _ban_object = ban.save() reversion.set_user(request.user) reversion.set_comment("Création") messages.success(request, "Bannissement ajouté") @@ -285,10 +325,12 @@ def add_ban(request, userid): ) return form({'userform': ban}, 'users/user.html', request) + @login_required @permission_required('bofh') def edit_ban(request, banid): - """ Editer un bannissement, nécessite au moins le droit bofh (a fortiori bureau) + """ Editer un bannissement, nécessite au moins le droit bofh + (a fortiori bureau) Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement""" try: ban_instance = Ban.objects.get(pk=banid) @@ -300,23 +342,31 @@ def edit_ban(request, banid): with transaction.atomic(), reversion.create_revision(): ban.save() reversion.set_user(request.user) - reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in ban.changed_data)) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join( + field for field in ban.changed_data + )) messages.success(request, "Bannissement modifié") return redirect("/users/") return form({'userform': ban}, 'users/user.html', request) + @login_required @permission_required('cableur') def add_whitelist(request, userid): - """ Accorder un accès gracieux, temporaire ou permanent. Need droit cableur - Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement, raison obligatoire""" + """ Accorder un accès gracieux, temporaire ou permanent. + Need droit cableur + Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement, + raison obligatoire""" try: user = User.objects.get(pk=userid) except User.DoesNotExist: messages.error(request, "Utilisateur inexistant") return redirect("/users/") whitelist_instance = Whitelist(user=user) - whitelist = WhitelistForm(request.POST or None, instance=whitelist_instance) + whitelist = WhitelistForm( + request.POST or None, + instance=whitelist_instance + ) if whitelist.is_valid(): with transaction.atomic(), reversion.create_revision(): whitelist.save() @@ -331,30 +381,40 @@ def add_whitelist(request, userid): ) return form({'userform': whitelist}, 'users/user.html', request) + @login_required @permission_required('cableur') def edit_whitelist(request, whitelistid): - """ Editer un accès gracieux, temporaire ou permanent. Need droit cableur - Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement, raison obligatoire""" + """ Editer un accès gracieux, temporaire ou permanent. + Need droit cableur + Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement, + raison obligatoire""" try: whitelist_instance = Whitelist.objects.get(pk=whitelistid) except Whitelist.DoesNotExist: messages.error(request, "Entrée inexistante") return redirect("/users/") - whitelist = WhitelistForm(request.POST or None, instance=whitelist_instance) + whitelist = WhitelistForm( + request.POST or None, + instance=whitelist_instance + ) if whitelist.is_valid(): with transaction.atomic(), reversion.create_revision(): whitelist.save() reversion.set_user(request.user) - reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in whitelist.changed_data)) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join( + field for field in whitelist.changed_data + )) messages.success(request, "Whitelist modifiée") return redirect("/users/") return form({'userform': whitelist}, 'users/user.html', request) + @login_required @permission_required('cableur') def add_school(request): - """ Ajouter un établissement d'enseignement à la base de donnée, need cableur""" + """ Ajouter un établissement d'enseignement à la base de donnée, + need cableur""" school = SchoolForm(request.POST or None) if school.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -365,30 +425,37 @@ def add_school(request): return redirect("/users/index_school/") return form({'userform': school}, 'users/user.html', request) + @login_required @permission_required('cableur') def edit_school(request, schoolid): - """ Editer un établissement d'enseignement à partir du schoolid dans la base de donnée, need cableur""" + """ Editer un établissement d'enseignement à partir du schoolid dans + la base de donnée, need cableur""" try: school_instance = School.objects.get(pk=schoolid) except School.DoesNotExist: - messages.error(request, u"Entrée inexistante" ) + messages.error(request, u"Entrée inexistante") return redirect("/users/") school = SchoolForm(request.POST or None, instance=school_instance) if school.is_valid(): with transaction.atomic(), reversion.create_revision(): school.save() reversion.set_user(request.user) - reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in school.changed_data)) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join( + field for field in school.changed_data + )) messages.success(request, "Établissement modifié") return redirect("/users/index_school/") return form({'userform': school}, 'users/user.html', request) + @login_required @permission_required('cableur') def del_school(request): - """ Supprimer un établissement d'enseignement à la base de donnée, need cableur - Objet protégé, possible seulement si aucun user n'est affecté à l'établissement """ + """ Supprimer un établissement d'enseignement à la base de donnée, + need cableur + Objet protégé, possible seulement si aucun user n'est affecté à + l'établissement """ school = DelSchoolForm(request.POST or None) if school.is_valid(): school_dels = school.cleaned_data['schools'] @@ -406,6 +473,7 @@ def del_school(request): return redirect("/users/index_school/") return form({'userform': school}, 'users/user.html', request) + @login_required @permission_required('bureau') def add_listright(request): @@ -421,29 +489,38 @@ def add_listright(request): return redirect("/users/index_listright/") return form({'userform': listright}, 'users/user.html', request) + @login_required @permission_required('bureau') def edit_listright(request, listrightid): - """ Editer un groupe/droit, necessite droit bureau, à partir du listright id """ + """ Editer un groupe/droit, necessite droit bureau, + à partir du listright id """ try: listright_instance = ListRight.objects.get(pk=listrightid) except ListRight.DoesNotExist: - messages.error(request, u"Entrée inexistante" ) + messages.error(request, u"Entrée inexistante") return redirect("/users/") - listright = ListRightForm(request.POST or None, instance=listright_instance) + listright = ListRightForm( + request.POST or None, + instance=listright_instance + ) if listright.is_valid(): with transaction.atomic(), reversion.create_revision(): listright.save() reversion.set_user(request.user) - reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in listright.changed_data)) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join( + field for field in listright.changed_data + )) messages.success(request, "Droit modifié") return redirect("/users/index_listright/") return form({'userform': listright}, 'users/user.html', request) + @login_required @permission_required('bureau') def del_listright(request): - """ Supprimer un ou plusieurs groupe, possible si il est vide, need droit bureau """ + """ Supprimer un ou plusieurs groupe, possible si il est vide, need droit + bureau """ listright = DelListRightForm(request.POST or None) if listright.is_valid(): listright_dels = listright.cleaned_data['listrights'] @@ -461,6 +538,7 @@ def del_listright(request): return redirect("/users/index_listright/") return form({'userform': listright}, 'users/user.html', request) + @login_required @permission_required('bureau') def mass_archive(request): @@ -469,7 +547,10 @@ 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: with transaction.atomic(), reversion.create_revision(): @@ -477,15 +558,22 @@ def mass_archive(request): user.save() reversion.set_user(request.user) reversion.set_comment("Archivage") - messages.success(request, "%s users ont été archivés" % len(to_archive_list)) - return redirect("/users/") - return form({'userform': to_archive_date, 'to_archive_list': to_archive_list}, 'users/mass_archive.html', request) + messages.success(request, "%s users ont été archivés" % len( + to_archive_list + )) + return redirect("/users/") + return form( + {'userform': to_archive_date, 'to_archive_list': to_archive_list}, + 'users/mass_archive.html', + request + ) + @login_required @permission_required('cableur') def index(request): """ Affiche l'ensemble des users, need droit cableur """ - options, created = GeneralOption.objects.get_or_create() + options, _created = GeneralOption.objects.get_or_create() pagination_number = options.pagination_number users_list = User.objects.select_related('room').order_by('state', 'name') paginator = Paginator(users_list, pagination_number) @@ -500,13 +588,15 @@ def index(request): users_list = paginator.page(paginator.num_pages) return render(request, 'users/index.html', {'users_list': users_list}) + @login_required @permission_required('cableur') def index_ban(request): """ Affiche l'ensemble des ban, need droit cableur """ - options, created = GeneralOption.objects.get_or_create() + options, _created = GeneralOption.objects.get_or_create() pagination_number = options.pagination_number - ban_list = Ban.objects.order_by('date_start').select_related('user').reverse() + ban_list = Ban.objects.order_by('date_start')\ + .select_related('user').reverse() paginator = Paginator(ban_list, pagination_number) page = request.GET.get('page') try: @@ -515,17 +605,19 @@ def index_ban(request): # If page isn't an integer, deliver first page ban_list = paginator.page(1) except EmptyPage: - # If page is out of range (e.g. 9999), deliver last page of results. - ban_list = paginator.page(paginator.num_pages) + # If page is out of range (e.g. 9999), deliver last page of results. + ban_list = paginator.page(paginator.num_pages) return render(request, 'users/index_ban.html', {'ban_list': ban_list}) + @login_required @permission_required('cableur') def index_white(request): """ Affiche l'ensemble des whitelist, need droit cableur """ - options, created = GeneralOption.objects.get_or_create() + options, _created = GeneralOption.objects.get_or_create() pagination_number = options.pagination_number - white_list = Whitelist.objects.select_related('user').order_by('date_start') + white_list = Whitelist.objects.select_related('user')\ + .order_by('date_start') paginator = Paginator(white_list, pagination_number) page = request.GET.get('page') try: @@ -534,92 +626,114 @@ def index_white(request): # If page isn't an integer, deliver first page white_list = paginator.page(1) except EmptyPage: - # If page is out of range (e.g. 9999), deliver last page of results. - white_list = paginator.page(paginator.num_pages) + # If page is out of range (e.g. 9999), deliver last page of results. + white_list = paginator.page(paginator.num_pages) return render( request, 'users/index_whitelist.html', {'white_list': white_list} ) + @login_required @permission_required('cableur') def index_school(request): """ Affiche l'ensemble des établissement, need droit cableur """ school_list = School.objects.order_by('name') - return render(request, 'users/index_schools.html', {'school_list':school_list}) + return render( + request, + 'users/index_schools.html', + {'school_list': school_list} + ) + @login_required @permission_required('cableur') def index_listright(request): """ Affiche l'ensemble des droits , need droit cableur """ listright_list = ListRight.objects.order_by('listright') - return render(request, 'users/index_listright.html', {'listright_list':listright_list}) + return render( + request, + 'users/index_listright.html', + {'listright_list': listright_list} + ) + @login_required @permission_required('cableur') def index_serviceusers(request): """ Affiche les users de services (pour les accès ldap)""" serviceusers_list = ServiceUser.objects.order_by('pseudo') - return render(request, 'users/index_serviceusers.html', {'serviceusers_list':serviceusers_list}) + return render( + request, + 'users/index_serviceusers.html', + {'serviceusers_list': serviceusers_list} + ) + @login_required -def history(request, object, id): +def history(request, object_name, object_id): """ Affichage de l'historique : (acl, argument) user : self or cableur, userid, ban : self or cableur, banid, whitelist : self or cableur, whitelistid, school : cableur, schoolid, listright : cableur, listrightid """ - if object == 'user': + if object_name == 'user': try: - object_instance = User.objects.get(pk=id) + object_instance = User.objects.get(pk=object_id) except User.DoesNotExist: - messages.error(request, "Utilisateur inexistant") - return redirect("/users/") - if not request.user.has_perms(('cableur',)) and object_instance != request.user: - messages.error(request, "Vous ne pouvez pas afficher l'historique d'un autre user que vous sans droit cableur") - return redirect("/users/profil/" + str(request.user.id)) - elif object == 'serviceuser' and request.user.has_perms(('cableur',)): + messages.error(request, "Utilisateur inexistant") + return redirect("/users/") + if not request.user.has_perms(('cableur',)) and\ + object_instance != request.user: + messages.error(request, "Vous ne pouvez pas afficher\ + l'historique d'un autre user que vous sans droit cableur") + return redirect("/users/profil/" + str(request.user.id)) + elif object_name == 'serviceuser' and request.user.has_perms(('cableur',)): try: - object_instance = ServiceUser.objects.get(pk=id) + object_instance = ServiceUser.objects.get(pk=object_id) except ServiceUser.DoesNotExist: - messages.error(request, "User service inexistant") - return redirect("/users/") - elif object == 'ban': + messages.error(request, "User service inexistant") + return redirect("/users/") + elif object_name == 'ban': try: - object_instance = Ban.objects.get(pk=id) + object_instance = Ban.objects.get(pk=object_id) except Ban.DoesNotExist: - messages.error(request, "Bannissement inexistant") - return redirect("/users/") - if not request.user.has_perms(('cableur',)) and object_instance.user != request.user: - messages.error(request, "Vous ne pouvez pas afficher les bans d'un autre user que vous sans droit cableur") - return redirect("/users/profil/" + str(request.user.id)) - elif object == 'whitelist': + messages.error(request, "Bannissement inexistant") + return redirect("/users/") + if not request.user.has_perms(('cableur',)) and\ + object_instance.user != request.user: + messages.error(request, "Vous ne pouvez pas afficher les bans\ + d'un autre user que vous sans droit cableur") + return redirect("/users/profil/" + str(request.user.id)) + elif object_name == 'whitelist': try: - object_instance = Whitelist.objects.get(pk=id) - except Whiltelist.DoesNotExist: - messages.error(request, "Whitelist inexistant") - return redirect("/users/") - if not request.user.has_perms(('cableur',)) and object_instance.user != request.user: - messages.error(request, "Vous ne pouvez pas afficher les whitelist d'un autre user que vous sans droit cableur") - return redirect("/users/profil/" + str(request.user.id)) - elif object == 'school' and request.user.has_perms(('cableur',)): + object_instance = Whitelist.objects.get(pk=object_id) + except Whitelist.DoesNotExist: + messages.error(request, "Whitelist inexistant") + return redirect("/users/") + if not request.user.has_perms(('cableur',)) and\ + object_instance.user != request.user: + messages.error(request, "Vous ne pouvez pas afficher les\ + whitelist d'un autre user que vous sans droit cableur") + return redirect("/users/profil/" + str(request.user.id)) + elif object_name == 'school' and request.user.has_perms(('cableur',)): try: - object_instance = School.objects.get(pk=id) + object_instance = School.objects.get(pk=object_id) except School.DoesNotExist: - messages.error(request, "Ecole inexistante") - return redirect("/users/") - elif object == 'listright' and request.user.has_perms(('cableur',)): + messages.error(request, "Ecole inexistante") + return redirect("/users/") + elif object_name == 'listright' and request.user.has_perms(('cableur',)): try: - object_instance = ListRight.objects.get(pk=id) + object_instance = ListRight.objects.get(pk=object_id) except ListRight.DoesNotExist: - messages.error(request, "Droit inexistant") - return redirect("/users/") + messages.error(request, "Droit inexistant") + return redirect("/users/") else: messages.error(request, "Objet inconnu") return redirect("/users/") - options, created = GeneralOption.objects.get_or_create() + options, _created = GeneralOption.objects.get_or_create() pagination_number = options.pagination_number reversions = Version.objects.get_for_object(object_instance) paginator = Paginator(reversions, pagination_number) @@ -632,7 +746,11 @@ def history(request, object, id): except EmptyPage: # If page is out of range (e.g. 9999), deliver last page of results. reversions = paginator.page(paginator.num_pages) - return render(request, 're2o/history.html', {'reversions': reversions, 'object': object_instance}) + return render( + request, + 're2o/history.html', + {'reversions': reversions, 'object': object_instance} + ) @login_required @@ -640,6 +758,7 @@ def mon_profil(request): """ Lien vers profil, renvoie request.id à la fonction """ return redirect("/users/profil/" + str(request.user.id)) + @login_required def profil(request, userid): """ Affiche un profil, self or cableur, prend un userid en argument """ @@ -649,14 +768,19 @@ def profil(request, userid): messages.error(request, "Utilisateur inexistant") return redirect("/users/") if not request.user.has_perms(('cableur',)) and users != request.user: - messages.error(request, "Vous ne pouvez pas afficher un autre user que vous sans droit cableur") + messages.error(request, "Vous ne pouvez pas afficher un autre user\ + que vous sans droit cableur") return redirect("/users/profil/" + str(request.user.id)) - machines = Machine.objects.filter(user=users).select_related('user').prefetch_related('interface_set__domain__extension').prefetch_related('interface_set__ipv4__ip_type__extension').prefetch_related('interface_set__type').prefetch_related('interface_set__domain__related_domain__extension') + machines = Machine.objects.filter(user=users).select_related('user')\ + .prefetch_related('interface_set__domain__extension')\ + .prefetch_related('interface_set__ipv4__ip_type__extension')\ + .prefetch_related('interface_set__type')\ + .prefetch_related('interface_set__domain__related_domain__extension') factures = Facture.objects.filter(user=users) bans = Ban.objects.filter(user=users) whitelists = Whitelist.objects.filter(user=users) list_droits = Right.objects.filter(user=users) - options, created = OptionalUser.objects.get_or_create() + options, _created = OptionalUser.objects.get_or_create() user_solde = options.user_solde return render( request, @@ -672,46 +796,56 @@ def profil(request, userid): } ) + def reset_password(request): """ Reintialisation du mot de passe si mdp oublié """ userform = ResetPasswordForm(request.POST or None) if userform.is_valid(): try: - user = User.objects.get(pseudo=userform.cleaned_data['pseudo'],email=userform.cleaned_data['email']) + user = User.objects.get( + pseudo=userform.cleaned_data['pseudo'], + email=userform.cleaned_data['email'] + ) except User.DoesNotExist: messages.error(request, "Cet utilisateur n'existe pas") return form({'userform': userform}, 'users/user.html', request) user.reset_passwd_mail(request) - messages.success(request, "Un mail pour l'initialisation du mot de passe a été envoyé") - redirect("/") + messages.success(request, "Un mail pour l'initialisation du mot\ + de passe a été envoyé") + redirect("/") return form({'userform': userform}, 'users/user.html', request) + def process(request, token): + """Process, lien pour la reinitialisation du mot de passe""" valid_reqs = Request.objects.filter(expires_at__gt=timezone.now()) req = get_object_or_404(valid_reqs, token=token) if req.type == Request.PASSWD: return process_passwd(request, req) - elif req.type == Request.EMAIL: - return process_email(request, req=req) else: messages.error(request, "Entrée incorrecte, contactez un admin") redirect("/") + def process_passwd(request, req): + """Process le changeemnt de mot de passe, renvoie le formulaire + demandant le nouveau password""" u_form = PassForm(request.POST or None) user = req.user if u_form.is_valid(): return password_change_action(u_form, user, request, req=req) return form({'userform': u_form}, 'users/user.html', request) -""" Framework Rest """ + class JSONResponse(HttpResponse): + """ Framework Rest """ def __init__(self, data, **kwargs): content = JSONRenderer().render(data) kwargs['content_type'] = 'application/json' super(JSONResponse, self).__init__(content, **kwargs) + @csrf_exempt @login_required @permission_required('serveur') @@ -721,4 +855,3 @@ def mailing(request): mails = all_has_access().values('email').distinct() seria = MailSerializer(mails, many=True) return JSONResponse(seria.data) - From 58c8ec23d7417af5f83584221212fd63e0385939 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sat, 14 Oct 2017 22:20:23 +0200 Subject: [PATCH 7/9] Fix urls.py pep8 --- users/urls.py | 84 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 16 deletions(-) diff --git a/users/urls.py b/users/urls.py index 43054fe5..531e0826 100644 --- a/users/urls.py +++ b/users/urls.py @@ -19,6 +19,9 @@ # 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. +""" +Definition des urls, pointant vers les views +""" from __future__ import unicode_literals @@ -32,39 +35,88 @@ urlpatterns = [ url(r'^state/(?P[0-9]+)$', views.state, name='state'), url(r'^password/(?P[0-9]+)$', views.password, name='password'), url(r'^new_serviceuser/$', views.new_serviceuser, name='new-serviceuser'), - url(r'^edit_serviceuser/(?P[0-9]+)$', views.edit_serviceuser, name='edit-serviceuser'), - url(r'^del_serviceuser/(?P[0-9]+)$', views.del_serviceuser, name='del-serviceuser'), + url( + r'^edit_serviceuser/(?P[0-9]+)$', + views.edit_serviceuser, + name='edit-serviceuser' + ), + url( + r'^del_serviceuser/(?P[0-9]+)$', + views.del_serviceuser, + name='del-serviceuser' + ), url(r'^add_ban/(?P[0-9]+)$', views.add_ban, name='add-ban'), url(r'^edit_ban/(?P[0-9]+)$', views.edit_ban, name='edit-ban'), - url(r'^add_whitelist/(?P[0-9]+)$', views.add_whitelist, name='add-whitelist'), - url(r'^edit_whitelist/(?P[0-9]+)$', views.edit_whitelist, name='edit-whitelist'), + url( + r'^add_whitelist/(?P[0-9]+)$', + views.add_whitelist, + name='add-whitelist' + ), + url( + r'^edit_whitelist/(?P[0-9]+)$', + views.edit_whitelist, + name='edit-whitelist' + ), url(r'^add_right/(?P[0-9]+)$', views.add_right, name='add-right'), url(r'^del_right/$', views.del_right, name='del-right'), url(r'^add_school/$', views.add_school, name='add-school'), - url(r'^edit_school/(?P[0-9]+)$', views.edit_school, name='edit-school'), + url( + r'^edit_school/(?P[0-9]+)$', + views.edit_school, + name='edit-school' + ), url(r'^del_school/$', views.del_school, name='del-school'), url(r'^add_listright/$', views.add_listright, name='add-listright'), - url(r'^edit_listright/(?P[0-9]+)$', views.edit_listright, name='edit-listright'), + url( + r'^edit_listright/(?P[0-9]+)$', + views.edit_listright, + name='edit-listright' + ), url(r'^del_listright/$', views.del_listright, name='del-listright'), url(r'^profil/(?P[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_listright/$', views.index_listright, name='index-listright'), - url(r'^index_serviceusers/$', views.index_serviceusers, name='index-serviceusers'), + url( + r'^index_serviceusers/$', + views.index_serviceusers, + name='index-serviceusers' + ), url(r'^mon_profil/$', views.mon_profil, name='mon-profil'), url(r'^process/(?P[a-z0-9]{32})/$', views.process, name='process'), url(r'^reset_password/$', views.reset_password, name='reset-password'), url(r'^mass_archive/$', views.mass_archive, name='mass-archive'), - url(r'^history/(?Puser)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Pban)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Pwhitelist)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Pschool)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Plistright)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Pserviceuser)/(?P[0-9]+)$', views.history, name='history'), + url( + r'^history/(?Puser)/(?P[0-9]+)$', + views.history, + name='history' + ), + url( + r'^history/(?Pban)/(?P[0-9]+)$', + views.history, + name='history' + ), + url( + r'^history/(?Pwhitelist)/(?P[0-9]+)$', + views.history, + name='history' + ), + url( + r'^history/(?Pschool)/(?P[0-9]+)$', + views.history, + name='history' + ), + url( + r'^history/(?Plistright)/(?P[0-9]+)$', + views.history, + name='history' + ), + url( + r'^history/(?Pserviceuser)/(?P[0-9]+)$', + views.history, + name='history' + ), url(r'^$', views.index, name='index'), url(r'^rest/mailing/$', views.mailing, name='mailing'), - ] - - From 25128b600ebbad203767d9ccc203bc7cb14caf36 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sat, 14 Oct 2017 22:46:21 +0200 Subject: [PATCH 8/9] Fix admin pep8 users --- users/admin.py | 101 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 82 insertions(+), 19 deletions(-) diff --git a/users/admin.py b/users/admin.py index 6a1e0e74..0c71064a 100644 --- a/users/admin.py +++ b/users/admin.py @@ -20,6 +20,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. +""" +Definition des vues pour les admin. Classique, sauf pour users, +où on fait appel à UserChange et ServiceUserChange, forms custom +""" from __future__ import unicode_literals @@ -28,11 +32,15 @@ from django.contrib.auth.models import Group from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from reversion.admin import VersionAdmin -from .models import User, ServiceUser, School, Right, ListRight, ListShell, Ban, Whitelist, Request, LdapUser, LdapServiceUser, LdapServiceUserGroup, LdapUserGroup -from .forms import UserChangeForm, UserCreationForm, ServiceUserChangeForm, ServiceUserCreationForm +from .models import User, ServiceUser, School, Right, ListRight, ListShell +from .models import Ban, Whitelist, Request, LdapUser, LdapServiceUser +from .models import LdapServiceUserGroup, LdapUserGroup +from .forms import UserChangeForm, UserCreationForm +from .forms import ServiceUserChangeForm, ServiceUserCreationForm class UserAdmin(admin.ModelAdmin): + """Administration d'un user""" list_display = ( 'name', 'surname', @@ -43,51 +51,73 @@ class UserAdmin(admin.ModelAdmin): 'shell', 'state' ) - search_fields = ('name','surname','pseudo','room') + search_fields = ('name', 'surname', 'pseudo', 'room') class LdapUserAdmin(admin.ModelAdmin): - list_display = ('name','uidNumber','login_shell') - exclude = ('user_password','sambat_nt_password') + """Administration du ldapuser""" + list_display = ('name', 'uidNumber', 'login_shell') + exclude = ('user_password', 'sambat_nt_password') search_fields = ('name',) + class LdapServiceUserAdmin(admin.ModelAdmin): + """Administration du ldapserviceuser""" list_display = ('name',) exclude = ('user_password',) search_fields = ('name',) + class LdapUserGroupAdmin(admin.ModelAdmin): - list_display = ('name','members','gid') + """Administration du ldapusergroupe""" + list_display = ('name', 'members', 'gid') search_fields = ('name',) + class LdapServiceUserGroupAdmin(admin.ModelAdmin): + """Administration du ldap serviceusergroup""" list_display = ('name',) search_fields = ('name',) + class SchoolAdmin(VersionAdmin): - list_display = ('name',) + """Administration, gestion des écoles""" + pass + class ListRightAdmin(VersionAdmin): + """Gestion de la liste des droits existants + Ne permet pas l'edition du gid (primarykey pour ldap)""" list_display = ('listright',) + class ListShellAdmin(VersionAdmin): - list_display = ('shell',) + """Gestion de la liste des shells coté admin""" + pass + class RightAdmin(VersionAdmin): - list_display = ('user', 'right') + """Gestion de la liste des droits affectés""" + pass + class RequestAdmin(admin.ModelAdmin): + """Gestion des request objet, ticket pour lien de reinit mot de passe""" list_display = ('user', 'type', 'created_at', 'expires_at') + class BanAdmin(VersionAdmin): - list_display = ('user', 'raison', 'date_start', 'date_end') + """Gestion des bannissements""" + pass class WhitelistAdmin(VersionAdmin): - list_display = ('user', 'raison', 'date_start', 'date_end') + """Gestion des whitelist""" + pass class UserAdmin(VersionAdmin, BaseUserAdmin): + """Gestion d'un user : modification des champs perso, mot de passe, etc""" # The forms to add and change user instances form = UserChangeForm add_form = UserCreationForm @@ -95,27 +125,56 @@ class UserAdmin(VersionAdmin, BaseUserAdmin): # The fields to be used in displaying the User model. # These override the definitions on the base UserAdmin # that reference specific fields on auth.User. - list_display = ('pseudo', 'name', 'surname', 'email', 'school', 'is_admin', 'shell') + list_display = ( + 'pseudo', + 'name', + 'surname', + 'email', + 'school', + 'is_admin', + 'shell' + ) list_display = ('pseudo',) list_filter = () fieldsets = ( (None, {'fields': ('pseudo', 'password')}), - ('Personal info', {'fields': ('name', 'surname', 'email', 'school','shell', 'uid_number')}), + ( + 'Personal info', + { + 'fields': + ('name', 'surname', 'email', 'school', 'shell', 'uid_number') + } + ), ('Permissions', {'fields': ('is_admin', )}), ) # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin # overrides get_fieldsets to use this attribute when creating a user. add_fieldsets = ( - (None, { - 'classes': ('wide',), - 'fields': ('pseudo', 'name', 'surname', 'email', 'school', 'is_admin', 'password1', 'password2')} + ( + None, + { + 'classes': ('wide',), + 'fields': ( + 'pseudo', + 'name', + 'surname', + 'email', + 'school', + 'is_admin', + 'password1', + 'password2' + ) + } ), ) search_fields = ('pseudo',) ordering = ('pseudo',) filter_horizontal = () + class ServiceUserAdmin(VersionAdmin, BaseUserAdmin): + """Gestion d'un service user admin : champs personnels, + mot de passe; etc""" # The forms to add and change user instances form = ServiceUserChangeForm add_form = ServiceUserCreationForm @@ -131,15 +190,19 @@ class ServiceUserAdmin(VersionAdmin, BaseUserAdmin): # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin # overrides get_fieldsets to use this attribute when creating a user. add_fieldsets = ( - (None, { - 'classes': ('wide',), - 'fields': ('pseudo', 'password1', 'password2')} + ( + None, + { + 'classes': ('wide',), + 'fields': ('pseudo', 'password1', 'password2') + } ), ) search_fields = ('pseudo',) ordering = ('pseudo',) filter_horizontal = () + admin.site.register(User, UserAdmin) admin.site.register(ServiceUser, ServiceUserAdmin) admin.site.register(LdapUser, LdapUserAdmin) From b8887b2b61c35709a33934ca05f0ab3296a061bf Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sun, 15 Oct 2017 03:47:17 +0200 Subject: [PATCH 9/9] Menage --- logs/views.py | 9 ++- machines/views.py | 24 +------- re2o/utils.py | 136 ++++++++++++++++++++++++++++++++++++++++++++++ users/models.py | 56 ------------------- users/views.py | 4 +- 5 files changed, 144 insertions(+), 85 deletions(-) create mode 100644 re2o/utils.py diff --git a/logs/views.py b/logs/views.py index 13879c86..e21d4de6 100644 --- a/logs/views.py +++ b/logs/views.py @@ -47,18 +47,17 @@ from reversion.models import Revision from reversion.models import Version, ContentType 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 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 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 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], diff --git a/machines/views.py b/machines/views.py index 0e00dc67..0bfe36f0 100644 --- a/machines/views.py +++ b/machines/views.py @@ -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.bootstrap_form_typeahead 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 diff --git a/re2o/utils.py b/re2o/utils.py new file mode 100644 index 00000000..e2ca6db9 --- /dev/null +++ b/re2o/utils.py @@ -0,0 +1,136 @@ +# -*- 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 © 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. + +# -*- coding: utf-8 -*- +# David Sinquin, Gabriel Détraz, Goulven Kermarec +""" +Regroupe les fonctions transversales utiles + +Fonction : + - récupérer tous les utilisateurs actifs + - récupérer toutes les machines + - récupérer tous les bans + etc +""" + + +from __future__ import unicode_literals + + +from django.utils import timezone +from django.db.models import Q + +from cotisations.models import Cotisation, Facture, Paiement, Vente +from machines.models import Domain, Interface, Machine +from users.models import User, Ban, Whitelist +from preferences.models import Service + +DT_NOW = timezone.now() + + +def all_adherent(search_time=DT_NOW): + """ Fonction renvoyant tous les users adherents. Optimisee pour n'est + qu'une seule requete sql + Inspecte les factures de l'user et ses cotisation, regarde si elles + sont posterieur à now (end_time)""" + 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=search_time) + ) + ) + ).distinct() + + +def all_baned(search_time=DT_NOW): + """ Fonction renvoyant tous les users bannis """ + return User.objects.filter( + ban__in=Ban.objects.filter( + date_end__gt=search_time + ) + ).distinct() + + +def all_whitelisted(search_time=DT_NOW): + """ Fonction renvoyant tous les users whitelistes """ + return User.objects.filter( + whitelist__in=Whitelist.objects.filter( + date_end__gt=search_time + ) + ).distinct() + + +def all_has_access(search_time=DT_NOW): + """ Renvoie tous les users beneficiant d'une connexion + : user adherent ou whiteliste et non banni """ + return User.objects.filter( + Q(state=User.STATE_ACTIVE) & + ~Q(ban__in=Ban.objects.filter(date_end__gt=search_time)) & + (Q(whitelist__in=Whitelist.objects.filter(date_end__gt=search_time)) | + 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=search_time) + ) + ))) + ).distinct() + + +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) diff --git a/users/models.py b/users/models.py index a0ef29f8..2f8f888f 100644 --- a/users/models.py +++ b/users/models.py @@ -144,62 +144,6 @@ def get_admin_right(): return admin_right -def all_adherent(search_time=DT_NOW): - """ Fonction renvoyant tous les users adherents. Optimisee pour n'est - qu'une seule requete sql - Inspecte les factures de l'user et ses cotisation, regarde si elles - sont posterieur à now (end_time)""" - 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=search_time) - ) - ) - ).distinct() - - -def all_baned(search_time=DT_NOW): - """ Fonction renvoyant tous les users bannis """ - return User.objects.filter( - ban__in=Ban.objects.filter( - date_end__gt=search_time - ) - ).distinct() - - -def all_whitelisted(search_time=DT_NOW): - """ Fonction renvoyant tous les users whitelistes """ - return User.objects.filter( - whitelist__in=Whitelist.objects.filter( - date_end__gt=search_time - ) - ).distinct() - - -def all_has_access(search_time=DT_NOW): - """ Renvoie tous les users beneficiant d'une connexion - : user adherent ou whiteliste et non banni """ - return User.objects.filter( - Q(state=User.STATE_ACTIVE) & - ~Q(ban__in=Ban.objects.filter(date_end__gt=search_time)) & - (Q(whitelist__in=Whitelist.objects.filter(date_end__gt=search_time)) | - 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=search_time) - ) - ))) - ).distinct() - - class UserManager(BaseUserManager): """User manager basique de django""" def _create_user( diff --git a/users/views.py b/users/views.py index e553f5b8..5b0b3910 100644 --- a/users/views.py +++ b/users/views.py @@ -53,7 +53,7 @@ from reversion.models import Version from reversion import revisions as reversion from users.serializers import MailSerializer from users.models import User, Right, Ban, Whitelist, School, ListRight -from users.models import Request, ServiceUser, all_has_access +from users.models import Request, ServiceUser from users.forms import DelRightForm, BanForm, WhitelistForm, DelSchoolForm from users.forms import DelListRightForm, NewListRightForm from users.forms import InfoForm, BaseInfoForm, StateForm @@ -65,7 +65,7 @@ from machines.models import Machine from preferences.models import OptionalUser, GeneralOption from re2o.views import form - +from re2o.utils import all_has_access def password_change_action(u_form, user, request, req=False): """ Fonction qui effectue le changeemnt de mdp bdd"""