8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-23 11:53:12 +00:00

Merge branch 'profil_frontend' into 'master'

Profil frontend

See merge request federez/re2o!111
This commit is contained in:
guimoz 2018-04-03 18:48:01 +02:00
commit 39a1c4eb86
29 changed files with 1232 additions and 841 deletions

View file

@ -45,9 +45,9 @@ from preferences.models import OptionalUser
from users.models import User
from re2o.field_permissions import FieldPermissionFormMixin
from re2o.mixins import FormRevMixin
class NewFactureForm(ModelForm):
class NewFactureForm(FormRevMixin, ModelForm):
"""Creation d'une facture, moyen de paiement, banque et numero
de cheque"""
def __init__(self, *args, **kwargs):
@ -96,7 +96,7 @@ class CreditSoldeForm(NewFactureForm):
montant = forms.DecimalField(max_digits=5, decimal_places=2, required=True)
class SelectUserArticleForm(Form):
class SelectUserArticleForm(FormRevMixin, Form):
"""Selection d'un article lors de la creation d'une facture"""
article = forms.ModelChoiceField(
queryset=Article.objects.filter(Q(type_user='All') | Q(type_user='Adherent')),
@ -158,7 +158,7 @@ class EditFactureForm(FieldPermissionFormMixin, NewFactureForm):
self.fields['valid'].label = 'Validité de la facture'
class ArticleForm(ModelForm):
class ArticleForm(FormRevMixin, ModelForm):
"""Creation d'un article. Champs : nom, cotisation, durée"""
class Meta:
model = Article
@ -170,7 +170,7 @@ class ArticleForm(ModelForm):
self.fields['name'].label = "Désignation de l'article"
class DelArticleForm(Form):
class DelArticleForm(FormRevMixin, Form):
"""Suppression d'un ou plusieurs articles en vente. Choix
parmis les modèles"""
articles = forms.ModelMultipleChoiceField(
@ -188,7 +188,7 @@ class DelArticleForm(Form):
self.fields['articles'].queryset = Article.objects.all()
class PaiementForm(ModelForm):
class PaiementForm(FormRevMixin, ModelForm):
"""Creation d'un moyen de paiement, champ text moyen et type
permettant d'indiquer si il s'agit d'un chèque ou non pour le form"""
class Meta:
@ -202,7 +202,7 @@ class PaiementForm(ModelForm):
self.fields['type_paiement'].label = 'Type de paiement à ajouter'
class DelPaiementForm(Form):
class DelPaiementForm(FormRevMixin, Form):
"""Suppression d'un ou plusieurs moyens de paiements, selection
parmis les models"""
paiements = forms.ModelMultipleChoiceField(
@ -220,7 +220,7 @@ class DelPaiementForm(Form):
self.fields['paiements'].queryset = Paiement.objects.all()
class BanqueForm(ModelForm):
class BanqueForm(FormRevMixin, ModelForm):
"""Creation d'une banque, field name"""
class Meta:
model = Banque
@ -232,7 +232,7 @@ class BanqueForm(ModelForm):
self.fields['name'].label = 'Banque à ajouter'
class DelBanqueForm(Form):
class DelBanqueForm(FormRevMixin, Form):
"""Selection d'une ou plusieurs banques, pour suppression"""
banques = forms.ModelMultipleChoiceField(
queryset=Banque.objects.none(),
@ -283,7 +283,7 @@ class NewFactureSoldeForm(NewFactureForm):
return cleaned_data
class RechargeForm(Form):
class RechargeForm(FormRevMixin, Form):
value = forms.FloatField(
label='Valeur',
min_value=0.01,

View file

@ -57,9 +57,10 @@ from django.utils import timezone
from machines.models import regen
from re2o.field_permissions import FieldPermissionModelMixin
from re2o.mixins import AclMixin
from re2o.mixins import AclMixin, RevMixin
class Facture(AclMixin, FieldPermissionModelMixin, models.Model):
class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
""" Définition du modèle des factures. Une facture regroupe une ou
plusieurs ventes, rattachée à un user, et reliée à un moyen de paiement
et si il y a lieu un numero pour les chèques. Possède les valeurs
@ -87,6 +88,11 @@ class Facture(AclMixin, FieldPermissionModelMixin, models.Model):
("change_all_facture", "Superdroit, peut modifier toutes les factures"),
)
def linked_objects(self):
"""Return linked objects : machine and domain.
Usefull in history display"""
return self.vente_set.all()
def prix(self):
"""Renvoie le prix brut sans les quantités. Méthode
dépréciée"""
@ -180,7 +186,7 @@ def facture_post_delete(sender, **kwargs):
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
class Vente(AclMixin, models.Model):
class Vente(RevMixin, AclMixin, models.Model):
"""Objet vente, contient une quantité, une facture parente, un nom,
un prix. Peut-être relié à un objet cotisation, via le boolean
iscotisation"""
@ -325,7 +331,7 @@ def vente_post_delete(sender, **kwargs):
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
class Article(AclMixin, models.Model):
class Article(RevMixin, AclMixin, models.Model):
"""Liste des articles en vente : prix, nom, et attribut iscotisation
et duree si c'est une cotisation"""
PRETTY_NAME = "Articles en vente"
@ -381,7 +387,7 @@ class Article(AclMixin, models.Model):
return self.name
class Banque(AclMixin, models.Model):
class Banque(RevMixin, AclMixin, models.Model):
"""Liste des banques"""
PRETTY_NAME = "Banques enregistrées"
@ -396,7 +402,7 @@ class Banque(AclMixin, models.Model):
return self.name
class Paiement(AclMixin, models.Model):
class Paiement(RevMixin, AclMixin, models.Model):
"""Moyens de paiement"""
PRETTY_NAME = "Moyens de paiement"
PAYMENT_TYPES = (
@ -426,7 +432,7 @@ class Paiement(AclMixin, models.Model):
super(Paiement, self).save(*args, **kwargs)
class Cotisation(AclMixin, models.Model):
class Cotisation(RevMixin, AclMixin, models.Model):
"""Objet cotisation, debut et fin, relié en onetoone à une vente"""
PRETTY_NAME = "Cotisations"

View file

@ -28,7 +28,6 @@ import os
from django.urls import reverse
from django.shortcuts import render, redirect
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.core.validators import MaxValueValidator
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib import messages
@ -39,14 +38,13 @@ from django.forms import modelformset_factory, formset_factory
from django.utils import timezone
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.debug import sensitive_variables
from reversion import revisions as reversion
from reversion.models import Version
# Import des models, forms et fonctions re2o
from reversion import revisions as reversion
from users.models import User
from re2o.settings import LOGO_PATH
from re2o import settings
from re2o.views import form
from re2o.utils import SortTable
from re2o.utils import SortTable, re2o_paginator
from re2o.acl import (
can_create,
can_edit,
@ -126,10 +124,7 @@ def new_facture(request, user, userid):
'users:profil',
kwargs={'userid': userid}
))
with transaction.atomic(), reversion.create_revision():
new_facture_instance.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
for art_item in articles:
if art_item.cleaned_data:
article = art_item.cleaned_data['article']
@ -142,10 +137,7 @@ def new_facture(request, user, userid):
duration=article.duration,
number=quantity
)
with transaction.atomic(), reversion.create_revision():
new_vente.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
if any(art_item.cleaned_data['article'].type_cotisation
for art_item in articles if art_item.cleaned_data):
messages.success(
@ -257,13 +249,9 @@ def edit_facture(request, facture, factureid):
)
vente_form = vente_form_set(request.POST or None, queryset=ventes_objects)
if facture_form.is_valid() and vente_form.is_valid():
with transaction.atomic(), reversion.create_revision():
if facture_form.changed_data:
facture_form.save()
vente_form.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for form in vente_form for field
in facture_form.changed_data + form.changed_data))
messages.success(request, "La facture a bien été modifiée")
return redirect(reverse('cotisations:index'))
return form({
@ -278,9 +266,7 @@ def del_facture(request, facture, factureid):
"""Suppression d'une facture. Supprime en cascade les ventes
et cotisations filles"""
if request.method == "POST":
with transaction.atomic(), reversion.create_revision():
facture.delete()
reversion.set_user(request.user)
messages.success(request, "La facture a été détruite")
return redirect(reverse('cotisations:index'))
return form({
@ -297,21 +283,15 @@ def credit_solde(request, user, userid):
facture = CreditSoldeForm(request.POST or None)
if facture.is_valid():
facture_instance = facture.save(commit=False)
with transaction.atomic(), reversion.create_revision():
facture_instance.user = user
facture_instance.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
new_vente = Vente.objects.create(
facture=facture_instance,
name="solde",
prix=facture.cleaned_data['montant'],
number=1
)
with transaction.atomic(), reversion.create_revision():
new_vente.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Solde modifié")
return redirect(reverse('cotisations:index'))
return form({'factureform': facture, 'action_name' : 'Créditer'}, 'cotisations/facture.html', request)
@ -329,10 +309,7 @@ def add_article(request):
PAS de conséquence sur les ventes déjà faites"""
article = ArticleForm(request.POST or None)
if article.is_valid():
with transaction.atomic(), reversion.create_revision():
article.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "L'article a été ajouté")
return redirect(reverse('cotisations:index-article'))
return form({'factureform': article, 'action_name' : 'Ajouter'}, 'cotisations/facture.html', request)
@ -345,14 +322,8 @@ def edit_article(request, article_instance, articleid):
Réservé au trésorier"""
article = ArticleForm(request.POST or None, instance=article_instance)
if article.is_valid():
with transaction.atomic(), reversion.create_revision():
if article.changed_data:
article.save()
reversion.set_user(request.user)
reversion.set_comment(
"Champs modifié(s) : %s" % ', '.join(
field for field in article.changed_data
)
)
messages.success(request, "Type d'article modifié")
return redirect(reverse('cotisations:index-article'))
return form({'factureform': article, 'action_name' : 'Editer'}, 'cotisations/facture.html', request)
@ -365,9 +336,7 @@ def del_article(request, instances):
article = DelArticleForm(request.POST or None, instances=instances)
if article.is_valid():
article_del = article.cleaned_data['articles']
with transaction.atomic(), reversion.create_revision():
article_del.delete()
reversion.set_user(request.user)
messages.success(request, "Le/les articles ont été supprimé")
return redirect(reverse('cotisations:index-article'))
return form({'factureform': article, 'action_name' : 'Supprimer'}, 'cotisations/facture.html', request)
@ -380,10 +349,7 @@ def add_paiement(request):
via foreign key"""
paiement = PaiementForm(request.POST or None)
if paiement.is_valid():
with transaction.atomic(), reversion.create_revision():
paiement.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Le moyen de paiement a été ajouté")
return redirect(reverse('cotisations:index-paiement'))
return form({'factureform': paiement, 'action_name' : 'Ajouter'}, 'cotisations/facture.html', request)
@ -395,14 +361,8 @@ def edit_paiement(request, paiement_instance, paiementid):
"""Edition d'un moyen de paiement"""
paiement = PaiementForm(request.POST or None, instance=paiement_instance)
if paiement.is_valid():
with transaction.atomic(), reversion.create_revision():
if paiement.changed_data:
paiement.save()
reversion.set_user(request.user)
reversion.set_comment(
"Champs modifié(s) : %s" % ', '.join(
field for field in paiement.changed_data
)
)
messages.success(request, "Type de paiement modifié")
return redirect(reverse('cotisations:index-paiement'))
return form({'factureform': paiement, 'action_name' : 'Editer'}, 'cotisations/facture.html', request)
@ -417,10 +377,7 @@ def del_paiement(request, instances):
paiement_dels = paiement.cleaned_data['paiements']
for paiement_del in paiement_dels:
try:
with transaction.atomic(), reversion.create_revision():
paiement_del.delete()
reversion.set_user(request.user)
reversion.set_comment("Destruction")
messages.success(
request,
"Le moyen de paiement a été supprimé"
@ -441,10 +398,7 @@ def add_banque(request):
"""Ajoute une banque à la liste des banques"""
banque = BanqueForm(request.POST or None)
if banque.is_valid():
with transaction.atomic(), reversion.create_revision():
banque.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "La banque a été ajoutée")
return redirect(reverse('cotisations:index-banque'))
return form({'factureform': banque, 'action_name' : 'Ajouter'}, 'cotisations/facture.html', request)
@ -456,14 +410,8 @@ def edit_banque(request, banque_instance, banqueid):
"""Edite le nom d'une banque"""
banque = BanqueForm(request.POST or None, instance=banque_instance)
if banque.is_valid():
with transaction.atomic(), reversion.create_revision():
if banque.changed_data:
banque.save()
reversion.set_user(request.user)
reversion.set_comment(
"Champs modifié(s) : %s" % ', '.join(
field for field in banque.changed_data
)
)
messages.success(request, "Banque modifiée")
return redirect(reverse('cotisations:index-banque'))
return form({'factureform': banque, 'action_name' : 'Editer'}, 'cotisations/facture.html', request)
@ -478,10 +426,7 @@ def del_banque(request, instances):
banque_dels = banque.cleaned_data['banques']
for banque_del in banque_dels:
try:
with transaction.atomic(), reversion.create_revision():
banque_del.delete()
reversion.set_user(request.user)
reversion.set_comment("Destruction")
messages.success(request, "La banque a été supprimée")
except ProtectedError:
messages.error(request, "La banque %s est affectée à au moins\
@ -509,20 +454,11 @@ def control(request):
fields=('control', 'valid'),
extra=0
)
paginator = Paginator(facture_list, pagination_number)
page = request.GET.get('page')
try:
facture_list = paginator.page(page)
except PageNotAnInteger:
facture_list = paginator.page(1)
except EmptyPage:
facture_list = paginator.page(paginator.num.pages)
facture_list = re2o_paginator(request, facture_list, pagination_number)
controlform = controlform_set(request.POST or None, queryset=facture_list.object_list)
if controlform.is_valid():
with transaction.atomic(), reversion.create_revision():
controlform.save()
reversion.set_user(request.user)
reversion.set_comment("Controle trésorier")
reversion.set_comment("Controle")
return redirect(reverse('cotisations:control'))
return render(request, 'cotisations/control.html', {
'facture_list': facture_list,
@ -573,16 +509,7 @@ def index(request):
request.GET.get('order'),
SortTable.COTISATIONS_INDEX
)
paginator = Paginator(facture_list, pagination_number)
page = request.GET.get('page')
try:
facture_list = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
facture_list = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
facture_list = paginator.page(paginator.num_pages)
facture_list = re2o_paginator(request, facture_list, pagination_number)
return render(request, 'cotisations/index.html', {
'facture_list': facture_list
})
@ -630,10 +557,7 @@ def new_facture_solde(request, userid):
'users:profil',
kwargs={'userid': userid}
))
with transaction.atomic(), reversion.create_revision():
facture.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
for art_item in articles:
if art_item.cleaned_data:
article = art_item.cleaned_data['article']
@ -646,10 +570,7 @@ def new_facture_solde(request, userid):
duration=article.duration,
number=quantity
)
with transaction.atomic(), reversion.create_revision():
new_vente.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
if any(art_item.cleaned_data['article'].type_cotisation
for art_item in articles if art_item.cleaned_data):
messages.success(

View file

@ -39,7 +39,6 @@ from __future__ import unicode_literals
from django.urls import reverse
from django.shortcuts import render, redirect
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db.models import Count, Max
@ -100,6 +99,7 @@ from re2o.utils import (
all_baned,
all_has_access,
all_adherent,
re2o_paginator,
)
from re2o.acl import (
can_view_all,
@ -139,17 +139,7 @@ def index(request):
request.GET.get('order'),
SortTable.LOGS_INDEX
)
paginator = Paginator(versions, pagination_number)
page = request.GET.get('page')
try:
versions = paginator.page(page)
except PageNotAnInteger:
# 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.
versions = paginator.page(paginator.num_pages)
versions = re2o_paginator(request, versions, pagination_number)
# Force to have a list instead of QuerySet
versions.count(0)
# Items to remove later because invalid
@ -191,16 +181,7 @@ def stats_logs(request):
request.GET.get('order'),
SortTable.LOGS_STATS_LOGS
)
paginator = Paginator(revisions, pagination_number)
page = request.GET.get('page')
try:
revisions = paginator.page(page)
except PageNotAnInteger:
# 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.
revisions = paginator.page(paginator.num_pages)
revisions = re2o_paginator(request, revisions, pagination_number)
return render(request, 'logs/stats_logs.html', {
'revisions_list': revisions
})

View file

@ -39,6 +39,7 @@ from django.forms import ModelForm, Form
from django import forms
from re2o.field_permissions import FieldPermissionFormMixin
from re2o.mixins import FormRevMixin
from .models import (
Domain,
@ -61,7 +62,7 @@ from .models import (
)
class EditMachineForm(FieldPermissionFormMixin, ModelForm):
class EditMachineForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
"""Formulaire d'édition d'une machine"""
class Meta:
model = Machine
@ -79,7 +80,7 @@ class NewMachineForm(EditMachineForm):
fields = ['name']
class EditInterfaceForm(FieldPermissionFormMixin, ModelForm):
class EditInterfaceForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
"""Edition d'une interface. Edition complète"""
class Meta:
model = Interface
@ -125,7 +126,7 @@ class AddInterfaceForm(EditInterfaceForm):
fields = ['type', 'ipv4', 'mac_address', 'details']
class AliasForm(ModelForm):
class AliasForm(FormRevMixin, ModelForm):
"""Ajout d'un alias (et edition), CNAME, contenant nom et extension"""
class Meta:
model = Domain
@ -142,7 +143,7 @@ class AliasForm(ModelForm):
)
class DomainForm(ModelForm):
class DomainForm(FormRevMixin, ModelForm):
"""Ajout et edition d'un enregistrement de nom, relié à interface"""
class Meta:
model = Domain
@ -158,7 +159,7 @@ class DomainForm(ModelForm):
super(DomainForm, self).__init__(*args, prefix=prefix, **kwargs)
class DelAliasForm(Form):
class DelAliasForm(FormRevMixin, Form):
"""Suppression d'un ou plusieurs objets alias"""
alias = forms.ModelMultipleChoiceField(
queryset=Domain.objects.all(),
@ -174,7 +175,7 @@ class DelAliasForm(Form):
)
class MachineTypeForm(ModelForm):
class MachineTypeForm(FormRevMixin, ModelForm):
"""Ajout et edition d'un machinetype, relié à un iptype"""
class Meta:
model = MachineType
@ -187,7 +188,7 @@ class MachineTypeForm(ModelForm):
self.fields['ip_type'].label = "Type d'ip relié"
class DelMachineTypeForm(Form):
class DelMachineTypeForm(FormRevMixin, Form):
"""Suppression d'un ou plusieurs machinetype"""
machinetypes = forms.ModelMultipleChoiceField(
queryset=MachineType.objects.none(),
@ -204,7 +205,7 @@ class DelMachineTypeForm(Form):
self.fields['machinetypes'].queryset = MachineType.objects.all()
class IpTypeForm(ModelForm):
class IpTypeForm(FormRevMixin, ModelForm):
"""Formulaire d'ajout d'un iptype. Pas d'edition de l'ip de start et de
stop après creation"""
class Meta:
@ -226,7 +227,7 @@ class EditIpTypeForm(IpTypeForm):
'ouverture_ports']
class DelIpTypeForm(Form):
class DelIpTypeForm(FormRevMixin, Form):
"""Suppression d'un ou plusieurs iptype"""
iptypes = forms.ModelMultipleChoiceField(
queryset=IpType.objects.none(),
@ -243,7 +244,7 @@ class DelIpTypeForm(Form):
self.fields['iptypes'].queryset = IpType.objects.all()
class ExtensionForm(ModelForm):
class ExtensionForm(FormRevMixin, ModelForm):
"""Formulaire d'ajout et edition d'une extension"""
class Meta:
model = Extension
@ -258,7 +259,7 @@ class ExtensionForm(ModelForm):
self.fields['soa'].label = 'En-tête SOA à utiliser'
class DelExtensionForm(Form):
class DelExtensionForm(FormRevMixin, Form):
"""Suppression d'une ou plusieurs extensions"""
extensions = forms.ModelMultipleChoiceField(
queryset=Extension.objects.none(),
@ -275,7 +276,7 @@ class DelExtensionForm(Form):
self.fields['extensions'].queryset = Extension.objects.all()
class Ipv6ListForm(FieldPermissionFormMixin, ModelForm):
class Ipv6ListForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
"""Gestion des ipv6 d'une machine"""
class Meta:
model = Ipv6List
@ -286,7 +287,7 @@ class Ipv6ListForm(FieldPermissionFormMixin, ModelForm):
super(Ipv6ListForm, self).__init__(*args, prefix=prefix, **kwargs)
class SOAForm(ModelForm):
class SOAForm(FormRevMixin, ModelForm):
"""Ajout et edition d'un SOA"""
class Meta:
model = SOA
@ -297,7 +298,7 @@ class SOAForm(ModelForm):
super(SOAForm, self).__init__(*args, prefix=prefix, **kwargs)
class DelSOAForm(Form):
class DelSOAForm(FormRevMixin, Form):
"""Suppression d'un ou plusieurs SOA"""
soa = forms.ModelMultipleChoiceField(
queryset=SOA.objects.none(),
@ -314,7 +315,7 @@ class DelSOAForm(Form):
self.fields['soa'].queryset = SOA.objects.all()
class MxForm(ModelForm):
class MxForm(FormRevMixin, ModelForm):
"""Ajout et edition d'un MX"""
class Meta:
model = Mx
@ -327,7 +328,7 @@ class MxForm(ModelForm):
interface_parent=None
).select_related('extension')
class DelMxForm(Form):
class DelMxForm(FormRevMixin, Form):
"""Suppression d'un ou plusieurs MX"""
mx = forms.ModelMultipleChoiceField(
queryset=Mx.objects.none(),
@ -344,7 +345,7 @@ class DelMxForm(Form):
self.fields['mx'].queryset = Mx.objects.all()
class NsForm(ModelForm):
class NsForm(FormRevMixin, ModelForm):
"""Ajout d'un NS pour une zone
On exclue les CNAME dans les objets domain (interdit par la rfc)
donc on prend uniquemet """
@ -360,7 +361,7 @@ class NsForm(ModelForm):
).select_related('extension')
class DelNsForm(Form):
class DelNsForm(FormRevMixin, Form):
"""Suppresion d'un ou plusieurs NS"""
ns = forms.ModelMultipleChoiceField(
queryset=Ns.objects.none(),
@ -377,7 +378,7 @@ class DelNsForm(Form):
self.fields['ns'].queryset = Ns.objects.all()
class TxtForm(ModelForm):
class TxtForm(FormRevMixin, ModelForm):
"""Ajout d'un txt pour une zone"""
class Meta:
model = Txt
@ -388,7 +389,7 @@ class TxtForm(ModelForm):
super(TxtForm, self).__init__(*args, prefix=prefix, **kwargs)
class DelTxtForm(Form):
class DelTxtForm(FormRevMixin, Form):
"""Suppression d'un ou plusieurs TXT"""
txt = forms.ModelMultipleChoiceField(
queryset=Txt.objects.none(),
@ -405,7 +406,7 @@ class DelTxtForm(Form):
self.fields['txt'].queryset = Txt.objects.all()
class SrvForm(ModelForm):
class SrvForm(FormRevMixin, ModelForm):
"""Ajout d'un srv pour une zone"""
class Meta:
model = Srv
@ -416,7 +417,7 @@ class SrvForm(ModelForm):
super(SrvForm, self).__init__(*args, prefix=prefix, **kwargs)
class DelSrvForm(Form):
class DelSrvForm(FormRevMixin, Form):
"""Suppression d'un ou plusieurs Srv"""
srv = forms.ModelMultipleChoiceField(
queryset=Srv.objects.none(),
@ -433,7 +434,7 @@ class DelSrvForm(Form):
self.fields['srv'].queryset = Srv.objects.all()
class NasForm(ModelForm):
class NasForm(FormRevMixin, ModelForm):
"""Ajout d'un type de nas (machine d'authentification,
swicths, bornes...)"""
class Meta:
@ -445,7 +446,7 @@ class NasForm(ModelForm):
super(NasForm, self).__init__(*args, prefix=prefix, **kwargs)
class DelNasForm(Form):
class DelNasForm(FormRevMixin, Form):
"""Suppression d'un ou plusieurs nas"""
nas = forms.ModelMultipleChoiceField(
queryset=Nas.objects.none(),
@ -462,7 +463,7 @@ class DelNasForm(Form):
self.fields['nas'].queryset = Nas.objects.all()
class ServiceForm(ModelForm):
class ServiceForm(FormRevMixin, ModelForm):
"""Ajout et edition d'une classe de service : dns, dhcp, etc"""
class Meta:
model = Service
@ -482,7 +483,7 @@ class ServiceForm(ModelForm):
return instance
class DelServiceForm(Form):
class DelServiceForm(FormRevMixin, Form):
"""Suppression d'un ou plusieurs service"""
service = forms.ModelMultipleChoiceField(
queryset=Service.objects.none(),
@ -499,7 +500,7 @@ class DelServiceForm(Form):
self.fields['service'].queryset = Service.objects.all()
class VlanForm(ModelForm):
class VlanForm(FormRevMixin, ModelForm):
"""Ajout d'un vlan : id, nom"""
class Meta:
model = Vlan
@ -510,7 +511,7 @@ class VlanForm(ModelForm):
super(VlanForm, self).__init__(*args, prefix=prefix, **kwargs)
class DelVlanForm(Form):
class DelVlanForm(FormRevMixin, Form):
"""Suppression d'un ou plusieurs vlans"""
vlan = forms.ModelMultipleChoiceField(
queryset=Vlan.objects.none(),
@ -527,7 +528,7 @@ class DelVlanForm(Form):
self.fields['vlan'].queryset = Vlan.objects.all()
class EditOuverturePortConfigForm(ModelForm):
class EditOuverturePortConfigForm(FormRevMixin, ModelForm):
"""Edition de la liste des profils d'ouverture de ports
pour l'interface"""
class Meta:
@ -543,7 +544,7 @@ class EditOuverturePortConfigForm(ModelForm):
)
class EditOuverturePortListForm(ModelForm):
class EditOuverturePortListForm(FormRevMixin, ModelForm):
"""Edition de la liste des ports et profils d'ouverture
des ports"""
class Meta:

View file

@ -27,6 +27,7 @@ from datetime import timedelta
import re
from netaddr import mac_bare, EUI, IPSet, IPRange, IPNetwork, IPAddress
from ipaddress import IPv6Address
from itertools import chain
from django.db import models
from django.db.models.signals import post_save, post_delete
@ -39,13 +40,13 @@ from django.core.validators import MaxValueValidator
from macaddress.fields import MACAddressField
from re2o.field_permissions import FieldPermissionModelMixin
from re2o.mixins import AclMixin
from re2o.mixins import AclMixin, RevMixin
import users.models
import preferences.models
class Machine(FieldPermissionModelMixin, models.Model):
class Machine(RevMixin, FieldPermissionModelMixin, models.Model):
""" Class définissant une machine, object parent user, objets fils
interfaces"""
PRETTY_NAME = "Machine"
@ -72,6 +73,11 @@ class Machine(FieldPermissionModelMixin, models.Model):
"""
return Machine.objects.get(pk=machineid)
def linked_objects(self):
"""Return linked objects : machine and domain.
Usefull in history display"""
return chain(self.interface_set.all(), Domain.objects.filter(interface_parent__in=self.interface_set.all()))
@staticmethod
def can_change_user(user_request, *args, **kwargs):
"""Checks if an user is allowed to change the user who owns a
@ -163,7 +169,7 @@ class Machine(FieldPermissionModelMixin, models.Model):
return str(self.user) + ' - ' + str(self.id) + ' - ' + str(self.name)
class MachineType(AclMixin, models.Model):
class MachineType(RevMixin, AclMixin, models.Model):
""" Type de machine, relié à un type d'ip, affecté aux interfaces"""
PRETTY_NAME = "Type de machine"
@ -203,7 +209,7 @@ class MachineType(AclMixin, models.Model):
return self.type
class IpType(AclMixin, models.Model):
class IpType(RevMixin, AclMixin, models.Model):
""" Type d'ip, définissant un range d'ip, affecté aux machine types"""
PRETTY_NAME = "Type d'ip"
@ -333,7 +339,7 @@ class IpType(AclMixin, models.Model):
return self.type
class Vlan(AclMixin, models.Model):
class Vlan(RevMixin, AclMixin, models.Model):
""" Un vlan : vlan_id et nom
On limite le vlan id entre 0 et 4096, comme défini par la norme"""
PRETTY_NAME = "Vlans"
@ -351,7 +357,7 @@ class Vlan(AclMixin, models.Model):
return self.name
class Nas(AclMixin, models.Model):
class Nas(RevMixin, AclMixin, models.Model):
""" Les nas. Associé à un machine_type.
Permet aussi de régler le port_access_mode (802.1X ou mac-address) pour
le radius. Champ autocapture de la mac à true ou false"""
@ -390,7 +396,7 @@ class Nas(AclMixin, models.Model):
return self.name
class SOA(AclMixin, models.Model):
class SOA(RevMixin, AclMixin, models.Model):
"""
Un enregistrement SOA associé à une extension
Les valeurs par défault viennent des recommandations RIPE :
@ -467,7 +473,7 @@ class SOA(AclMixin, models.Model):
class Extension(AclMixin, models.Model):
class Extension(RevMixin, AclMixin, models.Model):
""" Extension dns type example.org. Précise si tout le monde peut
l'utiliser, associé à un origin (ip d'origine)"""
PRETTY_NAME = "Extensions dns"
@ -530,7 +536,7 @@ class Extension(AclMixin, models.Model):
super(Extension, self).clean(*args, **kwargs)
class Mx(AclMixin, models.Model):
class Mx(RevMixin, AclMixin, models.Model):
""" Entrées des MX. Enregistre la zone (extension) associée et la
priorité
Todo : pouvoir associer un MX à une interface """
@ -555,7 +561,7 @@ class Mx(AclMixin, models.Model):
return str(self.zone) + ' ' + str(self.priority) + ' ' + str(self.name)
class Ns(AclMixin, models.Model):
class Ns(RevMixin, AclMixin, models.Model):
"""Liste des enregistrements name servers par zone considéérée"""
PRETTY_NAME = "Enregistrements NS"
@ -576,7 +582,7 @@ class Ns(AclMixin, models.Model):
return str(self.zone) + ' ' + str(self.ns)
class Txt(AclMixin, models.Model):
class Txt(RevMixin, AclMixin, models.Model):
""" Un enregistrement TXT associé à une extension"""
PRETTY_NAME = "Enregistrement TXT"
@ -599,7 +605,7 @@ class Txt(AclMixin, models.Model):
return str(self.field1).ljust(15) + " IN TXT " + str(self.field2)
class Srv(AclMixin, models.Model):
class Srv(RevMixin, AclMixin, models.Model):
PRETTY_NAME = "Enregistrement Srv"
TCP = 'TCP'
@ -661,7 +667,7 @@ class Srv(AclMixin, models.Model):
str(self.port) + ' ' + str(self.target) + '.'
class Interface(AclMixin, FieldPermissionModelMixin,models.Model):
class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
""" Une interface. Objet clef de l'application machine :
- une address mac unique. Possibilité de la rendre unique avec le
typemachine
@ -908,7 +914,7 @@ class Interface(AclMixin, FieldPermissionModelMixin,models.Model):
return self.ipv4 and not self.has_private_ip()
class Ipv6List(AclMixin, FieldPermissionModelMixin, models.Model):
class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
PRETTY_NAME = 'Enregistrements Ipv6 des machines'
ipv6 = models.GenericIPAddressField(
@ -1012,7 +1018,7 @@ class Ipv6List(AclMixin, FieldPermissionModelMixin, models.Model):
return str(self.ipv6)
class Domain(AclMixin, models.Model):
class Domain(RevMixin, AclMixin, models.Model):
""" Objet domain. Enregistrement A et CNAME en même temps : permet de
stocker les alias et les nom de machines, suivant si interface_parent
ou cname sont remplis"""
@ -1170,7 +1176,7 @@ class Domain(AclMixin, models.Model):
return str(self.name) + str(self.extension)
class IpList(AclMixin, models.Model):
class IpList(RevMixin, AclMixin, models.Model):
PRETTY_NAME = "Addresses ipv4"
ipv4 = models.GenericIPAddressField(protocol='IPv4', unique=True)
@ -1202,7 +1208,7 @@ class IpList(AclMixin, models.Model):
return self.ipv4
class Service(AclMixin, models.Model):
class Service(RevMixin, AclMixin, models.Model):
""" Definition d'un service (dhcp, dns, etc)"""
PRETTY_NAME = "Services à générer (dhcp, dns, etc)"
@ -1256,7 +1262,7 @@ def regen(service):
return
class Service_link(AclMixin, models.Model):
class Service_link(RevMixin, AclMixin, models.Model):
""" Definition du lien entre serveurs et services"""
PRETTY_NAME = "Relation entre service et serveur"
@ -1287,7 +1293,7 @@ class Service_link(AclMixin, models.Model):
return str(self.server) + " " + str(self.service)
class OuverturePortList(AclMixin, models.Model):
class OuverturePortList(RevMixin, AclMixin, models.Model):
"""Liste des ports ouverts sur une interface."""
PRETTY_NAME = "Profil d'ouverture de ports"
@ -1346,7 +1352,7 @@ class OuverturePortList(AclMixin, models.Model):
)
class OuverturePort(AclMixin, models.Model):
class OuverturePort(RevMixin, AclMixin, models.Model):
"""
Représente un simple port ou une plage de ports.

View file

@ -38,7 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% can_edit alias %}
{% include 'buttons/edit.html' with href='machines:edit-alias' id=alias.id %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='alias' id=alias.id %}
{% include 'buttons/history.html' with href='machines:history' name='domain' id=alias.id %}
</td>
</tr>
{% endfor %}

View file

@ -33,13 +33,11 @@ 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, F
from django.forms import ValidationError, modelformset_factory
from django.db import transaction
from django.contrib.auth import authenticate, login
from django.views.decorators.csrf import csrf_exempt
@ -124,6 +122,7 @@ from re2o.utils import (
all_has_access,
filter_active_interfaces,
SortTable,
re2o_paginator,
)
from re2o.acl import (
can_create,
@ -238,20 +237,11 @@ def new_machine(request, user, userid):
domain.instance.interface_parent = new_interface
if domain.is_valid():
new_domain = domain.save(commit=False)
with transaction.atomic(), reversion.create_revision():
new_machine.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
new_interface.machine = new_machine
with transaction.atomic(), reversion.create_revision():
new_interface.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
new_domain.interface_parent = new_interface
with transaction.atomic(), reversion.create_revision():
new_domain.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "La machine a été créée")
return redirect(reverse(
'users:profil',
@ -287,18 +277,12 @@ def edit_interface(request, interface_instance, interfaceid):
new_machine = machine_form.save(commit=False)
new_interface = interface_form.save(commit=False)
new_domain = domain_form.save(commit=False)
with transaction.atomic(), reversion.create_revision():
if machine_form.changed_data:
new_machine.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in machine_form.changed_data))
with transaction.atomic(), reversion.create_revision():
if interface_form.changed_data:
new_interface.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in interface_form.changed_data))
with transaction.atomic(), reversion.create_revision():
if domain_form.changed_data:
new_domain.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in domain_form.changed_data))
messages.success(request, "La machine a été modifiée")
return redirect(reverse(
'users:profil',
@ -318,9 +302,7 @@ def edit_interface(request, interface_instance, interfaceid):
def del_machine(request, machine, machineid):
""" Supprime une machine, interfaces en mode cascade"""
if request.method == "POST":
with transaction.atomic(), reversion.create_revision():
machine.delete()
reversion.set_user(request.user)
messages.success(request, "La machine a été détruite")
return redirect(reverse(
'users:profil',
@ -342,15 +324,9 @@ def new_interface(request, machine, machineid):
new_interface.machine = machine
if domain_form.is_valid():
new_domain = domain_form.save(commit=False)
with transaction.atomic(), reversion.create_revision():
new_interface.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
new_domain.interface_parent = new_interface
with transaction.atomic(), reversion.create_revision():
new_domain.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "L'interface a été ajoutée")
return redirect(reverse(
'users:profil',
@ -370,11 +346,9 @@ def del_interface(request, interface, interfaceid):
""" Supprime une interface. Domain objet en mode cascade"""
if request.method == "POST":
machine = interface.machine
with transaction.atomic(), reversion.create_revision():
interface.delete()
if not machine.interface_set.all():
machine.delete()
reversion.set_user(request.user)
messages.success(request, "L'interface a été détruite")
return redirect(reverse(
'users:profil',
@ -390,10 +364,7 @@ def new_ipv6list(request, interface, interfaceid):
ipv6list_instance = Ipv6List(interface=interface)
ipv6 = Ipv6ListForm(request.POST or None, instance=ipv6list_instance, user=request.user)
if ipv6.is_valid():
with transaction.atomic(), reversion.create_revision():
ipv6.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Ipv6 ajoutée")
return redirect(reverse(
'machines:index-ipv6',
@ -407,10 +378,8 @@ def edit_ipv6list(request, ipv6list_instance, ipv6listid):
"""Edition d'une ipv6"""
ipv6 = Ipv6ListForm(request.POST or None, instance=ipv6list_instance, user=request.user)
if ipv6.is_valid():
with transaction.atomic(), reversion.create_revision():
if ipv6.changed_data:
ipv6.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in ipv6.changed_data))
messages.success(request, "Ipv6 modifiée")
return redirect(reverse(
'machines:index-ipv6',
@ -424,9 +393,7 @@ def del_ipv6list(request, ipv6list, ipv6listid):
""" Supprime une ipv6"""
if request.method == "POST":
interfaceid = ipv6list.interface.id
with transaction.atomic(), reversion.create_revision():
ipv6list.delete()
reversion.set_user(request.user)
messages.success(request, "L'ipv6 a été détruite")
return redirect(reverse(
'machines:index-ipv6',
@ -441,10 +408,7 @@ def add_iptype(request):
iptype = IpTypeForm(request.POST or None)
if iptype.is_valid():
with transaction.atomic(), reversion.create_revision():
iptype.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Ce type d'ip a été ajouté")
return redirect(reverse('machines:index-iptype'))
return form({'iptypeform': iptype, 'action_name' : 'Créer'}, 'machines/machine.html', request)
@ -456,10 +420,8 @@ def edit_iptype(request, iptype_instance, iptypeid):
iptype = EditIpTypeForm(request.POST or None, instance=iptype_instance)
if iptype.is_valid():
with transaction.atomic(), reversion.create_revision():
if iptype.changed_data:
iptype.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in iptype.changed_data))
messages.success(request, "Type d'ip modifié")
return redirect(reverse('machines:index-iptype'))
return form({'iptypeform': iptype, 'action_name' : 'Editer'}, 'machines/machine.html', request)
@ -473,9 +435,7 @@ def del_iptype(request, instances):
iptype_dels = iptype.cleaned_data['iptypes']
for iptype_del in iptype_dels:
try:
with transaction.atomic(), reversion.create_revision():
iptype_del.delete()
reversion.set_user(request.user)
messages.success(request, "Le type d'ip a été supprimé")
except ProtectedError:
messages.error(request, "Le type d'ip %s est affectée à au moins une machine, vous ne pouvez pas le supprimer" % iptype_del)
@ -488,10 +448,7 @@ def add_machinetype(request):
machinetype = MachineTypeForm(request.POST or None)
if machinetype.is_valid():
with transaction.atomic(), reversion.create_revision():
machinetype.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Ce type de machine a été ajouté")
return redirect(reverse('machines:index-machinetype'))
return form({'machinetypeform': machinetype, 'action_name' : 'Créer'}, 'machines/machine.html', request)
@ -499,13 +456,10 @@ def add_machinetype(request):
@login_required
@can_edit(MachineType)
def edit_machinetype(request, machinetype_instance, machinetypeid):
machinetype = MachineTypeForm(request.POST or None, instance=machinetype_instance)
if machinetype.is_valid():
with transaction.atomic(), reversion.create_revision():
if machinetype.changed_data:
machinetype.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in machinetype.changed_data))
messages.success(request, "Type de machine modifié")
return redirect(reverse('machines:index-machinetype'))
return form({'machinetypeform': machinetype, 'action_name' : 'Editer'}, 'machines/machine.html', request)
@ -518,9 +472,7 @@ def del_machinetype(request, instances):
machinetype_dels = machinetype.cleaned_data['machinetypes']
for machinetype_del in machinetype_dels:
try:
with transaction.atomic(), reversion.create_revision():
machinetype_del.delete()
reversion.set_user(request.user)
messages.success(request, "Le type de machine a été supprimé")
except ProtectedError:
messages.error(request, "Le type de machine %s est affectée à au moins une machine, vous ne pouvez pas le supprimer" % machinetype_del)
@ -530,13 +482,9 @@ def del_machinetype(request, instances):
@login_required
@can_create(Extension)
def add_extension(request):
extension = ExtensionForm(request.POST or None)
if extension.is_valid():
with transaction.atomic(), reversion.create_revision():
extension.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Cette extension a été ajoutée")
return redirect(reverse('machines:index-extension'))
return form({'extensionform': extension, 'action_name' : 'Créer'}, 'machines/machine.html', request)
@ -544,13 +492,10 @@ def add_extension(request):
@login_required
@can_edit(Extension)
def edit_extension(request, extension_instance, extensionid):
extension = ExtensionForm(request.POST or None, instance=extension_instance)
if extension.is_valid():
with transaction.atomic(), reversion.create_revision():
if extension.changed_data:
extension.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in extension.changed_data))
messages.success(request, "Extension modifiée")
return redirect(reverse('machines:index-extension'))
return form({'extensionform': extension, 'action_name' : 'Editer'}, 'machines/machine.html', request)
@ -563,9 +508,7 @@ def del_extension(request, instances):
extension_dels = extension.cleaned_data['extensions']
for extension_del in extension_dels:
try:
with transaction.atomic(), reversion.create_revision():
extension_del.delete()
reversion.set_user(request.user)
messages.success(request, "L'extension a été supprimée")
except ProtectedError:
messages.error(request, "L'extension %s est affectée à au moins un type de machine, vous ne pouvez pas la supprimer" % extension_del)
@ -575,13 +518,9 @@ def del_extension(request, instances):
@login_required
@can_create(SOA)
def add_soa(request):
soa = SOAForm(request.POST or None)
if soa.is_valid():
with transaction.atomic(), reversion.create_revision():
soa.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Cet enregistrement SOA a été ajouté")
return redirect(reverse('machines:index-extension'))
return form({'soaform': soa, 'action_name' : 'Créer'}, 'machines/machine.html', request)
@ -589,13 +528,10 @@ def add_soa(request):
@login_required
@can_edit(SOA)
def edit_soa(request, soa_instance, soaid):
soa = SOAForm(request.POST or None, instance=soa_instance)
if soa.is_valid():
with transaction.atomic(), reversion.create_revision():
if soa.changed_data:
soa.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in soa.changed_data))
messages.success(request, "SOA modifié")
return redirect(reverse('machines:index-extension'))
return form({'soaform': soa, 'action_name' : 'Editer'}, 'machines/machine.html', request)
@ -608,9 +544,7 @@ def del_soa(request, instances):
soa_dels = soa.cleaned_data['soa']
for soa_del in soa_dels:
try:
with transaction.atomic(), reversion.create_revision():
soa_del.delete()
reversion.set_user(request.user)
messages.success(request, "Le SOA a été supprimée")
except ProtectedError:
messages.error(request, "Erreur le SOA suivant %s ne peut être supprimé" % soa_del)
@ -620,13 +554,9 @@ def del_soa(request, instances):
@login_required
@can_create(Mx)
def add_mx(request):
mx = MxForm(request.POST or None)
if mx.is_valid():
with transaction.atomic(), reversion.create_revision():
mx.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Cet enregistrement mx a été ajouté")
return redirect(reverse('machines:index-extension'))
return form({'mxform': mx, 'action_name' : 'Créer'}, 'machines/machine.html', request)
@ -634,13 +564,10 @@ def add_mx(request):
@login_required
@can_edit(Mx)
def edit_mx(request, mx_instance, mxid):
mx = MxForm(request.POST or None, instance=mx_instance)
if mx.is_valid():
with transaction.atomic(), reversion.create_revision():
if mx.changed_data:
mx.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in mx.changed_data))
messages.success(request, "Mx modifié")
return redirect(reverse('machines:index-extension'))
return form({'mxform': mx, 'action_name' : 'Editer'}, 'machines/machine.html', request)
@ -653,9 +580,7 @@ def del_mx(request, instances):
mx_dels = mx.cleaned_data['mx']
for mx_del in mx_dels:
try:
with transaction.atomic(), reversion.create_revision():
mx_del.delete()
reversion.set_user(request.user)
messages.success(request, "L'mx a été supprimée")
except ProtectedError:
messages.error(request, "Erreur le Mx suivant %s ne peut être supprimé" % mx_del)
@ -665,13 +590,9 @@ def del_mx(request, instances):
@login_required
@can_create(Ns)
def add_ns(request):
ns = NsForm(request.POST or None)
if ns.is_valid():
with transaction.atomic(), reversion.create_revision():
ns.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Cet enregistrement ns a été ajouté")
return redirect(reverse('machines:index-extension'))
return form({'nsform': ns, 'action_name' : 'Créer'}, 'machines/machine.html', request)
@ -679,13 +600,10 @@ def add_ns(request):
@login_required
@can_edit(Ns)
def edit_ns(request, ns_instance, nsid):
ns = NsForm(request.POST or None, instance=ns_instance)
if ns.is_valid():
with transaction.atomic(), reversion.create_revision():
if ns.changed_data:
ns.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in ns.changed_data))
messages.success(request, "Ns modifié")
return redirect(reverse('machines:index-extension'))
return form({'nsform': ns, 'action_name' : 'Editer'}, 'machines/machine.html', request)
@ -698,9 +616,7 @@ def del_ns(request, instances):
ns_dels = ns.cleaned_data['ns']
for ns_del in ns_dels:
try:
with transaction.atomic(), reversion.create_revision():
ns_del.delete()
reversion.set_user(request.user)
messages.success(request, "Le ns a été supprimée")
except ProtectedError:
messages.error(request, "Erreur le Ns suivant %s ne peut être supprimé" % ns_del)
@ -710,13 +626,9 @@ def del_ns(request, instances):
@login_required
@can_create(Txt)
def add_txt(request):
txt = TxtForm(request.POST or None)
if txt.is_valid():
with transaction.atomic(), reversion.create_revision():
txt.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Cet enregistrement text a été ajouté")
return redirect(reverse('machines:index-extension'))
return form({'txtform': txt, 'action_name' : 'Créer'}, 'machines/machine.html', request)
@ -724,13 +636,10 @@ def add_txt(request):
@login_required
@can_edit(Txt)
def edit_txt(request, txt_instance, txtid):
txt = TxtForm(request.POST or None, instance=txt_instance)
if txt.is_valid():
with transaction.atomic(), reversion.create_revision():
if txt.changed_data:
txt.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in txt.changed_data))
messages.success(request, "Txt modifié")
return redirect(reverse('machines:index-extension'))
return form({'txtform': txt, 'action_name' : 'Editer'}, 'machines/machine.html', request)
@ -743,9 +652,7 @@ def del_txt(request, instances):
txt_dels = txt.cleaned_data['txt']
for txt_del in txt_dels:
try:
with transaction.atomic(), reversion.create_revision():
txt_del.delete()
reversion.set_user(request.user)
messages.success(request, "Le txt a été supprimé")
except ProtectedError:
messages.error(request, "Erreur le Txt suivant %s ne peut être supprimé" % txt_del)
@ -755,13 +662,9 @@ def del_txt(request, instances):
@login_required
@can_create(Srv)
def add_srv(request):
srv = SrvForm(request.POST or None)
if srv.is_valid():
with transaction.atomic(), reversion.create_revision():
srv.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Cet enregistrement srv a été ajouté")
return redirect(reverse('machines:index-extension'))
return form({'srvform': srv, 'action_name' : 'Créer'}, 'machines/machine.html', request)
@ -769,13 +672,10 @@ def add_srv(request):
@login_required
@can_edit(Srv)
def edit_srv(request, srv_instance, srvid):
srv = SrvForm(request.POST or None, instance=srv_instance)
if srv.is_valid():
with transaction.atomic(), reversion.create_revision():
if srv.changed_data:
srv.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in srv.changed_data))
messages.success(request, "Srv modifié")
return redirect(reverse('machines:index-extension'))
return form({'srvform': srv, 'action_name' : 'Editer'}, 'machines/machine.html', request)
@ -788,9 +688,7 @@ def del_srv(request, instances):
srv_dels = srv.cleaned_data['srv']
for srv_del in srv_dels:
try:
with transaction.atomic(), reversion.create_revision():
srv_del.delete()
reversion.set_user(request.user)
messages.success(request, "L'srv a été supprimée")
except ProtectedError:
messages.error(request, "Erreur le Srv suivant %s ne peut être supprimé" % srv_del)
@ -801,15 +699,11 @@ def del_srv(request, instances):
@can_create(Domain)
@can_edit(Interface)
def add_alias(request, interface, interfaceid):
alias = AliasForm(request.POST or None, user=request.user)
if alias.is_valid():
alias = alias.save(commit=False)
alias.cname = interface.domain
with transaction.atomic(), reversion.create_revision():
alias.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Cet alias a été ajouté")
return redirect(reverse(
'machines:index-alias',
@ -820,13 +714,10 @@ def add_alias(request, interface, interfaceid):
@login_required
@can_edit(Domain)
def edit_alias(request, domain_instance, domainid):
alias = AliasForm(request.POST or None, instance=domain_instance, user=request.user)
if alias.is_valid():
with transaction.atomic(), reversion.create_revision():
if alias.changed_data:
domain_instance = alias.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in alias.changed_data))
messages.success(request, "Alias modifié")
return redirect(reverse(
'machines:index-alias',
@ -842,9 +733,7 @@ def del_alias(request, interface, interfaceid):
alias_dels = alias.cleaned_data['alias']
for alias_del in alias_dels:
try:
with transaction.atomic(), reversion.create_revision():
alias_del.delete()
reversion.set_user(request.user)
messages.success(request, "L'alias %s a été supprimé" % alias_del)
except ProtectedError:
messages.error(request, "Erreur l'alias suivant %s ne peut être supprimé" % alias_del)
@ -858,13 +747,9 @@ def del_alias(request, interface, interfaceid):
@login_required
@can_create(Service)
def add_service(request):
service = ServiceForm(request.POST or None)
if service.is_valid():
with transaction.atomic(), reversion.create_revision():
service.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Cet enregistrement service a été ajouté")
return redirect(reverse('machines:index-service'))
return form({'serviceform': service, 'action_name' : 'Créer'}, 'machines/machine.html', request)
@ -872,13 +757,10 @@ def add_service(request):
@login_required
@can_edit(Service)
def edit_service(request, service_instance, serviceid):
service = ServiceForm(request.POST or None, instance=service_instance)
if service.is_valid():
with transaction.atomic(), reversion.create_revision():
if service.changed_data:
service.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in service.changed_data))
messages.success(request, "Service modifié")
return redirect(reverse('machines:index-service'))
return form({'serviceform': service, 'action_name' : 'Editer'}, 'machines/machine.html', request)
@ -891,9 +773,7 @@ def del_service(request, instances):
service_dels = service.cleaned_data['service']
for service_del in service_dels:
try:
with transaction.atomic(), reversion.create_revision():
service_del.delete()
reversion.set_user(request.user)
messages.success(request, "Le service a été supprimée")
except ProtectedError:
messages.error(request, "Erreur le service suivant %s ne peut être supprimé" % service_del)
@ -903,13 +783,9 @@ def del_service(request, instances):
@login_required
@can_create(Vlan)
def add_vlan(request):
vlan = VlanForm(request.POST or None)
if vlan.is_valid():
with transaction.atomic(), reversion.create_revision():
vlan.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Cet enregistrement vlan a été ajouté")
return redirect(reverse('machines:index-vlan'))
return form({'vlanform': vlan, 'action_name' : 'Créer'}, 'machines/machine.html', request)
@ -917,13 +793,10 @@ def add_vlan(request):
@login_required
@can_edit(Vlan)
def edit_vlan(request, vlan_instance, vlanid):
vlan = VlanForm(request.POST or None, instance=vlan_instance)
if vlan.is_valid():
with transaction.atomic(), reversion.create_revision():
if vlan.changed_data:
vlan.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in vlan.changed_data))
messages.success(request, "Vlan modifié")
return redirect(reverse('machines:index-vlan'))
return form({'vlanform': vlan, 'action_name' : 'Editer'}, 'machines/machine.html', request)
@ -936,9 +809,7 @@ def del_vlan(request, instances):
vlan_dels = vlan.cleaned_data['vlan']
for vlan_del in vlan_dels:
try:
with transaction.atomic(), reversion.create_revision():
vlan_del.delete()
reversion.set_user(request.user)
messages.success(request, "Le vlan a été supprimée")
except ProtectedError:
messages.error(request, "Erreur le Vlan suivant %s ne peut être supprimé" % vlan_del)
@ -948,13 +819,9 @@ def del_vlan(request, instances):
@login_required
@can_create(Nas)
def add_nas(request):
nas = NasForm(request.POST or None)
if nas.is_valid():
with transaction.atomic(), reversion.create_revision():
nas.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Cet enregistrement nas a été ajouté")
return redirect(reverse('machines:index-nas'))
return form({'nasform': nas, 'action_name' : 'Créer'}, 'machines/machine.html', request)
@ -962,13 +829,10 @@ def add_nas(request):
@login_required
@can_edit(Nas)
def edit_nas(request, nas_instance, nasid):
nas = NasForm(request.POST or None, instance=nas_instance)
if nas.is_valid():
with transaction.atomic(), reversion.create_revision():
if nas.changed_data:
nas.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in nas.changed_data))
messages.success(request, "Nas modifié")
return redirect(reverse('machines:index-nas'))
return form({'nasform': nas, 'action_name' : 'Editer'}, 'machines/machine.html', request)
@ -981,9 +845,7 @@ def del_nas(request, instances):
nas_dels = nas.cleaned_data['nas']
for nas_del in nas_dels:
try:
with transaction.atomic(), reversion.create_revision():
nas_del.delete()
reversion.set_user(request.user)
messages.success(request, "Le nas a été supprimé")
except ProtectedError:
messages.error(request, "Erreur le Nas suivant %s ne peut être supprimé" % nas_del)
@ -1001,16 +863,7 @@ def index(request):
request.GET.get('order'),
SortTable.MACHINES_INDEX
)
paginator = Paginator(machines_list, pagination_large_number)
page = request.GET.get('page')
try:
machines_list = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
machines_list = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
machines_list = paginator.page(paginator.num_pages)
machines_list = re2o_paginator(request, machines_list, pagination_large_number)
return render(request, 'machines/index.html', {'machines_list': machines_list})
@login_required
@ -1084,7 +937,6 @@ def index_portlist(request):
@login_required
@can_edit(OuverturePortList)
def edit_portlist(request, ouvertureportlist_instance, ouvertureportlistid):
port_list = EditOuverturePortListForm(request.POST or None, instance=ouvertureportlist_instance)
port_formset = modelformset_factory(
OuverturePort,
@ -1095,7 +947,10 @@ def edit_portlist(request, ouvertureportlist_instance, ouvertureportlistid):
validate_min=True,
)(request.POST or None, queryset=ouvertureportlist_instance.ouvertureport_set.all())
if port_list.is_valid() and port_formset.is_valid():
if port_list.changed_data:
pl = port_list.save()
else:
pl = ouvertureportlist_instance
instances = port_formset.save(commit=False)
for to_delete in port_formset.deleted_objects:
to_delete.delete()
@ -1116,7 +971,6 @@ def del_portlist(request, port_list_instance, ouvertureportlistid):
@login_required
@can_create(OuverturePortList)
def add_portlist(request):
port_list = EditOuverturePortListForm(request.POST or None)
port_formset = modelformset_factory(
OuverturePort,
@ -1152,6 +1006,7 @@ def configure_ports(request, interface_instance, interfaceid):
messages.error(request, "Attention, l'ipv4 n'est pas publique, l'ouverture n'aura pas d'effet en v4")
interface = EditOuverturePortConfigForm(request.POST or None, instance=interface_instance)
if interface.is_valid():
if interface.changed_data:
interface.save()
messages.success(request, "Configuration des ports mise à jour.")
return redirect(reverse('machines:index'))

View file

@ -32,7 +32,6 @@ from __future__ import unicode_literals
from django.urls import reverse
from django.shortcuts import render, redirect
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required
from django.db.models import ProtectedError

View file

@ -4,6 +4,7 @@
# quelques clics.
#
# Copyright © 2018 Gabriel Détraz
# Copyright © 2017 Charlie Jacomme
#
# 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
@ -19,6 +20,29 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from reversion import revisions as reversion
class RevMixin(object):
def save(self, *args, **kwargs):
if self.pk is None:
reversion.set_comment("Création")
return super(RevMixin, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
reversion.set_comment("Suppresion")
return super(RevMixin, self).delete(*args, **kwargs)
class FormRevMixin(object):
def save(self, *args, **kwargs):
if reversion.get_comment() != "" and self.changed_data != []:
reversion.set_comment(reversion.get_comment() + ",%s" % ', '.join(field for field in self.changed_data))
elif self.changed_data:
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in self.changed_data))
return super(FormRevMixin, self).save(*args, **kwargs)
class AclMixin(object):
"""This mixin is used in nearly every class/models defined in re2o apps.
It is used by acl, in models (decorators can_...) and in templates tags

86
re2o/script_utils.py Normal file
View file

@ -0,0 +1,86 @@
# ⁻*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2018 Lev-Arcady Sellem
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import os, sys, pwd
proj_path="/var/www/re2o"
os.environ.setdefault("DJANGO_SETTINGS_MODULE","re2o.settings")
sys.path.append(proj_path)
os.chdir(proj_path)
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
from django.core.management.base import CommandError
from users.models import User
from django.utils.html import strip_tags
from reversion import revisions as reversion
from django.db import transaction
from getpass import getpass
def get_user(pseudo):
"""Cherche un utilisateur re2o à partir de son pseudo"""
user = User.objects.filter(pseudo=pseudo)
if len(user)==0:
raise CommandError("Utilisateur invalide")
if len(user)>1:
raise CommandError("Plusieurs utilisateurs correspondant à ce pseudo. Ceci NE DEVRAIT PAS arriver")
return user[0]
def get_system_user():
"""Retourne l'utilisateur système ayant lancé la commande"""
return pwd.getpwuid(int(os.getenv("SUDO_UID") or os.getuid())).pw_name
def form_cli(Form,user,action,*args,**kwargs):
"""
Remplit un formulaire à partir de la ligne de commande
Form : le formulaire (sous forme de classe) à remplir
user : l'utilisateur re2o faisant la modification
action : l'action réalisée par le formulaire (pour les logs)
Les arguments suivants sont transmis tels quels au formulaire.
"""
data={}
dumb_form = Form(user=user,*args,**kwargs)
for key in dumb_form.fields:
if not dumb_form.fields[key].widget.input_type=='hidden':
if dumb_form.fields[key].widget.input_type=='password':
data[key]=getpass("%s : " % dumb_form.fields[key].label)
else:
data[key]=input("%s : " % dumb_form.fields[key].label)
form = Form(data,user=user,*args,**kwargs)
if not form.is_valid():
sys.stderr.write("Erreurs : \n")
for err in form.errors:
#Oui, oui, on gère du HTML là où d'autres ont eu la lumineuse idée de le mettre
sys.stderr.write("\t%s : %s\n" % (err,strip_tags(form.errors[err])))
raise CommandError("Formulaire invalide")
with transaction.atomic(), reversion.create_revision():
form.save()
reversion.set_user(user)
reversion.set_comment(action)
sys.stdout.write("%s : effectué. La modification peut prendre quelques minutes pour s'appliquer.\n" % action)

View file

@ -89,6 +89,7 @@ MIDDLEWARE_CLASSES = (
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
'reversion.middleware.RevisionMiddleware',
)
ROOT_URLCONF = 're2o.urls'

View file

@ -41,7 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<thead>
<tr>
<th>Date</th>
<th>Cableur</th>
<th>Effectué par</th>
<th>Commentaire</th>
</tr>
</thead>

View file

@ -42,6 +42,7 @@ from django.db.models import Q
from django.contrib import messages
from django.shortcuts import redirect
from django.urls import reverse
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from cotisations.models import Cotisation, Facture, Paiement, Vente
from machines.models import Domain, Interface, Machine
@ -280,6 +281,22 @@ class SortTable:
else:
return request
def re2o_paginator(request, query_set, pagination_number):
"""Paginator script for list display in re2o.
:request:
:query_set: Query_set to paginate
:pagination_number: Number of entries to display"""
paginator = Paginator(query_set, pagination_number)
page = request.GET.get('page')
try:
results = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
results = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
results = paginator.page(paginator.num_pages)
return results
def remove_user_room(room):
""" Déménage de force l'ancien locataire de la chambre """

View file

@ -31,7 +31,6 @@ from django.urls import reverse
from django.shortcuts import render, redirect
from django.template.context_processors import csrf
from django.contrib.auth.decorators import login_required, permission_required
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from reversion.models import Version
from django.contrib import messages
from preferences.models import Service
@ -40,7 +39,9 @@ from django.conf import settings
from contributors import contributeurs
import os
import time
from itertools import chain
import users, preferences, cotisations, topologie, machines
from .utils import re2o_paginator
def form(ctx, template, request):
"""Form générique, raccourci importé par les fonctions views du site"""
@ -88,7 +89,7 @@ HISTORY_BIND = {
'machines' : {
'machine' : machines.models.Machine,
'interface' : machines.models.Interface,
'alias' : machines.models.Domain,
'domain' : machines.models.Domain,
'machinetype' : machines.models.MachineType,
'iptype' : machines.models.IpType,
'extension' : machines.models.Extension,
@ -146,16 +147,10 @@ def history(request, application, object_name, object_id):
))
pagination_number = GeneralOption.get_cached_value('pagination_number')
reversions = Version.objects.get_for_object(instance)
paginator = Paginator(reversions, pagination_number)
page = request.GET.get('page')
try:
reversions = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
reversions = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of result
reversions = paginator.page(paginator.num_pages)
if hasattr(instance, 'linked_objects'):
for related_object in chain(instance.linked_objects()):
reversions = reversions | Version.objects.get_for_object(related_object)
reversions = re2o_paginator(request, reversions, pagination_number)
return render(
request,
're2o/history.html',

View file

@ -50,9 +50,9 @@ from .models import (
ConstructorSwitch,
AccessPoint
)
from re2o.mixins import FormRevMixin
class PortForm(ModelForm):
class PortForm(FormRevMixin, ModelForm):
"""Formulaire pour la création d'un port d'un switch
Relié directement au modèle port"""
class Meta:
@ -64,7 +64,7 @@ class PortForm(ModelForm):
super(PortForm, self).__init__(*args, prefix=prefix, **kwargs)
class EditPortForm(ModelForm):
class EditPortForm(FormRevMixin, ModelForm):
"""Form pour l'édition d'un port de switche : changement des reglages
radius ou vlan, ou attribution d'une chambre, autre port ou machine
@ -89,7 +89,7 @@ class EditPortForm(ModelForm):
))
class AddPortForm(ModelForm):
class AddPortForm(FormRevMixin, ModelForm):
"""Permet d'ajouter un port de switch. Voir EditPortForm pour plus
d'informations"""
class Meta(PortForm.Meta):
@ -108,7 +108,7 @@ class AddPortForm(ModelForm):
))
class StackForm(ModelForm):
class StackForm(FormRevMixin, ModelForm):
"""Permet d'edition d'une stack : stack_id, et switches membres
de la stack"""
class Meta:
@ -149,7 +149,7 @@ class NewSwitchForm(NewMachineForm):
fields = ['name', 'location', 'number', 'stack', 'stack_member_id']
class EditRoomForm(ModelForm):
class EditRoomForm(FormRevMixin, ModelForm):
"""Permet d'éediter le nom et commentaire d'une prise murale"""
class Meta:
model = Room
@ -166,7 +166,7 @@ class CreatePortsForm(forms.Form):
end = forms.IntegerField(label="Fin :", min_value=0)
class EditModelSwitchForm(ModelForm):
class EditModelSwitchForm(FormRevMixin, ModelForm):
"""Permet d'éediter un modèle de switch : nom et constructeur"""
class Meta:
model = ModelSwitch
@ -177,7 +177,7 @@ class EditModelSwitchForm(ModelForm):
super(EditModelSwitchForm, self).__init__(*args, prefix=prefix, **kwargs)
class EditConstructorSwitchForm(ModelForm):
class EditConstructorSwitchForm(FormRevMixin, ModelForm):
"""Permet d'éediter le nom d'un constructeur"""
class Meta:
model = ConstructorSwitch

View file

@ -0,0 +1,228 @@
# -*- coding: utf-8 -*-
import sys
import json
import six
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from topologie.management.modelviz import ModelGraph, generate_dot
from django_extensions.management.utils import signalcommand
try:
import pygraphviz
HAS_PYGRAPHVIZ = True
except ImportError:
HAS_PYGRAPHVIZ = False
try:
try:
import pydotplus as pydot
except ImportError:
import pydot
HAS_PYDOT = True
except ImportError:
HAS_PYDOT = False
class Command(BaseCommand):
help = "Creates a GraphViz dot file for the specified app names. You can pass multiple app names and they will all be combined into a single model. Output is usually directed to a dot file."
can_import_settings = True
def __init__(self, *args, **kwargs):
"""Allow defaults for arguments to be set in settings.GRAPH_MODELS.
Each argument in self.arguments is a dict where the key is the
space-separated args and the value is our kwarg dict.
The default from settings is keyed as the long arg name with '--'
removed and any '-' replaced by '_'.
"""
self.arguments = {
'--pygraphviz': {
'action': 'store_true', 'dest': 'pygraphviz',
'help': 'Use PyGraphViz to generate the image.'},
'--pydot': {'action': 'store_true', 'dest': 'pydot',
'help': 'Use PyDot(Plus) to generate the image.'},
'--disable-fields -d': {
'action': 'store_true', 'dest': 'disable_fields',
'help': 'Do not show the class member fields'},
'--group-models -g': {
'action': 'store_true', 'dest': 'group_models',
'help': 'Group models together respective to their '
'application'},
'--all-applications -a': {
'action': 'store_true', 'dest': 'all_applications',
'help': 'Automatically include all applications from '
'INSTALLED_APPS'},
'--output -o': {
'action': 'store', 'dest': 'outputfile',
'help': 'Render output file. Type of output dependend on file '
'extensions. Use png or jpg to render graph to image.'},
'--layout -l': {
'action': 'store', 'dest': 'layout', 'default': 'dot',
'help': 'Layout to be used by GraphViz for visualization. '
'Layouts: circo dot fdp neato nop nop1 nop2 twopi'},
'--verbose-names -n': {
'action': 'store_true', 'dest': 'verbose_names',
'help': 'Use verbose_name of models and fields'},
'--language -L': {
'action': 'store', 'dest': 'language',
'help': 'Specify language used for verbose_name localization'},
'--exclude-columns -x': {
'action': 'store', 'dest': 'exclude_columns',
'help': 'Exclude specific column(s) from the graph. '
'Can also load exclude list from file.'},
'--exclude-models -X': {
'action': 'store', 'dest': 'exclude_models',
'help': 'Exclude specific model(s) from the graph. Can also '
'load exclude list from file. Wildcards (*) are allowed.'},
'--include-models -I': {
'action': 'store', 'dest': 'include_models',
'help': 'Restrict the graph to specified models. Wildcards '
'(*) are allowed.'},
'--inheritance -e': {
'action': 'store_true', 'dest': 'inheritance', 'default': True,
'help': 'Include inheritance arrows (default)'},
'--no-inheritance -E': {
'action': 'store_false', 'dest': 'inheritance',
'help': 'Do not include inheritance arrows'},
'--hide-relations-from-fields -R': {
'action': 'store_false', 'dest': 'relations_as_fields',
'default': True,
'help': 'Do not show relations as fields in the graph.'},
'--disable-sort-fields -S': {
'action': 'store_false', 'dest': 'sort_fields',
'default': True, 'help': 'Do not sort fields'},
'--json': {'action': 'store_true', 'dest': 'json',
'help': 'Output graph data as JSON'}
}
defaults = getattr(settings, 'GRAPH_MODELS', None)
if defaults:
for argument in self.arguments:
arg_split = argument.split(' ')
setting_opt = arg_split[0].lstrip('-').replace('-', '_')
if setting_opt in defaults:
self.arguments[argument]['default'] = defaults[setting_opt]
super(Command, self).__init__(*args, **kwargs)
def add_arguments(self, parser):
"""Unpack self.arguments for parser.add_arguments."""
parser.add_argument('app_label', nargs='*')
for argument in self.arguments:
parser.add_argument(*argument.split(' '),
**self.arguments[argument])
@signalcommand
def handle(self, *args, **options):
args = options['app_label']
if len(args) < 1 and not options['all_applications']:
raise CommandError("need one or more arguments for appname")
use_pygraphviz = options.get('pygraphviz', False)
use_pydot = options.get('pydot', False)
use_json = options.get('json', False)
if use_json and (use_pydot or use_pygraphviz):
raise CommandError("Cannot specify --json with --pydot or --pygraphviz")
cli_options = ' '.join(sys.argv[2:])
graph_models = ModelGraph(args, cli_options=cli_options, **options)
graph_models.generate_graph_data()
graph_data = graph_models.get_graph_data(as_json=use_json)
if use_json:
self.render_output_json(graph_data, **options)
return
dotdata = generate_dot(graph_data)
if not six.PY3:
dotdata = dotdata.encode('utf-8')
if options['outputfile']:
if not use_pygraphviz and not use_pydot:
if HAS_PYGRAPHVIZ:
use_pygraphviz = True
elif HAS_PYDOT:
use_pydot = True
if use_pygraphviz:
self.render_output_pygraphviz(dotdata, **options)
elif use_pydot:
self.render_output_pydot(dotdata, **options)
else:
raise CommandError("Neither pygraphviz nor pydotplus could be found to generate the image")
else:
self.print_output(dotdata)
def print_output(self, dotdata):
if six.PY3 and isinstance(dotdata, six.binary_type):
dotdata = dotdata.decode()
print(dotdata)
def render_output_json(self, graph_data, **kwargs):
output_file = kwargs.get('outputfile')
if output_file:
with open(output_file, 'wt') as json_output_f:
json.dump(graph_data, json_output_f)
else:
print(json.dumps(graph_data))
def render_output_pygraphviz(self, dotdata, **kwargs):
"""Renders the image using pygraphviz"""
if not HAS_PYGRAPHVIZ:
raise CommandError("You need to install pygraphviz python module")
version = pygraphviz.__version__.rstrip("-svn")
try:
if tuple(int(v) for v in version.split('.')) < (0, 36):
# HACK around old/broken AGraph before version 0.36 (ubuntu ships with this old version)
import tempfile
tmpfile = tempfile.NamedTemporaryFile()
tmpfile.write(dotdata)
tmpfile.seek(0)
dotdata = tmpfile.name
except ValueError:
pass
graph = pygraphviz.AGraph(dotdata)
graph.layout(prog=kwargs['layout'])
graph.draw(kwargs['outputfile'])
def render_output_pydot(self, dotdata, **kwargs):
"""Renders the image using pydot"""
if not HAS_PYDOT:
raise CommandError("You need to install pydot python module")
graph = pydot.graph_from_dot_data(dotdata)
if not graph:
raise CommandError("pydot returned an error")
if isinstance(graph, (list, tuple)):
if len(graph) > 1:
sys.stderr.write("Found more then one graph, rendering only the first one.\n")
graph = graph[0]
output_file = kwargs['outputfile']
formats = ['bmp', 'canon', 'cmap', 'cmapx', 'cmapx_np', 'dot', 'dia', 'emf',
'em', 'fplus', 'eps', 'fig', 'gd', 'gd2', 'gif', 'gv', 'imap',
'imap_np', 'ismap', 'jpe', 'jpeg', 'jpg', 'metafile', 'pdf',
'pic', 'plain', 'plain-ext', 'png', 'pov', 'ps', 'ps2', 'svg',
'svgz', 'tif', 'tiff', 'tk', 'vml', 'vmlz', 'vrml', 'wbmp', 'xdot']
ext = output_file[output_file.rfind('.') + 1:]
format = ext if ext in formats else 'raw'
graph.write(output_file, format=format)

View file

@ -0,0 +1,405 @@
# -*- coding: utf-8 -*-
"""
modelviz.py - DOT file generator for Django Models
Based on:
Django model to DOT (Graphviz) converter
by Antonio Cavedoni <antonio@cavedoni.org>
Adapted to be used with django-extensions
"""
import datetime
import os
import re
import six
from django.apps import apps
from django.db.models.fields.related import (
ForeignKey, ManyToManyField, OneToOneField, RelatedField,
)
from django.contrib.contenttypes.fields import GenericRelation
from django.template import Context, Template, loader
from django.utils.encoding import force_bytes, force_str
from django.utils.safestring import mark_safe
from django.utils.translation import activate as activate_language
__version__ = "1.1"
__license__ = "Python"
__author__ = "Bas van Oostveen <v.oostveen@gmail.com>",
__contributors__ = [
"Antonio Cavedoni <http://cavedoni.com/>"
"Stefano J. Attardi <http://attardi.org/>",
"limodou <http://www.donews.net/limodou/>",
"Carlo C8E Miron",
"Andre Campos <cahenan@gmail.com>",
"Justin Findlay <jfindlay@gmail.com>",
"Alexander Houben <alexander@houben.ch>",
"Joern Hees <gitdev@joernhees.de>",
"Kevin Cherepski <cherepski@gmail.com>",
"Jose Tomas Tocino <theom3ga@gmail.com>",
"Adam Dobrawy <naczelnik@jawnosc.tk>",
"Mikkel Munch Mortensen <https://www.detfalskested.dk/>",
"Andrzej Bistram <andrzej.bistram@gmail.com>",
]
def parse_file_or_list(arg):
if not arg:
return []
if isinstance(arg, (list, tuple, set)):
return arg
if ',' not in arg and os.path.isfile(arg):
return [e.strip() for e in open(arg).readlines()]
return [e.strip() for e in arg.split(',')]
class ModelGraph(object):
def __init__(self, app_labels, **kwargs):
self.graphs = []
self.cli_options = kwargs.get('cli_options', None)
self.disable_fields = kwargs.get('disable_fields', False)
self.include_models = parse_file_or_list(
kwargs.get('include_models', "")
)
self.all_applications = kwargs.get('all_applications', False)
self.use_subgraph = kwargs.get('group_models', False)
self.verbose_names = kwargs.get('verbose_names', False)
self.inheritance = kwargs.get('inheritance', True)
self.relations_as_fields = kwargs.get("relations_as_fields", True)
self.sort_fields = kwargs.get("sort_fields", True)
self.language = kwargs.get('language', None)
if self.language is not None:
activate_language(self.language)
self.exclude_columns = parse_file_or_list(
kwargs.get('exclude_columns', "")
)
self.exclude_models = parse_file_or_list(
kwargs.get('exclude_models', "")
)
if self.all_applications:
self.app_labels = [app.label for app in apps.get_app_configs()]
else:
self.app_labels = app_labels
def generate_graph_data(self):
self.process_apps()
nodes = []
for graph in self.graphs:
nodes.extend([e['name'] for e in graph['models']])
for graph in self.graphs:
for model in graph['models']:
for relation in model['relations']:
if relation is not None:
if relation['target'] in nodes:
relation['needs_node'] = False
def get_graph_data(self, as_json=False):
now = datetime.datetime.now()
graph_data = {
'created_at': now.strftime("%Y-%m-%d %H:%M"),
'cli_options': self.cli_options,
'disable_fields': self.disable_fields,
'use_subgraph': self.use_subgraph,
}
if as_json:
graph_data['graphs'] = [context.flatten() for context in self.graphs]
else:
graph_data['graphs'] = self.graphs
return graph_data
def add_attributes(self, field, abstract_fields):
if self.verbose_names and field.verbose_name:
label = force_bytes(field.verbose_name)
if label.islower():
label = label.capitalize()
else:
label = field.name
t = type(field).__name__
if isinstance(field, (OneToOneField, ForeignKey)):
remote_field = field.remote_field if hasattr(field, 'remote_field') else field.rel # Remove me after Django 1.8 is unsupported
t += " ({0})".format(remote_field.field_name)
# TODO: ManyToManyField, GenericRelation
return {
'name': field.name,
'label': label,
'type': t,
'blank': field.blank,
'abstract': field in abstract_fields,
'relation': isinstance(field, RelatedField),
'primary_key': field.primary_key,
}
def add_relation(self, field, model, extras=""):
if self.verbose_names and field.verbose_name:
label = force_bytes(field.verbose_name)
if label.islower():
label = label.capitalize()
else:
label = field.name
# show related field name
if hasattr(field, 'related_query_name'):
related_query_name = field.related_query_name()
if self.verbose_names and related_query_name.islower():
related_query_name = related_query_name.replace('_', ' ').capitalize()
label = '{} ({})'.format(label, force_str(related_query_name))
# handle self-relationships and lazy-relationships
remote_field = field.remote_field if hasattr(field, 'remote_field') else field.rel # Remove me after Django 1.8 is unsupported
remote_field_model = remote_field.model if hasattr(remote_field, 'model') else remote_field.to # Remove me after Django 1.8 is unsupported
if isinstance(remote_field_model, six.string_types):
if remote_field_model == 'self':
target_model = field.model
else:
if '.' in remote_field_model:
app_label, model_name = remote_field_model.split('.', 1)
else:
app_label = field.model._meta.app_label
model_name = remote_field_model
target_model = apps.get_model(app_label, model_name)
else:
target_model = remote_field_model
_rel = self.get_relation_context(target_model, field, label, extras)
if _rel not in model['relations'] and self.use_model(_rel['target']):
return _rel
def get_abstract_models(self, appmodels):
abstract_models = []
for appmodel in appmodels:
abstract_models += [abstract_model for abstract_model in
appmodel.__bases__ if
hasattr(abstract_model, '_meta') and
abstract_model._meta.abstract]
abstract_models = list(set(abstract_models)) # remove duplicates
return abstract_models
def get_app_context(self, app):
return Context({
'name': '"%s"' % app.name,
'app_name': "%s" % app.name,
'cluster_app_name': "cluster_%s" % app.name.replace(".", "_"),
'models': []
})
def get_appmodel_attributes(self, appmodel):
if self.relations_as_fields:
attributes = [field for field in appmodel._meta.local_fields]
else:
# Find all the 'real' attributes. Relations are depicted as graph edges instead of attributes
attributes = [field for field in appmodel._meta.local_fields if not
isinstance(field, RelatedField)]
return attributes
def get_appmodel_abstracts(self, appmodel):
return [abstract_model.__name__ for abstract_model in
appmodel.__bases__ if
hasattr(abstract_model, '_meta') and
abstract_model._meta.abstract]
def get_appmodel_context(self, appmodel, appmodel_abstracts):
context = {
'app_name': appmodel.__module__.replace(".", "_"),
'name': appmodel.__name__,
'abstracts': appmodel_abstracts,
'fields': [],
'relations': []
}
if self.verbose_names and appmodel._meta.verbose_name:
context['label'] = force_bytes(appmodel._meta.verbose_name)
else:
context['label'] = context['name']
return context
def get_bases_abstract_fields(self, c):
_abstract_fields = []
for e in c.__bases__:
if hasattr(e, '_meta') and e._meta.abstract:
_abstract_fields.extend(e._meta.fields)
_abstract_fields.extend(self.get_bases_abstract_fields(e))
return _abstract_fields
def get_inheritance_context(self, appmodel, parent):
label = "multi-table"
if parent._meta.abstract:
label = "abstract"
if appmodel._meta.proxy:
label = "proxy"
label += r"\ninheritance"
return {
'target_app': parent.__module__.replace(".", "_"),
'target': parent.__name__,
'type': "inheritance",
'name': "inheritance",
'label': label,
'arrows': '[arrowhead=empty, arrowtail=none, dir=both]',
'needs_node': True,
}
def get_models(self, app):
appmodels = list(app.get_models())
return appmodels
def get_relation_context(self, target_model, field, label, extras):
return {
'target_app': target_model.__module__.replace('.', '_'),
'target': target_model.__name__,
'type': type(field).__name__,
'name': field.name,
'label': label,
'arrows': extras,
'needs_node': True
}
def process_attributes(self, field, model, pk, abstract_fields):
newmodel = model.copy()
if self.skip_field(field) or pk and field == pk:
return newmodel
newmodel['fields'].append(self.add_attributes(field, abstract_fields))
return newmodel
def process_apps(self):
for app_label in self.app_labels:
app = apps.get_app_config(app_label)
if not app:
continue
app_graph = self.get_app_context(app)
app_models = self.get_models(app)
abstract_models = self.get_abstract_models(app_models)
app_models = abstract_models + app_models
for appmodel in app_models:
if not self.use_model(appmodel._meta.object_name):
continue
appmodel_abstracts = self.get_appmodel_abstracts(appmodel)
abstract_fields = self.get_bases_abstract_fields(appmodel)
model = self.get_appmodel_context(appmodel, appmodel_abstracts)
attributes = self.get_appmodel_attributes(appmodel)
# find primary key and print it first, ignoring implicit id if other pk exists
pk = appmodel._meta.pk
if pk and not appmodel._meta.abstract and pk in attributes:
model['fields'].append(self.add_attributes(pk, abstract_fields))
for field in attributes:
model = self.process_attributes(field, model, pk, abstract_fields)
if self.sort_fields:
model = self.sort_model_fields(model)
for field in appmodel._meta.local_fields:
model = self.process_local_fields(field, model, abstract_fields)
for field in appmodel._meta.local_many_to_many:
model = self.process_local_many_to_many(field, model)
if self.inheritance:
# add inheritance arrows
for parent in appmodel.__bases__:
model = self.process_parent(parent, appmodel, model)
app_graph['models'].append(model)
if app_graph['models']:
self.graphs.append(app_graph)
def process_local_fields(self, field, model, abstract_fields):
newmodel = model.copy()
if (field.attname.endswith('_ptr_id') or # excluding field redundant with inheritance relation
field in abstract_fields or # excluding fields inherited from abstract classes. they too show as local_fields
self.skip_field(field)):
return newmodel
if isinstance(field, OneToOneField):
newmodel['relations'].append(self.add_relation(field, newmodel, '[arrowhead=none, arrowtail=none, dir=both]'))
elif isinstance(field, ForeignKey):
newmodel['relations'].append(self.add_relation(field, newmodel, '[arrowhead=none, arrowtail=dot, dir=both]'))
return newmodel
def process_local_many_to_many(self, field, model):
newmodel = model.copy()
if self.skip_field(field):
return newmodel
if isinstance(field, ManyToManyField):
remote_field = field.remote_field if hasattr(field, 'remote_field') else field.rel # Remove me after Django 1.8 is unsupported
if hasattr(remote_field.through, '_meta') and remote_field.through._meta.auto_created:
newmodel['relations'].append(self.add_relation(field, newmodel, '[arrowhead=dot arrowtail=dot, dir=both]'))
elif isinstance(field, GenericRelation):
newmodel['relations'].append(self.add_relation(field, newmodel, mark_safe('[style="dotted", arrowhead=normal, arrowtail=normal, dir=both]')))
return newmodel
def process_parent(self, parent, appmodel, model):
newmodel = model.copy()
if hasattr(parent, "_meta"): # parent is a model
_rel = self.get_inheritance_context(appmodel, parent)
# TODO: seems as if abstract models aren't part of models.getModels, which is why they are printed by this without any attributes.
if _rel not in newmodel['relations'] and self.use_model(_rel['target']):
newmodel['relations'].append(_rel)
return newmodel
def sort_model_fields(self, model):
newmodel = model.copy()
newmodel['fields'] = sorted(newmodel['fields'], key=lambda field: (not field['primary_key'], not field['relation'], field['label']))
return newmodel
def use_model(self, model_name):
"""
Decide whether to use a model, based on the model name and the lists of
models to exclude and include.
"""
# Check against exclude list.
if self.exclude_models:
for model_pattern in self.exclude_models:
model_pattern = '^%s$' % model_pattern.replace('*', '.*')
if re.search(model_pattern, model_name):
return False
# Check against exclude list.
elif self.include_models:
for model_pattern in self.include_models:
model_pattern = '^%s$' % model_pattern.replace('*', '.*')
if re.search(model_pattern, model_name):
return True
# Return `True` if `include_models` is falsey, otherwise return `False`.
return not self.include_models
def skip_field(self, field):
if self.exclude_columns:
if self.verbose_names and field.verbose_name:
if field.verbose_name in self.exclude_columns:
return True
if field.name in self.exclude_columns:
return True
return False
def generate_dot(graph_data, template='django_extensions/graph_models/digraph.dot'):
t = loader.get_template(template)
if not isinstance(t, Template) and not (hasattr(t, 'template') and isinstance(t.template, Template)):
raise Exception("Default Django template loader isn't used. "
"This can lead to the incorrect template rendering. "
"Please, check the settings.")
c = Context(graph_data).flatten()
dot = t.render(c)
return dot
def generate_graph_data(*args, **kwargs):
generator = ModelGraph(*args, **kwargs)
generator.generate_graph_data()
return generator.get_graph_data()
def use_model(model, include_models, exclude_models):
generator = ModelGraph([], include_models=include_models, exclude_models=exclude_models)
return generator.use_model(model)

View file

@ -48,9 +48,9 @@ from django.db import transaction
from reversion import revisions as reversion
from machines.models import Machine, Interface, regen
from re2o.mixins import AclMixin
from re2o.mixins import AclMixin, RevMixin
class Stack(AclMixin, models.Model):
class Stack(AclMixin, RevMixin, models.Model):
"""Un objet stack. Regrouppe des switchs en foreign key
,contient une id de stack, un switch id min et max dans
le stack"""
@ -102,6 +102,9 @@ class AccessPoint(AclMixin, Machine):
("view_accesspoint", "Peut voir une borne"),
)
def __str__(self):
return str(self.interface_set.first())
class Switch(AclMixin, Machine):
""" Definition d'un switch. Contient un nombre de ports (number),
@ -187,7 +190,7 @@ class Switch(AclMixin, Machine):
return str(self.interface_set.first())
class ModelSwitch(AclMixin, models.Model):
class ModelSwitch(AclMixin, RevMixin, models.Model):
"""Un modèle (au sens constructeur) de switch"""
PRETTY_NAME = "Modèle de switch"
reference = models.CharField(max_length=255)
@ -205,7 +208,7 @@ class ModelSwitch(AclMixin, models.Model):
return str(self.constructor) + ' ' + self.reference
class ConstructorSwitch(AclMixin, models.Model):
class ConstructorSwitch(AclMixin, RevMixin, models.Model):
"""Un constructeur de switch"""
PRETTY_NAME = "Constructeur de switch"
name = models.CharField(max_length=255)
@ -219,7 +222,7 @@ class ConstructorSwitch(AclMixin, models.Model):
return self.name
class Port(AclMixin, models.Model):
class Port(AclMixin, RevMixin, models.Model):
""" Definition d'un port. Relié à un switch(foreign_key),
un port peut etre relié de manière exclusive à :
- une chambre (room)
@ -335,7 +338,7 @@ class Port(AclMixin, models.Model):
return str(self.switch) + " - " + str(self.port)
class Room(AclMixin, models.Model):
class Room(AclMixin, RevMixin, models.Model):
"""Une chambre/local contenant une prise murale"""
PRETTY_NAME = "Chambre/ Prise murale"

View file

@ -24,6 +24,64 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load acl %}
<div class="table-responsive" style="font-size: 12px">
<table class="table table-bordered text-center text-nowrap">
<thead>
<tr>
{% for port in port_list|slice:"::2" %}
<td class="bg-primary text-white">{{ port.port }}</td>
{% endfor %}
</tr>
<tr>
{% for port in port_list|slice:"::2" %}
<td class="p-3 mb-2 bg-success text-dark">
{% if port.room %}
{{ port.room }}
{% elif port.machine_interface %}
<a href="{% url 'users:profil' userid=port.machine_interface.machine.user.id %}">{{ port.machine_interface }}</a>
{% elif port.related%}
<a href="{% url 'topologie:index-port' switchid=port.related.switch.id %}">{{ port.related }}</a>
{% else %}
Vide
{% endif %}
</td>
{% endfor %}
</tr>
<tr>
{% for port in port_list|slice:"1::2" %}
<td class="bg-primary text-white">{{ port.port }}</td>
{% endfor %}
</tr>
<tr>
{% for port in port_list|slice:"1::2" %}
<td class="p-3 mb-2 bg-success text-dark">
{% if port.room %}
{{ port.room }}
{% elif port.machine_interface %}
<a href="{% url 'users:profil' userid=port.machine_interface.machine.user.id %}">{{ port.machine_interface }}</a>
{% elif port.related%}
<a href="{% url 'topologie:index-port' switchid=port.related.switch.id %}">{{ port.related }}</a>
{% else %}
Vide
{% endif %}
</td>
{% endfor %}
</tr>
</table>
</div>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
@ -76,3 +134,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</tr>
{% endfor %}
</table>
</div>

View file

@ -35,7 +35,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:new-port' id_switch %}"><i class="fa fa-plus"></i> Ajouter un port</a>
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:create-ports' id_switch %}"><i class="fa fa-plus"></i> Ajouter des ports</a>
{% acl_end %}
{% include "topologie/aff_port.html" with port_list=port_list %}
<hr>
{% include "topologie/aff_port.html" with port_list=port_list %}
<br />
<br />
<br />

View file

@ -43,9 +43,6 @@ from django.db import IntegrityError
from django.db import transaction
from django.db.models import ProtectedError, Prefetch
from django.core.exceptions import ValidationError
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from reversion import revisions as reversion
from reversion.models import Version
from topologie.models import (
Switch,
@ -68,7 +65,7 @@ from topologie.forms import (
EditAccessPointForm
)
from users.views import form
from re2o.utils import SortTable
from re2o.utils import re2o_paginator, SortTable
from re2o.acl import (
can_create,
can_edit,
@ -105,16 +102,7 @@ def index(request):
SortTable.TOPOLOGIE_INDEX
)
pagination_number = GeneralOption.get_cached_value('pagination_number')
paginator = Paginator(switch_list, pagination_number)
page = request.GET.get('page')
try:
switch_list = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
switch_list = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
switch_list = paginator.page(paginator.num_pages)
switch_list = re2o_paginator(request, switch_list, pagination_number)
return render(request, 'topologie/index.html', {
'switch_list': switch_list
})
@ -160,16 +148,7 @@ def index_room(request):
SortTable.TOPOLOGIE_INDEX_ROOM
)
pagination_number = GeneralOption.get_cached_value('pagination_number')
paginator = Paginator(room_list, pagination_number)
page = request.GET.get('page')
try:
room_list = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
room_list = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
room_list = paginator.page(paginator.num_pages)
room_list = re2o_paginator(request, room_list, pagination_number)
return render(request, 'topologie/index_room.html', {
'room_list': room_list
})
@ -191,16 +170,7 @@ def index_ap(request):
SortTable.TOPOLOGIE_INDEX_BORNE
)
pagination_number = GeneralOption.get_cached_value('pagination_number')
paginator = Paginator(ap_list, pagination_number)
page = request.GET.get('page')
try:
ap_list = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
ap_list = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
ap_list = paginator.page(paginator.num_pages)
ap_list = re2o_paginator(request, ap_list, pagination_number)
return render(request, 'topologie/index_ap.html', {
'ap_list': ap_list
})
@ -262,10 +232,7 @@ def new_port(request, switchid):
port = port.save(commit=False)
port.switch = switch
try:
with transaction.atomic(), reversion.create_revision():
port.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Port ajouté")
except IntegrityError:
messages.error(request, "Ce port existe déjà")
@ -284,12 +251,8 @@ def edit_port(request, port_object, portid):
port = EditPortForm(request.POST or None, instance=port_object)
if port.is_valid():
with transaction.atomic(), reversion.create_revision():
if port.changed_data:
port.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in port.changed_data
))
messages.success(request, "Le port a bien été modifié")
return redirect(reverse(
'topologie:index-port',
@ -304,10 +267,7 @@ def del_port(request, port, portid):
""" Supprime le port"""
if request.method == "POST":
try:
with transaction.atomic(), reversion.create_revision():
port.delete()
reversion.set_user(request.user)
reversion.set_comment("Destruction")
messages.success(request, "Le port a été détruit")
except ProtectedError:
messages.error(request, "Le port %s est affecté à un autre objet,\
@ -325,10 +285,7 @@ def new_stack(request):
"""Ajoute un nouveau stack : stackid_min, max, et nombre de switches"""
stack = StackForm(request.POST or None)
if stack.is_valid():
with transaction.atomic(), reversion.create_revision():
stack.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Stack crée")
return form({'topoform': stack, 'action_name' : 'Créer'}, 'topologie/topo.html', request)
@ -337,17 +294,10 @@ def new_stack(request):
@can_edit(Stack)
def edit_stack(request, stack, stackid):
"""Edition d'un stack (nombre de switches, nom...)"""
stack = StackForm(request.POST or None, instance=stack)
if stack.is_valid():
with transaction.atomic(), reversion.create_revision():
if stack.changed_data:
stack.save()
reversion.set_user(request.user)
reversion.set_comment(
"Champs modifié(s) : %s" % ', '.join(
field for field in stack.changed_data
)
)
return redirect(reverse('topologie:index-stack'))
return form({'topoform': stack, 'action_name' : 'Editer'}, 'topologie/topo.html', request)
@ -358,10 +308,7 @@ def del_stack(request, stack, stackid):
"""Supprime un stack"""
if request.method == "POST":
try:
with transaction.atomic(), reversion.create_revision():
stack.delete()
reversion.set_user(request.user)
reversion.set_comment("Destruction")
messages.success(request, "La stack a eté détruite")
except ProtectedError:
messages.error(request, "La stack %s est affectée à un autre\
@ -412,20 +359,11 @@ def new_switch(request):
domain.instance.interface_parent = new_interface_instance
if domain.is_valid():
new_domain_instance = domain.save(commit=False)
with transaction.atomic(), reversion.create_revision():
new_switch.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
new_interface_instance.machine = new_switch
with transaction.atomic(), reversion.create_revision():
new_interface_instance.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
new_domain_instance.interface_parent = new_interface_instance
with transaction.atomic(), reversion.create_revision():
new_domain_instance.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Le switch a été créé")
return redirect(reverse('topologie:index'))
i_mbf_param = generate_ipv4_mbf_param(interface, False)
@ -468,7 +406,6 @@ def create_ports(request, switchid):
messages.success(request, "Ports créés.")
except ValidationError as e:
messages.error(request, ''.join(e))
return redirect(reverse(
'topologie:index-port',
kwargs={'switchid':switchid}
@ -500,26 +437,12 @@ def edit_switch(request, switch, switchid):
new_switch = switch_form.save(commit=False)
new_interface_instance = interface_form.save(commit=False)
new_domain = domain_form.save(commit=False)
with transaction.atomic(), reversion.create_revision():
if switch_form.changed_data:
new_switch.save()
reversion.set_user(request.user)
reversion.set_comment(
"Champs modifié(s) : %s" % ', '.join(
field for field in switch_form.changed_data
)
)
with transaction.atomic(), reversion.create_revision():
if interface_form.changed_data:
new_interface_instance.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in interface_form.changed_data)
)
with transaction.atomic(), reversion.create_revision():
if domain_form.changed_data:
new_domain.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in domain_form.changed_data)
)
messages.success(request, "Le switch a bien été modifié")
return redirect(reverse('topologie:index'))
i_mbf_param = generate_ipv4_mbf_param(interface_form, False )
@ -562,20 +485,11 @@ def new_ap(request):
domain.instance.interface_parent = new_interface
if domain.is_valid():
new_domain_instance = domain.save(commit=False)
with transaction.atomic(), reversion.create_revision():
new_ap.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
new_interface.machine = new_ap
with transaction.atomic(), reversion.create_revision():
new_interface.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
new_domain_instance.interface_parent = new_interface
with transaction.atomic(), reversion.create_revision():
new_domain_instance.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "La borne a été créé")
return redirect(reverse('topologie:index-ap'))
i_mbf_param = generate_ipv4_mbf_param(interface, False)
@ -616,26 +530,12 @@ def edit_ap(request, ap, accesspointid):
new_ap = ap_form.save(commit=False)
new_interface = interface_form.save(commit=False)
new_domain = domain_form.save(commit=False)
with transaction.atomic(), reversion.create_revision():
if ap_form.changed_data:
new_ap.save()
reversion.set_user(request.user)
reversion.set_comment(
"Champs modifié(s) : %s" % ', '.join(
field for field in ap_form.changed_data)
)
with transaction.atomic(), reversion.create_revision():
if interface_form.changed_data:
new_interface.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in interface_form.changed_data)
)
reversion.set_comment("Création")
with transaction.atomic(), reversion.create_revision():
if domain_form.changed_data:
new_domain.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in domain_form.changed_data)
)
messages.success(request, "La borne a été modifiée")
return redirect(reverse('topologie:index-ap'))
i_mbf_param = generate_ipv4_mbf_param(interface_form, False )
@ -654,10 +554,7 @@ def new_room(request):
"""Nouvelle chambre """
room = EditRoomForm(request.POST or None)
if room.is_valid():
with transaction.atomic(), reversion.create_revision():
room.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "La chambre a été créé")
return redirect(reverse('topologie:index-room'))
return form({'topoform': room, 'action_name' : 'Ajouter'}, 'topologie/topo.html', request)
@ -667,15 +564,10 @@ def new_room(request):
@can_edit(Room)
def edit_room(request, room, roomid):
""" Edition numero et details de la chambre"""
room = EditRoomForm(request.POST or None, instance=room)
if room.is_valid():
with transaction.atomic(), reversion.create_revision():
if room.changed_data:
room.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in room.changed_data)
)
messages.success(request, "La chambre a bien été modifiée")
return redirect(reverse('topologie:index-room'))
return form({'topoform': room, 'action_name' : 'Editer'}, 'topologie/topo.html', request)
@ -687,10 +579,7 @@ def del_room(request, room, roomid):
""" Suppression d'un chambre"""
if request.method == "POST":
try:
with transaction.atomic(), reversion.create_revision():
room.delete()
reversion.set_user(request.user)
reversion.set_comment("Destruction")
messages.success(request, "La chambre/prise a été détruite")
except ProtectedError:
messages.error(request, "La chambre %s est affectée à un autre objet,\
@ -708,10 +597,7 @@ def new_model_switch(request):
"""Nouveau modèle de switch"""
model_switch = EditModelSwitchForm(request.POST or None)
if model_switch.is_valid():
with transaction.atomic(), reversion.create_revision():
model_switch.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Le modèle a été créé")
return redirect(reverse('topologie:index-model-switch'))
return form({'topoform': model_switch, 'action_name' : 'Ajouter'}, 'topologie/topo.html', request)
@ -724,12 +610,8 @@ def edit_model_switch(request, model_switch, modelswitchid):
model_switch = EditModelSwitchForm(request.POST or None, instance=model_switch)
if model_switch.is_valid():
with transaction.atomic(), reversion.create_revision():
if model_switch.changed_data:
model_switch.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in model_switch.changed_data)
)
messages.success(request, "Le modèle a bien été modifié")
return redirect(reverse('topologie:index-model-switch'))
return form({'topoform': model_switch, 'action_name' : 'Editer'}, 'topologie/topo.html', request)
@ -741,10 +623,7 @@ def del_model_switch(request, model_switch, modelswitchid):
""" Suppression d'un modèle de switch"""
if request.method == "POST":
try:
with transaction.atomic(), reversion.create_revision():
model_switch.delete()
reversion.set_user(request.user)
reversion.set_comment("Destruction")
messages.success(request, "Le modèle a été détruit")
except ProtectedError:
messages.error(request, "Le modèle %s est affectée à un autre objet,\
@ -762,10 +641,7 @@ def new_constructor_switch(request):
"""Nouveau constructeur de switch"""
constructor_switch = EditConstructorSwitchForm(request.POST or None)
if constructor_switch.is_valid():
with transaction.atomic(), reversion.create_revision():
constructor_switch.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Le constructeur a été créé")
return redirect(reverse('topologie:index-model-switch'))
return form({'topoform': constructor_switch, 'action_name' : 'Ajouter'}, 'topologie/topo.html', request)
@ -778,12 +654,8 @@ def edit_constructor_switch(request, constructor_switch, constructorswitchid):
constructor_switch = EditConstructorSwitchForm(request.POST or None, instance=constructor_switch)
if constructor_switch.is_valid():
with transaction.atomic(), reversion.create_revision():
if constructor_switch.changed_data:
constructor_switch.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in constructor_switch.changed_data)
)
messages.success(request, "Le modèle a bien été modifié")
return redirect(reverse('topologie:index-model-switch'))
return form({'topoform': constructor_switch, 'action_name' : 'Editer'}, 'topologie/topo.html', request)
@ -795,10 +667,7 @@ def del_constructor_switch(request, constructor_switch, constructorswitchid):
""" Suppression d'un constructeur de switch"""
if request.method == "POST":
try:
with transaction.atomic(), reversion.create_revision():
constructor_switch.delete()
reversion.set_user(request.user)
reversion.set_comment("Destruction")
messages.success(request, "Le constructeur a été détruit")
except ProtectedError:
messages.error(request, "Le constructeur %s est affecté à un autre objet,\

View file

@ -32,11 +32,28 @@ 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, 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
from .models import (
User,
ServiceUser,
School,
ListRight,
ListShell,
Adherent,
Club,
Ban,
Whitelist,
Request,
LdapUser,
LdapServiceUser,
LdapServiceUserGroup,
LdapUserGroup
)
from .forms import (
UserChangeForm,
UserCreationForm,
ServiceUserChangeForm,
ServiceUserCreationForm
)
class UserAdmin(admin.ModelAdmin):
@ -195,6 +212,8 @@ class ServiceUserAdmin(VersionAdmin, BaseUserAdmin):
admin.site.register(User, UserAdmin)
admin.site.register(Adherent, UserAdmin)
admin.site.register(Club, UserAdmin)
admin.site.register(ServiceUser, ServiceUserAdmin)
admin.site.register(LdapUser, LdapUserAdmin)
admin.site.register(LdapUserGroup, LdapUserGroupAdmin)

View file

@ -53,13 +53,11 @@ from .models import (
Club
)
from re2o.utils import remove_user_room
from re2o.mixins import FormRevMixin
from re2o.field_permissions import FieldPermissionFormMixin
NOW = timezone.now()
class PassForm(FieldPermissionFormMixin, forms.ModelForm):
class PassForm(FormRevMixin, FieldPermissionFormMixin, forms.ModelForm):
"""Formulaire de changement de mot de passe. Verifie que les 2
nouveaux mots de passe renseignés sont identiques et respectent
une norme"""
@ -107,7 +105,7 @@ class PassForm(FieldPermissionFormMixin, forms.ModelForm):
user.save()
class UserCreationForm(forms.ModelForm):
class UserCreationForm(FormRevMixin, forms.ModelForm):
"""A form for creating new users. Includes all the required
fields, plus a repeated password.
@ -154,7 +152,7 @@ class UserCreationForm(forms.ModelForm):
return user
class ServiceUserCreationForm(forms.ModelForm):
class ServiceUserCreationForm(FormRevMixin, forms.ModelForm):
"""A form for creating new users. Includes all the required
fields, plus a repeated password.
@ -202,7 +200,7 @@ class ServiceUserCreationForm(forms.ModelForm):
return user
class UserChangeForm(forms.ModelForm):
class UserChangeForm(FormRevMixin, 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.
@ -238,7 +236,7 @@ class UserChangeForm(forms.ModelForm):
return user
class ServiceUserChangeForm(forms.ModelForm):
class ServiceUserChangeForm(FormRevMixin, 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.
@ -281,12 +279,12 @@ class MassArchiveForm(forms.Form):
cleaned_data = super(MassArchiveForm, self).clean()
date = cleaned_data.get("date")
if date:
if date > NOW:
if date > timezone.now():
raise forms.ValidationError("Impossible d'archiver des\
utilisateurs dont la fin d'accès se situe dans le futur !")
class AdherentForm(FieldPermissionFormMixin, ModelForm):
class AdherentForm(FormRevMixin, FieldPermissionFormMixin, 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"""
@ -339,7 +337,7 @@ class AdherentForm(FieldPermissionFormMixin, ModelForm):
return
class ClubForm(FieldPermissionFormMixin, ModelForm):
class ClubForm(FormRevMixin, FieldPermissionFormMixin, 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"""
@ -379,7 +377,7 @@ class ClubForm(FieldPermissionFormMixin, ModelForm):
return telephone
class ClubAdminandMembersForm(ModelForm):
class ClubAdminandMembersForm(FormRevMixin, ModelForm):
"""Permet d'éditer la liste des membres et des administrateurs
d'un club"""
class Meta:
@ -391,7 +389,7 @@ class ClubAdminandMembersForm(ModelForm):
super(ClubAdminandMembersForm, self).__init__(*args, prefix=prefix, **kwargs)
class PasswordForm(ModelForm):
class PasswordForm(FormRevMixin, ModelForm):
""" Formulaire de changement brut de mot de passe.
Ne pas utiliser sans traitement"""
class Meta:
@ -403,7 +401,7 @@ class PasswordForm(ModelForm):
super(PasswordForm, self).__init__(*args, prefix=prefix, **kwargs)
class ServiceUserForm(ModelForm):
class ServiceUserForm(FormRevMixin, ModelForm):
""" Modification d'un service user"""
password = forms.CharField(
label=u'Nouveau mot de passe',
@ -429,7 +427,7 @@ class EditServiceUserForm(ServiceUserForm):
fields = ['access_group', 'comment']
class StateForm(ModelForm):
class StateForm(FormRevMixin, ModelForm):
""" Changement de l'état d'un user"""
class Meta:
model = User
@ -440,7 +438,7 @@ class StateForm(ModelForm):
super(StateForm, self).__init__(*args, prefix=prefix, **kwargs)
class GroupForm(ModelForm):
class GroupForm(FormRevMixin, ModelForm):
""" Gestion des groupes d'un user"""
groups = forms.ModelMultipleChoiceField(
Group.objects.all(),
@ -457,7 +455,7 @@ class GroupForm(ModelForm):
super(GroupForm, self).__init__(*args, prefix=prefix, **kwargs)
class SchoolForm(ModelForm):
class SchoolForm(FormRevMixin, ModelForm):
"""Edition, creation d'un école"""
class Meta:
model = School
@ -469,7 +467,7 @@ class SchoolForm(ModelForm):
self.fields['name'].label = 'Établissement'
class ShellForm(ModelForm):
class ShellForm(FormRevMixin, ModelForm):
"""Edition, creation d'un école"""
class Meta:
model = ListShell
@ -481,7 +479,7 @@ class ShellForm(ModelForm):
self.fields['shell'].label = 'Nom du shell'
class ListRightForm(ModelForm):
class ListRightForm(FormRevMixin, ModelForm):
"""Edition, d'un groupe , équivalent à un droit
Ne peremet pas d'editer le gid, car il sert de primary key"""
permissions = forms.ModelMultipleChoiceField(
@ -545,7 +543,7 @@ class DelSchoolForm(Form):
self.fields['schools'].queryset = School.objects.all()
class BanForm(ModelForm):
class BanForm(FormRevMixin, ModelForm):
"""Creation, edition d'un objet bannissement"""
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
@ -557,7 +555,7 @@ class BanForm(ModelForm):
exclude = ['user']
class WhitelistForm(ModelForm):
class WhitelistForm(FormRevMixin, ModelForm):
"""Creation, edition d'un objet whitelist"""
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)

View file

@ -0,0 +1,47 @@
# ⁻*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2018 Lev-Arcady Sellem
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import os, pwd
from django.core.management.base import BaseCommand, CommandError
from users.forms import PassForm
from re2o.script_utils import get_user, get_system_user, form_cli
class Command(BaseCommand):
help = "Changer le mot de passe d'un utilisateur"
def add_arguments(self, parser):
parser.add_argument('target_username', nargs='?')
def handle(self, *args, **kwargs):
current_username = get_system_user()
current_user = get_user(current_username)
target_username = kwargs["target_username"] or current_username
target_user = get_user(target_username)
ok, msg = target_user.can_change_password(current_user)
if not ok:
raise CommandError(msg)
self.stdout.write("Changement du mot de passe de %s" % target_user.pseudo)
form_cli(PassForm,current_user,"Changement du mot de passe",instance=target_user)

View file

@ -26,6 +26,7 @@ from django.db import transaction
from reversion import revisions as reversion
from users.models import User, ListShell
from re2o.script_utils import get_user, get_system_user
class Command(BaseCommand):
help = 'Change the default shell of a user'
@ -35,14 +36,7 @@ class Command(BaseCommand):
def handle(self, *args, **options):
def get_user(user_pseudo):
"""Return the user queried by pseudo, and exit the script if not found."""
user = User.objects.filter(pseudo=user_pseudo)
if not user:
raise CommandError("Utilisateur invalide")
return user[0]
current_username = pwd.getpwuid(int(os.getenv("SUDO_UID") or os.getuid())).pw_name
current_username = get_system_user()
current_user = get_user(current_username)
target_username = options["target_username"] or current_username

View file

@ -76,7 +76,7 @@ import ldapdb.models.fields
from re2o.settings import RIGHTS_LINK, LDAP, GID_RANGES, UID_RANGES
from re2o.login import hashNT
from re2o.field_permissions import FieldPermissionModelMixin
from re2o.mixins import AclMixin
from re2o.mixins import AclMixin, RevMixin
from cotisations.models import Cotisation, Facture, Paiement, Vente
from machines.models import Domain, Interface, Machine, regen
@ -171,7 +171,7 @@ class UserManager(BaseUserManager):
"""
return self._create_user(pseudo, surname, email, password, True)
class User(FieldPermissionModelMixin, AbstractBaseUser, PermissionsMixin, AclMixin):
class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMixin, AclMixin):
""" Definition de l'utilisateur de base.
Champs principaux : name, surnname, pseudo, email, room, password
Herite du django BaseUser et du système d'auth django"""
@ -892,8 +892,8 @@ def user_post_save(sender, **kwargs):
Synchronise le ldap"""
is_created = kwargs['created']
user = kwargs['instance']
if is_created:
user.notif_inscription()
#if is_created:
#user.notif_inscription()
user.ldap_sync(base=True, access_refresh=True, mac_refresh=False, group_refresh=True)
regen('mailing')
@ -907,7 +907,7 @@ def user_post_delete(sender, **kwargs):
user.ldap_del()
regen('mailing')
class ServiceUser(AclMixin, AbstractBaseUser):
class ServiceUser(RevMixin, AclMixin, AbstractBaseUser):
""" Classe des users daemons, règle leurs accès au ldap"""
readonly = 'readonly'
ACCESS = (
@ -991,7 +991,7 @@ def service_user_post_delete(sender, **kwargs):
service_user.ldap_del()
class School(AclMixin, models.Model):
class School(RevMixin, AclMixin, models.Model):
""" Etablissement d'enseignement"""
PRETTY_NAME = "Établissements enregistrés"
@ -1006,7 +1006,7 @@ class School(AclMixin, models.Model):
return self.name
class ListRight(AclMixin, Group):
class ListRight(RevMixin, AclMixin, Group):
""" 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
@ -1073,7 +1073,7 @@ def listright_post_delete(sender, **kwargs):
right.ldap_del()
class ListShell(AclMixin, models.Model):
class ListShell(RevMixin, AclMixin, models.Model):
"""Un shell possible. Pas de check si ce shell existe, les
admin sont des grands"""
PRETTY_NAME = "Liste des shells disponibles"
@ -1093,7 +1093,7 @@ class ListShell(AclMixin, models.Model):
return self.shell
class Ban(AclMixin, models.Model):
class Ban(RevMixin, AclMixin, models.Model):
""" Bannissement. Actuellement a un effet tout ou rien.
Gagnerait à être granulaire"""
PRETTY_NAME = "Liste des bannissements"
@ -1140,9 +1140,6 @@ class Ban(AclMixin, models.Model):
"""Ce ban est-il actif?"""
return self.date_end > timezone.now()
def get_instance(banid, *args, **kwargs):
return Ban.objects.get(pk=banid)
def can_view(self, user_request, *args, **kwargs):
"""Check if an user can view a Ban object.
@ -1189,7 +1186,7 @@ def ban_post_delete(sender, **kwargs):
regen('mac_ip_list')
class Whitelist(AclMixin, models.Model):
class Whitelist(RevMixin, AclMixin, 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"""

View file

@ -39,7 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% can_edit shell %}
{% include 'buttons/edit.html' with href='users:edit-shell' id=shell.id %}
{% acl_end %}
{% include 'buttons/history.html' with href='users:history' name='shell' id=shell.id %}
{% include 'buttons/history.html' with href='users:history' name='listshell' id=shell.id %}
</td>
</tr>
{% endfor %}

View file

@ -37,7 +37,6 @@ from __future__ import unicode_literals
from django.urls import reverse
from django.shortcuts import get_object_or_404, render, redirect
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required
from django.db.models import ProtectedError, Q
@ -95,6 +94,7 @@ from re2o.views import form
from re2o.utils import (
all_has_access,
SortTable,
re2o_paginator
)
from re2o.acl import (
can_create,
@ -115,10 +115,7 @@ def new_user(request):
GTU = GeneralOption.get_cached_value('GTU')
if user.is_valid():
user = user.save(commit=False)
with transaction.atomic(), reversion.create_revision():
user.save()
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)
@ -137,10 +134,7 @@ def new_club(request):
club = ClubForm(request.POST or None, user=request.user)
if club.is_valid():
club = club.save(commit=False)
with transaction.atomic(), reversion.create_revision():
club.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
club.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é" % club.pseudo)
@ -158,12 +152,8 @@ def edit_club_admin_members(request, club_instance, clubid):
membres d'un club"""
club = ClubAdminandMembersForm(request.POST or None, instance=club_instance)
if club.is_valid():
with transaction.atomic(), reversion.create_revision():
if club.changed_data:
club.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in club.changed_data
))
messages.success(request, "Le club a bien été modifié")
return redirect(reverse(
'users:profil',
@ -191,12 +181,8 @@ def edit_info(request, user, userid):
user=request.user
)
if user.is_valid():
with transaction.atomic(), reversion.create_revision():
if user.changed_data:
user.save()
reversion.set_user(request.user)
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(reverse(
'users:profil',
@ -212,17 +198,13 @@ def state(request, user, userid):
need droit bureau """
state = StateForm(request.POST or None, instance=user)
if state.is_valid():
with transaction.atomic(), reversion.create_revision():
if state.cleaned_data['state'] == User.STATE_ARCHIVE:
user.archive()
elif state.cleaned_data['state'] == User.STATE_ACTIVE:
user.unarchive()
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
))
if user.changed_data:
user.save()
messages.success(request, "Etat changé avec succès")
return redirect(reverse(
@ -237,11 +219,7 @@ def state(request, user, userid):
def groups(request, user, userid):
group = GroupForm(request.POST or None, instance=user)
if group.is_valid():
with transaction.atomic(), reversion.create_revision():
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in group.changed_data
))
if group.changed_data:
group.save()
messages.success(request, "Groupes changés avec succès")
return redirect(reverse(
@ -259,10 +237,8 @@ def password(request, user, userid):
pour tous si droit bureau """
u_form = PassForm(request.POST or None, instance=user, user=request.user)
if u_form.is_valid():
with transaction.atomic(), reversion.create_revision():
if u_form.changed_data:
u_form.save()
reversion.set_user(request.user)
reversion.set_comment("Changement du mot de passe")
messages.success(request, "Le mot de passe a changé")
return redirect(reverse(
'users:profil',
@ -274,11 +250,8 @@ def password(request, user, userid):
@login_required
@can_edit(User, 'groups')
def del_group(request, user, userid, listrightid):
with transaction.atomic(), reversion.create_revision():
user.groups.remove(ListRight.objects.get(id=listrightid))
user.save()
reversion.set_user(request.user)
reversion.set_comment("Suppression de droit")
messages.success(request, "Droit supprimé à %s" % user)
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
@ -290,11 +263,8 @@ def new_serviceuser(request):
user = ServiceUserForm(request.POST or None)
if user.is_valid():
user_object = user.save(commit=False)
with transaction.atomic(), reversion.create_revision():
user_object.set_password(user.cleaned_data['password'])
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
@ -307,17 +277,13 @@ def new_serviceuser(request):
@can_edit(ServiceUser)
def edit_serviceuser(request, serviceuser, serviceuserid):
""" Edit a ServiceUser """
serviceuser = EditServiceUserForm(request.POST or None, instance=serviceuser)
if serviceuser.is_valid():
user_object = serviceuser.save(commit=False)
with transaction.atomic(), reversion.create_revision():
if serviceuser.cleaned_data['password']:
user_object.set_password(serviceuser.cleaned_data['password'])
user = EditServiceUserForm(request.POST or None, instance=user)
if user.is_valid():
user_object = user.save(commit=False)
if user.cleaned_data['password']:
user_object.set_password(user.cleaned_data['password'])
if user.changed_data:
user_object.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in serviceuser.changed_data
))
messages.success(request, "L'user a bien été modifié")
return redirect(reverse('users:index-serviceusers'))
return form({'userform': serviceuser, 'action_name':'Editer un serviceuser'}, 'users/user.html', request)
@ -328,9 +294,7 @@ def edit_serviceuser(request, serviceuser, serviceuserid):
def del_serviceuser(request, serviceuser, serviceuserid):
"""Suppression d'un ou plusieurs serviceusers"""
if request.method == "POST":
with transaction.atomic(), reversion.create_revision():
serviceuser.delete()
reversion.set_user(request.user)
user.delete()
messages.success(request, "L'user a été détruite")
return redirect(reverse('users:index-serviceusers'))
return form(
@ -350,10 +314,7 @@ def add_ban(request, user, userid):
ban_instance = Ban(user=user)
ban = BanForm(request.POST or None, instance=ban_instance)
if ban.is_valid():
with transaction.atomic(), reversion.create_revision():
_ban_object = ban.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Bannissement ajouté")
return redirect(reverse(
'users:profil',
@ -374,12 +335,8 @@ def edit_ban(request, ban_instance, banid):
Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement"""
ban = BanForm(request.POST or None, instance=ban_instance)
if ban.is_valid():
with transaction.atomic(), reversion.create_revision():
if ban.changed_data:
ban.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in ban.changed_data
))
messages.success(request, "Bannissement modifié")
return redirect(reverse('users:index'))
return form({'userform': ban, 'action_name': 'Editer un ban'}, 'users/user.html', request)
@ -399,10 +356,7 @@ def add_whitelist(request, user, userid):
instance=whitelist_instance
)
if whitelist.is_valid():
with transaction.atomic(), reversion.create_revision():
whitelist.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Accès à titre gracieux accordé")
return redirect(reverse(
'users:profil',
@ -428,12 +382,8 @@ def edit_whitelist(request, whitelist_instance, whitelistid):
instance=whitelist_instance
)
if whitelist.is_valid():
with transaction.atomic(), reversion.create_revision():
if whitelist.changed_data:
whitelist.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in whitelist.changed_data
))
messages.success(request, "Whitelist modifiée")
return redirect(reverse('users:index'))
return form({'userform': whitelist, 'action_name': 'Editer une whitelist'}, 'users/user.html', request)
@ -446,10 +396,7 @@ def add_school(request):
need cableur"""
school = SchoolForm(request.POST or None)
if school.is_valid():
with transaction.atomic(), reversion.create_revision():
school.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "L'établissement a été ajouté")
return redirect(reverse('users:index-school'))
return form({'userform': school, 'action_name':'Ajouter'}, 'users/user.html', request)
@ -462,12 +409,8 @@ def edit_school(request, school_instance, schoolid):
la base de donnée, need cableur"""
school = SchoolForm(request.POST or None, instance=school_instance)
if school.is_valid():
with transaction.atomic(), reversion.create_revision():
if school.changed_data:
school.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in school.changed_data
))
messages.success(request, "Établissement modifié")
return redirect(reverse('users:index-school'))
return form({'userform': school, 'action_name':'Editer'}, 'users/user.html', request)
@ -485,9 +428,7 @@ def del_school(request, instances):
school_dels = school.cleaned_data['schools']
for school_del in school_dels:
try:
with transaction.atomic(), reversion.create_revision():
school_del.delete()
reversion.set_comment("Destruction")
messages.success(request, "L'établissement a été supprimé")
except ProtectedError:
messages.error(
@ -504,10 +445,7 @@ def add_shell(request):
""" Ajouter un shell à la base de donnée"""
shell = ShellForm(request.POST or None)
if shell.is_valid():
with transaction.atomic(), reversion.create_revision():
shell.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Le shell a été ajouté")
return redirect(reverse('users:index-shell'))
return form({'userform': shell, 'action_name':'Ajouter'}, 'users/user.html', request)
@ -519,12 +457,8 @@ def edit_shell(request, shell_instance, listshellid):
""" Editer un shell à partir du listshellid"""
shell = ShellForm(request.POST or None, instance=shell_instance)
if shell.is_valid():
with transaction.atomic(), reversion.create_revision():
if shell.changed_data:
shell.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in shell.changed_data
))
messages.success(request, "Le shell a été modifié")
return redirect(reverse('users:index-shell'))
return form({'userform': shell, 'action_name':'Editer'}, 'users/user.html', request)
@ -535,9 +469,7 @@ def edit_shell(request, shell_instance, listshellid):
def del_shell(request, shell, listshellid):
"""Destruction d'un shell"""
if request.method == "POST":
with transaction.atomic(), reversion.create_revision():
shell.delete()
reversion.set_user(request.user)
messages.success(request, "Le shell a été détruit")
return redirect(reverse('users:index-shell'))
return form(
@ -554,10 +486,7 @@ def add_listright(request):
Obligation de fournir un gid pour la synchro ldap, unique """
listright = NewListRightForm(request.POST or None)
if listright.is_valid():
with transaction.atomic(), reversion.create_revision():
listright.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Le droit/groupe a été ajouté")
return redirect(reverse('users:index-listright'))
return form({'userform': listright, 'action_name': 'Ajouter'}, 'users/user.html', request)
@ -573,12 +502,8 @@ def edit_listright(request, listright_instance, listrightid):
instance=listright_instance
)
if listright.is_valid():
with transaction.atomic(), reversion.create_revision():
if listright.changed_data:
listright.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in listright.changed_data
))
messages.success(request, "Droit modifié")
return redirect(reverse('users:index-listright'))
return form({'userform': listright, 'action_name': 'Editer'}, 'users/user.html', request)
@ -594,9 +519,7 @@ def del_listright(request, instances):
listright_dels = listright.cleaned_data['listrights']
for listright_del in listright_dels:
try:
with transaction.atomic(), reversion.create_revision():
listright_del.delete()
reversion.set_comment("Destruction")
messages.success(request, "Le droit/groupe a été supprimé")
except ProtectedError:
messages.error(
@ -625,7 +548,6 @@ def mass_archive(request):
with transaction.atomic(), reversion.create_revision():
user.archive()
user.save()
reversion.set_user(request.user)
reversion.set_comment("Archivage")
messages.success(request, "%s users ont été archivés" % len(
to_archive_list
@ -650,16 +572,7 @@ def index(request):
request.GET.get('order'),
SortTable.USERS_INDEX
)
paginator = Paginator(users_list, pagination_number)
page = request.GET.get('page')
try:
users_list = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
users_list = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
users_list = paginator.page(paginator.num_pages)
users_list = re2o_paginator(request, users_list, pagination_number)
return render(request, 'users/index.html', {'users_list': users_list})
@ -675,16 +588,7 @@ def index_clubs(request):
request.GET.get('order'),
SortTable.USERS_INDEX
)
paginator = Paginator(clubs_list, pagination_number)
page = request.GET.get('page')
try:
clubs_list = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
clubs_list = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
clubs_list = paginator.page(paginator.num_pages)
clubs_list = re2o_paginator(request, clubs_list, pagination_number)
return render(request, 'users/index_clubs.html', {'clubs_list': clubs_list})
@ -700,16 +604,7 @@ def index_ban(request):
request.GET.get('order'),
SortTable.USERS_INDEX_BAN
)
paginator = Paginator(ban_list, pagination_number)
page = request.GET.get('page')
try:
ban_list = paginator.page(page)
except PageNotAnInteger:
# 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)
ban_list = re2o_paginator(request, ban_list, pagination_number)
return render(request, 'users/index_ban.html', {'ban_list': ban_list})
@ -725,16 +620,7 @@ def index_white(request):
request.GET.get('order'),
SortTable.USERS_INDEX_BAN
)
paginator = Paginator(white_list, pagination_number)
page = request.GET.get('page')
try:
white_list = paginator.page(page)
except PageNotAnInteger:
# 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)
white_list = re2o_paginator(request, white_list, pagination_number)
return render(
request,
'users/index_whitelist.html',
@ -754,16 +640,7 @@ def index_school(request):
request.GET.get('order'),
SortTable.USERS_INDEX_SCHOOL
)
paginator = Paginator(school_list, pagination_number)
page = request.GET.get('page')
try:
school_list = paginator.page(page)
except PageNotAnInteger:
# If page isn't an integer, deliver first page
school_list = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
school_list = paginator.page(paginator.num_pages)
school_list = re2o_paginator(request, school_list, pagination_number)
return render(
request,
'users/index_schools.html',
@ -832,6 +709,8 @@ def profil(request, users, userid):
request.GET.get('order'),
SortTable.MACHINES_INDEX
)
pagination_large_number = GeneralOption.get_cached_value('pagination_large_number')
machines = re2o_paginator(request, machines, pagination_large_number)
factures = Facture.objects.filter(user=users)
factures = SortTable.sort(
factures,