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

Merge branch 'acl' into 'master'

Acl

See merge request federez/re2o!55
This commit is contained in:
chirac 2018-01-07 02:38:41 +01:00
commit 23732800b3
117 changed files with 5513 additions and 1919 deletions

View file

@ -21,3 +21,4 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from .acl import *

40
cotisations/acl.py Normal file
View file

@ -0,0 +1,40 @@
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""cotisations.acl
Here are defined some functions to check acl on the application.
"""
def can_view(user):
"""Check if an user can view the application.
Args:
user: The user who wants to view the application.
Returns:
A couple (allowed, msg) where allowed is a boolean which is True if
viewing is granted and msg is a message (can be None).
"""
can = user.has_module_perms('cotisations')
return can, None if can else "Vous ne pouvez pas voir cette application."

View file

@ -43,6 +43,8 @@ from django.forms import ModelForm, Form
from django.core.validators import MinValueValidator
from .models import Article, Paiement, Facture, Banque
from re2o.field_permissions import FieldPermissionFormMixin
class NewFactureForm(ModelForm):
"""Creation d'une facture, moyen de paiement, banque et numero
@ -141,27 +143,18 @@ class NewFactureFormPdf(Form):
)
class EditFactureForm(NewFactureForm):
class EditFactureForm(FieldPermissionFormMixin, NewFactureForm):
"""Edition d'une facture : moyen de paiement, banque, user parent"""
class Meta(NewFactureForm.Meta):
fields = ['paiement', 'banque', 'cheque', 'user']
model = Facture
fields = '__all__'
def __init__(self, *args, **kwargs):
super(EditFactureForm, self).__init__(*args, **kwargs)
self.fields['user'].label = 'Adherent'
self.fields['user'].empty_label = "Séléctionner\
l'adhérent propriétaire"
class TrezEditFactureForm(EditFactureForm):
"""Vue pour édition controle trésorier"""
class Meta(EditFactureForm.Meta):
fields = '__all__'
def __init__(self, *args, **kwargs):
super(TrezEditFactureForm, self).__init__(*args, **kwargs)
self.fields['valid'].label = 'Validité de la facture'
self.fields['control'].label = 'Contrôle de la facture'
class ArticleForm(ModelForm):
@ -180,11 +173,19 @@ class DelArticleForm(Form):
"""Suppression d'un ou plusieurs articles en vente. Choix
parmis les modèles"""
articles = forms.ModelMultipleChoiceField(
queryset=Article.objects.all(),
queryset=Article.objects.none(),
label="Articles actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelArticleForm, self).__init__(*args, **kwargs)
if instances:
self.fields['articles'].queryset = instances
else:
self.fields['articles'].queryset = Article.objects.all()
class PaiementForm(ModelForm):
"""Creation d'un moyen de paiement, champ text moyen et type
@ -204,11 +205,19 @@ class DelPaiementForm(Form):
"""Suppression d'un ou plusieurs moyens de paiements, selection
parmis les models"""
paiements = forms.ModelMultipleChoiceField(
queryset=Paiement.objects.all(),
queryset=Paiement.objects.none(),
label="Moyens de paiement actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelPaiementForm, self).__init__(*args, **kwargs)
if instances:
self.fields['paiements'].queryset = instances
else:
self.fields['paiements'].queryset = Paiement.objects.all()
class BanqueForm(ModelForm):
"""Creation d'une banque, field name"""
@ -225,7 +234,15 @@ class BanqueForm(ModelForm):
class DelBanqueForm(Form):
"""Selection d'une ou plusieurs banques, pour suppression"""
banques = forms.ModelMultipleChoiceField(
queryset=Banque.objects.all(),
queryset=Banque.objects.none(),
label="Banques actuelles",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelBanqueForm, self).__init__(*args, **kwargs)
if instances:
self.fields['banques'].queryset = instances
else:
self.fields['banques'].queryset = Banque.objects.all()

View file

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-30 23:07
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cotisations', '0027_auto_20171029_1156'),
]
operations = [
migrations.AlterModelOptions(
name='article',
options={'permissions': (('view_article', 'Peut voir un objet article'),)},
),
migrations.AlterModelOptions(
name='banque',
options={'permissions': (('view_banque', 'Peut voir un objet banque'),)},
),
migrations.AlterModelOptions(
name='cotisation',
options={'permissions': (('view_cotisation', 'Peut voir un objet cotisation'), ('change_all_cotisation', 'Superdroit, peut modifier toutes les cotisations'))},
),
migrations.AlterModelOptions(
name='facture',
options={'permissions': (('change_facture_control', "Peut changer l'etat de controle"), ('change_facture_pdf', 'Peut éditer une facture pdf'), ('view_facture', 'Peut voir un objet facture'), ('change_all_facture', 'Superdroit, peut modifier toutes les factures'))},
),
migrations.AlterModelOptions(
name='paiement',
options={'permissions': (('view_paiement', 'Peut voir un objet paiement'),)},
),
migrations.AlterModelOptions(
name='vente',
options={'permissions': (('view_vente', 'Peut voir un objet vente'), ('change_all_vente', 'Superdroit, peut modifier toutes les ventes'))},
),
]

View file

@ -56,8 +56,10 @@ from django.db.models import Max
from django.utils import timezone
from machines.models import regen
from re2o.field_permissions import FieldPermissionModelMixin
class Facture(models.Model):
class Facture(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
@ -76,6 +78,15 @@ class Facture(models.Model):
valid = models.BooleanField(default=True)
control = models.BooleanField(default=False)
class Meta:
abstract = False
permissions = (
("change_facture_control", "Peut changer l'etat de controle"),
("change_facture_pdf", "Peut éditer une facture pdf"),
("view_facture", "Peut voir un objet facture"),
("change_all_facture", "Superdroit, peut modifier toutes les factures"),
)
def prix(self):
"""Renvoie le prix brut sans les quantités. Méthode
dépréciée"""
@ -103,6 +114,59 @@ class Facture(models.Model):
).values_list('name', flat=True))
return name
def get_instance(factureid, *args, **kwargs):
return Facture.objects.get(pk=factureid)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.add_facture'), u"Vous n'avez pas le\
droit de créer des factures"
def can_edit(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.change_facture'):
return False, u"Vous n'avez pas le droit d'éditer les factures"
elif not user_request.has_perm('cotisations.change_all_facture') and\
(self.control or not self.valid):
return False, u"Vous n'avez pas le droit d'éditer une facture\
controlée ou invalidée par un trésorier"
else:
return True, None
def can_delete(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.delete_facture'):
return False, u"Vous n'avez pas le droit de supprimer une facture"
if self.control or not self.valid:
return False, u"Vous ne pouvez pas supprimer une facture\
contrôlée ou invalidée par un trésorier"
else:
return True, None
def can_view_all(user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.view_facture'):
return False, u"Vous n'avez pas le droit de voir les factures"
return True, None
def can_view(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.view_facture') and\
self.user != user_request:
return False, u"Vous ne pouvez pas afficher l'historique d'une\
facture d'un autre user que vous sans droit cableur"
elif not self.valid:
return False, u"La facture est invalidée et ne peut être affichée"
else:
return True, None
@staticmethod
def can_change_control(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.change_facture_control'), "Vous ne pouvez pas éditer le controle sans droit trésorier"
@staticmethod
def can_change_pdf(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.change_facture_pdf'), "Vous ne pouvez pas éditer une facture sans droit trésorier"
field_permissions = {
'control': can_change_control,
}
def __str__(self):
return str(self.user) + ' ' + str(self.date)
@ -149,6 +213,12 @@ class Vente(models.Model):
max_length=255
)
class Meta:
permissions = (
("view_vente", "Peut voir un objet vente"),
("change_all_vente", "Superdroit, peut modifier toutes les ventes"),
)
def prix_total(self):
"""Renvoie le prix_total de self (nombre*prix)"""
return self.prix*self.number
@ -201,6 +271,46 @@ class Vente(models.Model):
self.update_cotisation()
super(Vente, self).save(*args, **kwargs)
def get_instance(venteid, *args, **kwargs):
return Vente.objects.get(pk=venteid)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.add_vente'), u"Vous n'avez pas le\
droit de créer des ventes"
return True, None
def can_edit(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.change_vente'):
return False, u"Vous n'avez pas le droit d'éditer les ventes"
elif not user_request.has_perm('cotisations.change_all_vente') and\
(self.facture.control or not self.facture.valid):
return False, u"Vous n'avez pas le droit d'éditer une vente\
controlée ou invalidée par un trésorier"
else:
return True, None
def can_delete(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.delete_vente'):
return False, u"Vous n'avez pas le droit de supprimer une vente"
if self.facture.control or not self.facture.valid:
return False, u"Vous ne pouvez pas supprimer une vente\
contrôlée ou invalidée par un trésorier"
else:
return True, None
def can_view_all(user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.view_vente'):
return False, u"Vous n'avez pas le droit de voir les ventes"
return True, None
def can_view(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.view_vente') and\
self.facture.user != user_request:
return False, u"Vous ne pouvez pas afficher l'historique d'une\
facture d'un autre user que vous sans droit cableur"
else:
return True, None
def __str__(self):
return str(self.name) + ' ' + str(self.facture)
@ -269,6 +379,11 @@ class Article(models.Model):
unique_together = ('name', 'type_user')
class Meta:
permissions = (
("view_article", "Peut voir un objet article"),
)
def clean(self):
if self.name.lower() == "solde":
raise ValidationError("Solde est un nom d'article invalide")
@ -277,6 +392,29 @@ class Article(models.Model):
"La durée est obligatoire si il s'agit d'une cotisation"
)
def get_instance(articleid, *args, **kwargs):
return Article.objects.get(pk=articleid)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.add_article'), u"Vous n'avez pas le\
droit d'ajouter des articles"
def can_edit(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.change_article'), u"Vous n'avez pas le\
droit d'éditer des articles"
def can_delete(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.delete_article'), u"Vous n'avez pas le\
droit de supprimer des articles"
def can_view_all(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.view_article'), u"Vous n'avez pas le\
droit de voir des articles"
def can_view(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.view_article'), u"Vous n'avez pas le\
droit de voir des articles"
def __str__(self):
return self.name
@ -287,6 +425,34 @@ class Banque(models.Model):
name = models.CharField(max_length=255)
class Meta:
permissions = (
("view_banque", "Peut voir un objet banque"),
)
def get_instance(banqueid, *args, **kwargs):
return Banque.objects.get(pk=banqueid)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.add_banque'), u"Vous n'avez pas le\
droit d'ajouter des banques"
def can_edit(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.change_banque'), u"Vous n'avez pas le\
droit d'éditer des banques"
def can_delete(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.delete_banque'), u"Vous n'avez pas le\
droit de supprimer des banques"
def can_view_all(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.view_banque'), u"Vous n'avez pas le\
droit de voir des banques"
def can_view(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.view_banque'), u"Vous n'avez pas le\
droit de voir des banques"
def __str__(self):
return self.name
@ -302,6 +468,34 @@ class Paiement(models.Model):
moyen = models.CharField(max_length=255)
type_paiement = models.IntegerField(choices=PAYMENT_TYPES, default=0)
class Meta:
permissions = (
("view_paiement", "Peut voir un objet paiement"),
)
def get_instance(paiementid, *args, **kwargs):
return Paiement.objects.get(pk=paiementid)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.add_paiement'), u"Vous n'avez pas le\
droit d'ajouter des paiements"
def can_edit(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.change_paiement'), u"Vous n'avez pas le\
droit d'éditer des paiements"
def can_delete(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.delete_paiement'), u"Vous n'avez pas le\
droit de supprimer des paiements"
def can_view_all(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.view_paiement'), u"Vous n'avez pas le\
droit de voir des paiements"
def can_view(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.view_paiement'), u"Vous n'avez pas le\
droit de voir des paiements"
def __str__(self):
return self.moyen
@ -334,6 +528,52 @@ class Cotisation(models.Model):
date_start = models.DateTimeField()
date_end = models.DateTimeField()
class Meta:
permissions = (
("view_cotisation", "Peut voir un objet cotisation"),
("change_all_cotisation", "Superdroit, peut modifier toutes les cotisations"),
)
def get_instance(cotisationid, *args, **kwargs):
return Cotisations.objects.get(pk=cotisationid)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.add_cotisation'), u"Vous n'avez pas le\
droit de créer des cotisations"
return True, None
def can_edit(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.change_cotisation'):
return False, u"Vous n'avez pas le droit d'éditer les cotisations"
elif not user_request.has_perm('cotisations.change_all_cotisation') and\
(self.vente.facture.control or not self.vente.facture.valid):
return False, u"Vous n'avez pas le droit d'éditer une cotisation\
controlée ou invalidée par un trésorier"
else:
return True, None
def can_delete(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.delete_cotisation'):
return False, u"Vous n'avez pas le droit de supprimer une cotisations"
if self.vente.facture.control or not self.vente.facture.valid:
return False, u"Vous ne pouvez pas supprimer une cotisations\
contrôlée ou invalidée par un trésorier"
else:
return True, None
def can_view_all(user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.view_cotisation'):
return False, u"Vous n'avez pas le droit de voir les cotisations"
return True, None
def can_view(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.view_cotisation') and\
self.vente.facture.user != user_request:
return False, u"Vous ne pouvez pas afficher l'historique d'une\
cotisation d'un autre user que vous sans droit cableur"
else:
return True, None
def __str__(self):
return str(self.vente)

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -41,11 +43,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ article.duration }}</td>
<td>{{ article.type_user }}</td>
<td class="text-right">
{% if is_trez %}
{% can_edit article %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'cotisations:edit-article' article.id %}">
<i class="glyphicon glyphicon-edit"></i>
</a>
{% endif %}
{% acl_end %}
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'cotisations:history' 'article' article.id %}">
<i class="glyphicon glyphicon-time"></i>
</a>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -33,11 +35,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<tr>
<td>{{ banque.name }}</td>
<td class="text-right">
{% if is_trez %}
{% can_edit banque %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'cotisations:edit-banque' banque.id %}">
<i class="glyphicon glyphicon-edit"></i>
</a>
{% endif %}
{% acl_end %}
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'cotisations:history' 'banque' banque.id %}">
<i class="glyphicon glyphicon-time"></i>
</a>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
{% if facture_list.paginator %}
{% include "pagination.html" with list=facture_list %}
{% endif %}
@ -47,7 +49,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ facture.paiement }}</td>
<td>{{ facture.date }}</td>
<td>{{ facture.id }}</td>
{% if is_cableur %}
<td>
<div class="dropdown">
<button class="btn btn-default dropdown-toggle" type="button" id="editionfacture" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
@ -55,17 +56,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="editionfacture">
{% if facture.valid and not facture.control or is_trez %}
{% can_edit facture %}
<li><a href="{% url 'cotisations:edit-facture' facture.id %}"><i class="glyphicon glyphicon-bitcoin"></i> Modifier</a></li>
<li><a href="{% url 'cotisations:del-facture' facture.id %}"><i class="glyphicon glyphicon-trash"></i> Supprimer</a></li>
<li><a href="{% url 'cotisations:history' 'facture' facture.id %}"><i class="glyphicon glyphicon-time"></i> Historique</a></li>
{% else %}
{% acl_else %}
<li>Facture controlée</li>
{% endif %}
{% acl_end %}
{% can_delete facture %}
<li><a href="{% url 'cotisations:del-facture' facture.id %}"><i class="glyphicon glyphicon-trash"></i> Supprimer</a></li>
{% acl_end %}
<li><a href="{% url 'cotisations:history' 'facture' facture.id %}"><i class="glyphicon glyphicon-time"></i> Historique</a></li>
</ul>
</div>
</td>
{% endif %}
<td>
{% if facture.valid %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:facture-pdf' facture.id %}">

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -33,11 +35,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<tr>
<td>{{ paiement.moyen }}</td>
<td class="text-right">
{% if is_trez %}
{% can_edit paiement %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'cotisations:edit-paiement' paiement.id %}">
<i class="glyphicon glyphicon-edit"></i>
</a>
{% endif %}
{% acl_end %}
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'cotisations:history' 'paiement' paiement.id %}">
<i class="glyphicon glyphicon-time"></i>
</a>

View file

@ -24,15 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% block title %}Articles{% endblock %}
{% block content %}
<h2>Liste des types d'articles</h2>
{% if is_trez %}
{% can_create Article %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:add-article' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un type d'articles</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'cotisations:del-article' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs types d'articles</a>
{% endif %}
{% include "cotisations/aff_article.html" with article_list=article_list %}
<br />
<br />

View file

@ -24,15 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% block title %}Banques{% endblock %}
{% block content %}
<h2>Liste des banques</h2>
{% can_create Banque %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:add-banque' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter une banque</a>
{% if is_trez %}
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'cotisations:del-banque' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer une ou plusieurs banques</a>
{% endif %}
{% include "cotisations/aff_banque.html" with banque_list=banque_list %}
<br />
<br />

View file

@ -24,15 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% block title %}Paiements{% endblock %}
{% block content %}
<h2>Liste des types de paiements</h2>
{% if is_trez %}
{% can_create Paiement %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:add-paiement' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un type de paiement</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'cotisations:del-paiement' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs types de paiements</a>
{% endif %}
{% include "cotisations/aff_paiement.html" with paiement_list=paiement_list %}
<br />
<br />

View file

@ -23,9 +23,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
{% block sidebar %}
{% if is_trez %}
{% can_change Facture pdf %}
<a class="list-group-item list-group-item-success" href="{% url "cotisations:new-facture-pdf" %}">
<i class="glyphicon glyphicon-plus"></i>
Créer une facture
@ -34,21 +35,29 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<i class="glyphicon glyphicon-eye-open"></i>
Contrôler les factures
</a>
{% endif %}
{% acl_end %}
{% can_view_all Facture %}
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index" %}">
<i class="glyphicon glyphicon-list"></i>
Factures
</a>
{% acl_end %}
{% can_view_all Article %}
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-article" %}">
<i class="glyphicon glyphicon-list"></i>
Articles en vente
</a>
{% acl_end %}
{% can_view_all Banque %}
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-banque" %}">
<i class="glyphicon glyphicon-list"></i>
Banques
</a>
{% acl_end %}
{% can_view_all Paiement %}
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-paiement" %}">
<i class="glyphicon glyphicon-list"></i>
Moyens de paiement
</a>
{% acl_end %}
{% endblock %}

View file

@ -24,6 +24,7 @@ from __future__ import unicode_literals
from django.conf.urls import url
import re2o
from . import views
urlpatterns = [
@ -99,20 +100,11 @@ urlpatterns = [
views.index_paiement,
name='index-paiement'
),
url(r'^history/(?P<object_name>facture)/(?P<object_id>[0-9]+)$',
views.history,
name='history'
),
url(r'^history/(?P<object_name>article)/(?P<object_id>[0-9]+)$',
views.history,
name='history'
),
url(r'^history/(?P<object_name>paiement)/(?P<object_id>[0-9]+)$',
views.history,
name='history'),
url(r'^history/(?P<object_name>banque)/(?P<object_id>[0-9]+)$',
views.history,
name='history'
url(
r'history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
re2o.views.history,
name='history',
kwargs={'application':'cotisations'},
),
url(r'^control/$',
views.control,

View file

@ -44,11 +44,19 @@ from re2o.settings import LOGO_PATH
from re2o import settings
from re2o.views import form
from re2o.utils import SortTable
from re2o.acl import (
can_create,
can_edit,
can_delete,
can_view,
can_view_all,
can_delete_set,
can_change,
)
from preferences.models import OptionalUser, AssoOption, GeneralOption
from .models import Facture, Article, Vente, Paiement, Banque
from .forms import (
NewFactureForm,
TrezEditFactureForm,
EditFactureForm,
ArticleForm,
DelArticleForm,
@ -64,9 +72,11 @@ from .forms import (
from .tex import render_invoice
@login_required
@permission_required('cableur')
def new_facture(request, userid):
@can_create(Facture)
@can_edit(User)
def new_facture(request, user, userid):
"""Creation d'une facture pour un user. Renvoie la liste des articles
et crée des factures dans un formset. Utilise un peu de js coté template
pour ajouter des articles.
@ -74,11 +84,6 @@ def new_facture(request, userid):
enfin sauve la facture parente.
TODO : simplifier cette fonction, déplacer l'intelligence coté models
Facture et Vente."""
try:
user = User.objects.get(pk=userid)
except User.DoesNotExist:
messages.error(request, u"Utilisateur inexistant")
return redirect(reverse('cotisations:index'))
facture = Facture(user=user)
# Le template a besoin de connaitre les articles pour le js
article_list = Article.objects.filter(
@ -163,7 +168,7 @@ def new_facture(request, userid):
@login_required
@permission_required('tresorier')
@can_change(Facture, 'pdf')
def new_facture_pdf(request):
"""Permet de générer un pdf d'une facture. Réservée
au trésorier, permet d'emettre des factures sans objet
@ -203,31 +208,13 @@ def new_facture_pdf(request):
@login_required
def facture_pdf(request, factureid):
@can_view(Facture)
def facture_pdf(request, facture, factureid):
"""Affiche en pdf une facture. Cree une ligne par Vente de la facture,
et génére une facture avec le total, le moyen de paiement, l'adresse
de l'adhérent, etc. Réservée à self pour un user sans droits,
les droits cableurs permettent d'afficher toute facture"""
try:
facture = Facture.objects.get(pk=factureid)
except Facture.DoesNotExist:
messages.error(request, u"Facture inexistante")
return redirect(reverse('cotisations:index'))
if not request.user.has_perms(('cableur',))\
and facture.user != request.user:
messages.error(request, "Vous ne pouvez pas afficher une facture ne vous\
appartenant pas sans droit cableur")
return redirect(reverse(
'users:profil',
kwargs={'userid': str(request.user.id)}
))
if not facture.valid:
messages.error(request, "Vous ne pouvez pas afficher\
une facture non valide")
return redirect(reverse(
'users:profil',
kwargs={'userid': str(request.user.id)}
))
ventes_objects = Vente.objects.all().filter(facture=facture)
ventes = []
options, _created = AssoOption.objects.get_or_create()
@ -251,27 +238,12 @@ def facture_pdf(request, factureid):
@login_required
@permission_required('cableur')
def edit_facture(request, factureid):
@can_edit(Facture)
def edit_facture(request, facture, factureid):
"""Permet l'édition d'une facture. On peut y éditer les ventes
déjà effectuer, ou rendre une facture invalide (non payées, chèque
en bois etc). Mets à jour les durée de cotisation attenantes"""
try:
facture = Facture.objects.get(pk=factureid)
except Facture.DoesNotExist:
messages.error(request, u"Facture inexistante")
return redirect(reverse('cotisations:index'))
if request.user.has_perms(['tresorier']):
facture_form = TrezEditFactureForm(
request.POST or None,
instance=facture
)
elif facture.control or not facture.valid:
messages.error(request, "Vous ne pouvez pas editer une facture\
controlée ou invalidée par le trésorier")
return redirect(reverse('cotisations:index'))
else:
facture_form = EditFactureForm(request.POST or None, instance=facture)
facture_form = EditFactureForm(request.POST or None, instance=facture, user=request.user)
ventes_objects = Vente.objects.filter(facture=facture)
vente_form_set = modelformset_factory(
Vente,
@ -297,19 +269,10 @@ def edit_facture(request, factureid):
@login_required
@permission_required('cableur')
def del_facture(request, factureid):
@can_delete(Facture)
def del_facture(request, facture, factureid):
"""Suppression d'une facture. Supprime en cascade les ventes
et cotisations filles"""
try:
facture = Facture.objects.get(pk=factureid)
except Facture.DoesNotExist:
messages.error(request, u"Facture inexistante")
return redirect(reverse('cotisations:index'))
if facture.control or not facture.valid:
messages.error(request, "Vous ne pouvez pas editer une facture\
controlée ou invalidée par le trésorier")
return redirect(reverse('cotisations:index'))
if request.method == "POST":
with transaction.atomic(), reversion.create_revision():
facture.delete()
@ -323,14 +286,10 @@ def del_facture(request, factureid):
@login_required
@permission_required('cableur')
def credit_solde(request, userid):
@can_create(Facture)
@can_edit(User)
def credit_solde(request, user, userid):
""" Credit ou débit de solde """
try:
user = User.objects.get(pk=userid)
except User.DoesNotExist:
messages.error(request, u"Utilisateur inexistant")
return redirect(reverse('cotisations:index'))
facture = CreditSoldeForm(request.POST or None)
if facture.is_valid():
facture_instance = facture.save(commit=False)
@ -355,7 +314,7 @@ def credit_solde(request, userid):
@login_required
@permission_required('tresorier')
@can_create(Article)
def add_article(request):
"""Ajoute un article. Champs : désignation,
prix, est-ce une cotisation et si oui sa durée
@ -376,15 +335,10 @@ def add_article(request):
@login_required
@permission_required('tresorier')
def edit_article(request, articleid):
@can_edit(Article)
def edit_article(request, article_instance, articleid):
"""Edition d'un article (designation, prix, etc)
Réservé au trésorier"""
try:
article_instance = Article.objects.get(pk=articleid)
except Article.DoesNotExist:
messages.error(request, u"Entrée inexistante")
return redirect(reverse('cotisations:index-article'))
article = ArticleForm(request.POST or None, instance=article_instance)
if article.is_valid():
with transaction.atomic(), reversion.create_revision():
@ -401,10 +355,10 @@ def edit_article(request, articleid):
@login_required
@permission_required('tresorier')
def del_article(request):
@can_delete_set(Article)
def del_article(request, instances):
"""Suppression d'un article en vente"""
article = DelArticleForm(request.POST or None)
article = DelArticleForm(request.POST or None, instances=instances)
if article.is_valid():
article_del = article.cleaned_data['articles']
with transaction.atomic(), reversion.create_revision():
@ -416,7 +370,7 @@ def del_article(request):
@login_required
@permission_required('tresorier')
@can_create(Paiement)
def add_paiement(request):
"""Ajoute un moyen de paiement. Relié aux factures
via foreign key"""
@ -432,14 +386,9 @@ def add_paiement(request):
@login_required
@permission_required('tresorier')
def edit_paiement(request, paiementid):
@can_edit(Paiement)
def edit_paiement(request, paiement_instance, paiementid):
"""Edition d'un moyen de paiement"""
try:
paiement_instance = Paiement.objects.get(pk=paiementid)
except Paiement.DoesNotExist:
messages.error(request, u"Entrée inexistante")
return redirect(reverse('cotisations:index-paiement'))
paiement = PaiementForm(request.POST or None, instance=paiement_instance)
if paiement.is_valid():
with transaction.atomic(), reversion.create_revision():
@ -456,10 +405,10 @@ def edit_paiement(request, paiementid):
@login_required
@permission_required('tresorier')
def del_paiement(request):
@can_delete_set(Paiement)
def del_paiement(request, instances):
"""Suppression d'un moyen de paiement"""
paiement = DelPaiementForm(request.POST or None)
paiement = DelPaiementForm(request.POST or None, instances=instances)
if paiement.is_valid():
paiement_dels = paiement.cleaned_data['paiements']
for paiement_del in paiement_dels:
@ -483,7 +432,7 @@ def del_paiement(request):
@login_required
@permission_required('cableur')
@can_create(Banque)
def add_banque(request):
"""Ajoute une banque à la liste des banques"""
banque = BanqueForm(request.POST or None)
@ -498,14 +447,9 @@ def add_banque(request):
@login_required
@permission_required('tresorier')
def edit_banque(request, banqueid):
@can_edit(Banque)
def edit_banque(request, banque_instance, banqueid):
"""Edite le nom d'une banque"""
try:
banque_instance = Banque.objects.get(pk=banqueid)
except Banque.DoesNotExist:
messages.error(request, u"Entrée inexistante")
return redirect(reverse('cotisations:index-banque'))
banque = BanqueForm(request.POST or None, instance=banque_instance)
if banque.is_valid():
with transaction.atomic(), reversion.create_revision():
@ -522,10 +466,10 @@ def edit_banque(request, banqueid):
@login_required
@permission_required('tresorier')
def del_banque(request):
@can_delete_set(Banque)
def del_banque(request, instances):
"""Supprime une banque"""
banque = DelBanqueForm(request.POST or None)
banque = DelBanqueForm(request.POST or None, instances=instances)
if banque.is_valid():
banque_dels = banque.cleaned_data['banques']
for banque_del in banque_dels:
@ -543,7 +487,8 @@ def del_banque(request):
@login_required
@permission_required('tresorier')
@can_view_all(Facture)
@can_change(Facture, 'control')
def control(request):
"""Pour le trésorier, vue pour controler en masse les
factures.Case à cocher, pratique"""
@ -583,7 +528,7 @@ def control(request):
@login_required
@permission_required('cableur')
@can_view_all(Article)
def index_article(request):
"""Affiche l'ensemble des articles en vente"""
article_list = Article.objects.order_by('name')
@ -593,7 +538,7 @@ def index_article(request):
@login_required
@permission_required('cableur')
@can_view_all(Paiement)
def index_paiement(request):
"""Affiche l'ensemble des moyens de paiement en vente"""
paiement_list = Paiement.objects.order_by('moyen')
@ -603,7 +548,7 @@ def index_paiement(request):
@login_required
@permission_required('cableur')
@can_view_all(Banque)
def index_banque(request):
"""Affiche l'ensemble des banques"""
banque_list = Banque.objects.order_by('name')
@ -613,7 +558,7 @@ def index_banque(request):
@login_required
@permission_required('cableur')
@can_view_all(Facture)
def index(request):
"""Affiche l'ensemble des factures, pour les cableurs et +"""
options, _created = GeneralOption.objects.get_or_create()
@ -639,60 +584,3 @@ def index(request):
return render(request, 'cotisations/index.html', {
'facture_list': facture_list
})
@login_required
def history(request, object_name, object_id):
"""Affiche l'historique de chaque objet"""
if object_name == 'facture':
try:
object_instance = Facture.objects.get(pk=object_id)
except Facture.DoesNotExist:
messages.error(request, "Facture inexistante")
return redirect(reverse('cotisations:index'))
if not request.user.has_perms(('cableur',))\
and object_instance.user != request.user:
messages.error(request, "Vous ne pouvez pas afficher l'historique\
d'une facture d'un autre user que vous sans droit cableur")
return redirect(reverse(
'users:profil',
kwargs={'userid':str(request.user.id)}
))
elif object_name == 'paiement' and request.user.has_perms(('cableur',)):
try:
object_instance = Paiement.objects.get(pk=object_id)
except Paiement.DoesNotExist:
messages.error(request, "Paiement inexistant")
return redirect(reverse('cotisations:index'))
elif object_name == 'article' and request.user.has_perms(('cableur',)):
try:
object_instance = Article.objects.get(pk=object_id)
except Article.DoesNotExist:
messages.error(request, "Article inexistante")
return redirect(reverse('cotisations:index'))
elif object_name == 'banque' and request.user.has_perms(('cableur',)):
try:
object_instance = Banque.objects.get(pk=object_id)
except Banque.DoesNotExist:
messages.error(request, "Banque inexistante")
return redirect(reverse('cotisations:index'))
else:
messages.error(request, "Objet inconnu")
return redirect(reverse('cotisations:index'))
options, _created = GeneralOption.objects.get_or_create()
pagination_number = options.pagination_number
reversions = Version.objects.get_for_object(object_instance)
paginator = Paginator(reversions, pagination_number)
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 results.
reversions = paginator.page(paginator.num_pages)
return render(request, 're2o/history.html', {
'reversions': reversions,
'object': object_instance
})

View file

@ -21,3 +21,4 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from .acl import *

40
logs/acl.py Normal file
View file

@ -0,0 +1,40 @@
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""logs.acl
Here are defined some functions to check acl on the application.
"""
def can_view(user):
"""Check if an user can view the application.
Args:
user: The user who wants to view the application.
Returns:
A couple (allowed, msg) where allowed is a boolean which is True if
viewing is granted and msg is a message (can be None).
"""
can = user.has_module_perms('admin')
return can, None if can else "Vous ne pouvez pas voir cette application."

View file

@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endif %}
{% load logs_extra %}
{% load acl %}
<table class="table table-striped">
<thead>
@ -47,14 +48,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ revision.user }}</td>
<td>{{ revision.date_created }}</td>
<td>{{ revision.comment }}</td>
{% if is_bureau %}
{% can_edit_history %}
<td>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' revision.id %}">
<i class="glyphicon glyphicon-remove"></i>
Annuler
</a>
</td>
{% endif %}
{% acl_end %}
</tr>
{% endfor %}
{% endfor %}

View file

@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endif %}
{% load logs_extra %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -51,14 +51,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endif %}
</i>)
</td>
{% if is_bureau %}
{% can_edit_history %}
<td>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' v.rev_id %}">
<i class="glyphicon glyphicon-remove"></i>
Annuler
</a>
</td>
{% endif %}
{% acl_end %}
</tr>
{% elif v.version.content_type.model == 'whitelist' %}
<tr class="success">
@ -74,14 +74,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endif %}
</i>)
</td>
{% if is_bureau %}
{% can_edit_history%}
<td>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' v.rev_id %}">
<i class="glyphicon glyphicon-remove"></i>
Annuler
</a>
</td>
{% endif %}
{% acl_end %}
</tr>
{% elif v.version.content_type.model == 'user' %}
<tr>
@ -93,14 +93,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
(<i>{{ v.comment }}</i>)
{% endif %}
</td>
{% if is_bureau %}
{% can_edit_history %}
<td>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' v.rev_id %}">
<i class="glyphicon glyphicon-remove"></i>
Annuler
</a>
</td>
{% endif %}
{% acl_end %}
</tr>
{% elif v.version.content_type.model == 'vente' %}
<tr>
@ -112,14 +112,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
(<i>+{{ v.version.object.duration }} mois</i>)
{% endif %}
</td>
{% if is_bureau %}
{% can_edit_history %}
<td>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' v.rev_id %}">
<i class="glyphicon glyphicon-remove"></i>
Annuler
</a>
</td>
{% endif %}
{% acl_end %}
</tr>
{% elif v.version.content_type.model == 'interface' %}
<tr>
@ -131,14 +131,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
(<i>{{ v.comment }}</i>)
{% endif %}
</td>
{% if is_bureau %}
{% can_edit_history %}
<td>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' v.rev_id %}">
<i class="glyphicon glyphicon-remove"></i>
Annuler
</a>
</td>
{% endif %}
{% acl_end %}
</tr>
{% endif %}
{% endfor %}

View file

@ -23,9 +23,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
{% block sidebar %}
{% if is_cableur %}
{% can_view_app logs %}
<a class="list-group-item list-group-item-info" href="{% url "logs:index" %}">
<i class="glyphicon glyphicon-stats"></i>
Résumé
@ -50,5 +51,5 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<i class="glyphicon glyphicon-stats"></i>
Utilisateurs
</a>
{% endif %}
{% acl_end %}
{% endblock %}

View file

@ -41,7 +41,7 @@ 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.contrib.auth.decorators import login_required
from django.db.models import Count
from reversion.models import Revision
@ -50,7 +50,6 @@ from reversion.models import Version, ContentType
from users.models import (
User,
ServiceUser,
Right,
School,
ListRight,
ListShell,
@ -93,7 +92,17 @@ from topologie.models import (
)
from preferences.models import GeneralOption
from re2o.views import form
from re2o.utils import all_whitelisted, all_baned, all_has_access, all_adherent
from re2o.utils import (
all_whitelisted,
all_baned,
all_has_access,
all_adherent,
)
from re2o.acl import (
can_view_all,
can_view_app,
can_edit_history,
)
from re2o.utils import all_active_assigned_interfaces_count
from re2o.utils import all_active_interfaces_count, SortTable
@ -108,7 +117,7 @@ STATS_DICT = {
@login_required
@permission_required('cableur')
@can_view_app('logs')
def index(request):
"""Affiche les logs affinés, date reformatées, selectionne
les event importants (ajout de droits, ajout de ban/whitelist)"""
@ -167,7 +176,7 @@ def index(request):
@login_required
@permission_required('cableur')
@can_view_all(GeneralOption)
def stats_logs(request):
"""Affiche l'ensemble des logs et des modifications sur les objets,
classés par date croissante, en vrac"""
@ -197,7 +206,7 @@ def stats_logs(request):
@login_required
@permission_required('bureau')
@can_edit_history
def revert_action(request, revision_id):
""" Annule l'action en question """
try:
@ -215,7 +224,9 @@ def revert_action(request, revision_id):
@login_required
@permission_required('cableur')
@can_view_all(IpList)
@can_view_all(Interface)
@can_view_all(User)
def stats_general(request):
"""Statistiques générales affinées sur les ip, activées, utilisées par
range, et les statistiques générales sur les users : users actifs,
@ -298,7 +309,10 @@ def stats_general(request):
@login_required
@permission_required('cableur')
@can_view_app('users')
@can_view_app('cotisations')
@can_view_app('machines')
@can_view_app('topologie')
def stats_models(request):
"""Statistiques générales, affiche les comptages par models:
nombre d'users, d'écoles, de droits, de bannissements,
@ -310,7 +324,6 @@ def stats_models(request):
'clubs': [Club.PRETTY_NAME, Club.objects.count()],
'serviceuser': [ServiceUser.PRETTY_NAME,
ServiceUser.objects.count()],
'right': [Right.PRETTY_NAME, Right.objects.count()],
'school': [School.PRETTY_NAME, School.objects.count()],
'listright': [ListRight.PRETTY_NAME, ListRight.objects.count()],
'listshell': [ListShell.PRETTY_NAME, ListShell.objects.count()],
@ -340,7 +353,7 @@ def stats_models(request):
OuverturePortList.objects.count()
],
'vlan': [Vlan.PRETTY_NAME, Vlan.objects.count()],
'SOA': [Mx.PRETTY_NAME, Mx.objects.count()],
'SOA': [SOA.PRETTY_NAME, SOA.objects.count()],
'Mx': [Mx.PRETTY_NAME, Mx.objects.count()],
'Ns': [Ns.PRETTY_NAME, Ns.objects.count()],
'nas': [Nas.PRETTY_NAME, Nas.objects.count()],
@ -368,7 +381,7 @@ def stats_models(request):
@login_required
@permission_required('cableur')
@can_view_app('users')
def stats_users(request):
"""Affiche les statistiques base de données aggrégées par user :
nombre de machines par user, d'etablissements par user,
@ -395,7 +408,7 @@ def stats_users(request):
num=Count('whitelist')
).order_by('-num')[:10],
'Droits': User.objects.annotate(
num=Count('right')
num=Count('groups')
).order_by('-num')[:10],
},
'Etablissement': {
@ -422,7 +435,7 @@ def stats_users(request):
@login_required
@permission_required('cableur')
@can_view_app('users')
def stats_actions(request):
"""Vue qui affiche les statistiques de modifications d'objets par
utilisateurs.

View file

@ -21,3 +21,4 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from .acl import *

40
machines/acl.py Normal file
View file

@ -0,0 +1,40 @@
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""machines.acl
Here are defined some functions to check acl on the application.
"""
def can_view(user):
"""Check if an user can view the application.
Args:
user: The user who wants to view the application.
Returns:
A couple (allowed, msg) where allowed is a boolean which is True if
viewing is granted and msg is a message (can be None).
"""
can = user.has_module_perms('machines')
return can, None if can else "Vous ne pouvez pas voir cette application."

View file

@ -38,6 +38,8 @@ from __future__ import unicode_literals
from django.forms import ModelForm, Form
from django import forms
from re2o.field_permissions import FieldPermissionFormMixin
from .models import (
Domain,
Machine,
@ -58,7 +60,7 @@ from .models import (
)
class EditMachineForm(ModelForm):
class EditMachineForm(FieldPermissionFormMixin, ModelForm):
"""Formulaire d'édition d'une machine"""
class Meta:
model = Machine
@ -117,10 +119,10 @@ class AddInterfaceForm(EditInterfaceForm):
fields = ['type', 'ipv4', 'mac_address', 'details']
def __init__(self, *args, **kwargs):
infra = kwargs.pop('infra')
user = kwargs.pop('user')
super(AddInterfaceForm, self).__init__(*args, **kwargs)
self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4"
if not infra:
if not IpType.can_use_all(user):
self.fields['type'].queryset = MachineType.objects.filter(
ip_type__in=IpType.objects.filter(need_infra=False)
)
@ -146,13 +148,14 @@ class BaseEditInterfaceForm(EditInterfaceForm):
fields = ['type', 'ipv4', 'mac_address', 'details']
def __init__(self, *args, **kwargs):
infra = kwargs.pop('infra')
user = kwargs.pop('user')
super(BaseEditInterfaceForm, self).__init__(*args, **kwargs)
self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4"
if not infra:
if not MachineType.can_use_all(user):
self.fields['type'].queryset = MachineType.objects.filter(
ip_type__in=IpType.objects.filter(need_infra=False)
)
if not IpType.can_use_all(user):
self.fields['ipv4'].queryset = IpList.objects.filter(
interface__isnull=True
).filter(ip_type__in=IpType.objects.filter(need_infra=False))
@ -177,9 +180,10 @@ class AliasForm(ModelForm):
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
infra = kwargs.pop('infra')
user = kwargs.pop('user')
super(AliasForm, self).__init__(*args, prefix=prefix, **kwargs)
if not infra:
can_use_all, reason = Extension.can_use_all(user)
if not can_use_all:
self.fields['extension'].queryset = Extension.objects.filter(
need_infra=False
)
@ -233,11 +237,19 @@ class MachineTypeForm(ModelForm):
class DelMachineTypeForm(Form):
"""Suppression d'un ou plusieurs machinetype"""
machinetypes = forms.ModelMultipleChoiceField(
queryset=MachineType.objects.all(),
queryset=MachineType.objects.none(),
label="Types de machines actuelles",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelMachineTypeForm, self).__init__(*args, **kwargs)
if instances:
self.fields['machinetypes'].queryset = instances
else:
self.fields['machinetypes'].queryset = MachineType.objects.all()
class IpTypeForm(ModelForm):
"""Formulaire d'ajout d'un iptype. Pas d'edition de l'ip de start et de
@ -264,11 +276,19 @@ class EditIpTypeForm(IpTypeForm):
class DelIpTypeForm(Form):
"""Suppression d'un ou plusieurs iptype"""
iptypes = forms.ModelMultipleChoiceField(
queryset=IpType.objects.all(),
queryset=IpType.objects.none(),
label="Types d'ip actuelles",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelIpTypeForm, self).__init__(*args, **kwargs)
if instances:
self.fields['iptypes'].queryset = instances
else:
self.fields['iptypes'].queryset = IpType.objects.all()
class ExtensionForm(ModelForm):
"""Formulaire d'ajout et edition d'une extension"""
@ -288,11 +308,19 @@ class ExtensionForm(ModelForm):
class DelExtensionForm(Form):
"""Suppression d'une ou plusieurs extensions"""
extensions = forms.ModelMultipleChoiceField(
queryset=Extension.objects.all(),
queryset=Extension.objects.none(),
label="Extensions actuelles",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelExtensionForm, self).__init__(*args, **kwargs)
if instances:
self.fields['extensions'].queryset = instances
else:
self.fields['extensions'].queryset = Extension.objects.all()
class SOAForm(ModelForm):
"""Ajout et edition d'un SOA"""
@ -308,11 +336,19 @@ class SOAForm(ModelForm):
class DelSOAForm(Form):
"""Suppression d'un ou plusieurs SOA"""
soa = forms.ModelMultipleChoiceField(
queryset=SOA.objects.all(),
queryset=SOA.objects.none(),
label="SOA actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelSOAForm, self).__init__(*args, **kwargs)
if instances:
self.fields['soa'].queryset = instances
else:
self.fields['soa'].queryset = SOA.objects.all()
class MxForm(ModelForm):
"""Ajout et edition d'un MX"""
@ -327,15 +363,22 @@ class MxForm(ModelForm):
interface_parent=None
).select_related('extension')
class DelMxForm(Form):
"""Suppression d'un ou plusieurs MX"""
mx = forms.ModelMultipleChoiceField(
queryset=Mx.objects.all(),
queryset=Mx.objects.none(),
label="MX actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelMxForm, self).__init__(*args, **kwargs)
if instances:
self.fields['mx'].queryset = instances
else:
self.fields['mx'].queryset = Mx.objects.all()
class NsForm(ModelForm):
"""Ajout d'un NS pour une zone
@ -356,11 +399,19 @@ class NsForm(ModelForm):
class DelNsForm(Form):
"""Suppresion d'un ou plusieurs NS"""
ns = forms.ModelMultipleChoiceField(
queryset=Ns.objects.all(),
queryset=Ns.objects.none(),
label="Enregistrements NS actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelNsForm, self).__init__(*args, **kwargs)
if instances:
self.fields['ns'].queryset = instances
else:
self.fields['ns'].queryset = Ns.objects.all()
class TxtForm(ModelForm):
"""Ajout d'un txt pour une zone"""
@ -376,11 +427,19 @@ class TxtForm(ModelForm):
class DelTxtForm(Form):
"""Suppression d'un ou plusieurs TXT"""
txt = forms.ModelMultipleChoiceField(
queryset=Txt.objects.all(),
queryset=Txt.objects.none(),
label="Enregistrements Txt actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelTxtForm, self).__init__(*args, **kwargs)
if instances:
self.fields['txt'].queryset = instances
else:
self.fields['txt'].queryset = Txt.objects.all()
class SrvForm(ModelForm):
"""Ajout d'un srv pour une zone"""
@ -396,11 +455,19 @@ class SrvForm(ModelForm):
class DelSrvForm(Form):
"""Suppression d'un ou plusieurs Srv"""
srv = forms.ModelMultipleChoiceField(
queryset=Srv.objects.all(),
queryset=Srv.objects.none(),
label="Enregistrements Srv actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelSrvForm, self).__init__(*args, **kwargs)
if instances:
self.fields['srv'].queryset = instances
else:
self.fields['srv'].queryset = Srv.objects.all()
class NasForm(ModelForm):
"""Ajout d'un type de nas (machine d'authentification,
@ -417,11 +484,19 @@ class NasForm(ModelForm):
class DelNasForm(Form):
"""Suppression d'un ou plusieurs nas"""
nas = forms.ModelMultipleChoiceField(
queryset=Nas.objects.all(),
queryset=Nas.objects.none(),
label="Enregistrements Nas actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelNasForm, self).__init__(*args, **kwargs)
if instances:
self.fields['nas'].queryset = instances
else:
self.fields['nas'].queryset = Nas.objects.all()
class ServiceForm(ModelForm):
"""Ajout et edition d'une classe de service : dns, dhcp, etc"""
@ -446,11 +521,19 @@ class ServiceForm(ModelForm):
class DelServiceForm(Form):
"""Suppression d'un ou plusieurs service"""
service = forms.ModelMultipleChoiceField(
queryset=Service.objects.all(),
queryset=Service.objects.none(),
label="Services actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelServiceForm, self).__init__(*args, **kwargs)
if instances:
self.fields['service'].queryset = instances
else:
self.fields['service'].queryset = Service.objects.all()
class VlanForm(ModelForm):
"""Ajout d'un vlan : id, nom"""
@ -466,11 +549,19 @@ class VlanForm(ModelForm):
class DelVlanForm(Form):
"""Suppression d'un ou plusieurs vlans"""
vlan = forms.ModelMultipleChoiceField(
queryset=Vlan.objects.all(),
queryset=Vlan.objects.none(),
label="Vlan actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelVlanForm, self).__init__(*args, **kwargs)
if instances:
self.fields['vlan'].queryset = instances
else:
self.fields['vlan'].queryset = Vlan.objects.all()
class EditOuverturePortConfigForm(ModelForm):
"""Edition de la liste des profils d'ouverture de ports

View file

@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 18:47
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('machines', '0069_auto_20171116_0822'),
]
operations = [
migrations.AlterModelOptions(
name='domain',
options={'permissions': (('view_domain', 'Peut voir un objet domain'),)},
),
migrations.AlterModelOptions(
name='extension',
options={'permissions': (('view_extension', 'Peut voir un objet extension'), ('use_all_extension', 'Peut utiliser toutes les extension'))},
),
migrations.AlterModelOptions(
name='interface',
options={'permissions': (('view_interface', 'Peut voir un objet interface'),)},
),
migrations.AlterModelOptions(
name='iplist',
options={'permissions': (('view_iplist', 'Peut voir un objet iplist'),)},
),
migrations.AlterModelOptions(
name='iptype',
options={'permissions': (('view_iptype', 'Peut voir un objet iptype'), ('use_all_iptype', 'Peut utiliser tous les iptype'))},
),
migrations.AlterModelOptions(
name='machine',
options={'permissions': (('view_machine', 'Peut voir un objet machine quelquonque'), ('change_machine_user', "Peut changer le propriétaire d'une machine"))},
),
migrations.AlterModelOptions(
name='machinetype',
options={'permissions': (('view_machinetype', 'Peut voir un objet machinetype'), ('use_all_machinetype', "Peut utiliser n'importe quel type de machine"))},
),
migrations.AlterModelOptions(
name='mx',
options={'permissions': (('view_mx', 'Peut voir un objet mx'),)},
),
migrations.AlterModelOptions(
name='nas',
options={'permissions': (('view_nas', 'Peut voir un objet Nas'),)},
),
migrations.AlterModelOptions(
name='ns',
options={'permissions': (('view_nx', 'Peut voir un objet nx'),)},
),
migrations.AlterModelOptions(
name='ouvertureportlist',
options={'permissions': (('view_ouvertureportlist', 'Peut voir un objet ouvertureport'),)},
),
migrations.AlterModelOptions(
name='service',
options={'permissions': (('view_service', 'Peut voir un objet service'),)},
),
migrations.AlterModelOptions(
name='soa',
options={'permissions': (('view_soa', 'Peut voir un objet soa'),)},
),
migrations.AlterModelOptions(
name='srv',
options={'permissions': (('view_soa', 'Peut voir un objet soa'),)},
),
migrations.AlterModelOptions(
name='txt',
options={'permissions': (('view_txt', 'Peut voir un objet txt'),)},
),
migrations.AlterModelOptions(
name='vlan',
options={'permissions': (('view_vlan', 'Peut voir un objet vlan'),)},
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 20:00
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('machines', '0070_auto_20171231_1947'),
]
operations = [
migrations.AlterModelOptions(
name='ns',
options={'permissions': (('view_ns', 'Peut voir un objet ns'),)},
),
]

File diff suppressed because it is too large Load diff

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -33,7 +35,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<tr>
<td>{{ alias }}</td>
<td class="text-right">
{% 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 %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -45,9 +47,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ extension.origin_v6 }}</td>
{% endif %}
<td class="text-right">
{% if is_infra %}
{% can_create Extension %}
{% include 'buttons/edit.html' with href='machines:edit-extension' id=extension.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='extension' id=extension.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -48,9 +50,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ type.vlan }}</td>
<td>{{ type.ouverture_ports }}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit type %}
{% include 'buttons/edit.html' with href='machines:edit-iptype' id=type.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='iptype' id=type.id %}
</td>
</tr>

View file

@ -22,11 +22,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
{% if machines_list.paginator %}
{% include "pagination.html" with list=machines_list %}
{% endif %}
<table class="table">
<table class="table" id="machines_table">
<colgroup>
<col>
<col>
@ -50,30 +52,23 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</a>
</td>
<td class="text-right">
{% can_create Interface machine.id %}
{% include 'buttons/add.html' with href='machines:new-interface' id=machine.id desc='Ajouter une interface' %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='machine' id=machine.id %}
{% can_delete machine %}
{% include 'buttons/suppr.html' with href='machines:del-machine' id=machine.id %}
{% acl_end %}
</td>
</tr>
{% for interface in machine.interface_set.all %}
<tr>
<td>
{% if interface.domain.related_domain.all %}
<div class="dropdown">
<button class="btn btn-default dropdown-toggle" type="button" id="editioninterface" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
{{ interface.domain }} <span class="caret"></span>
{{ interface.domain }}
<button class="btn btn-default btn-xs" type="button" data-toggle="collapse" data-target="#collapseDomain_{{interface.id}}" aria-expanded="true" aria-controls="collapseDomain_{{interface.id}}">
Afficher les alias
</button>
<ul class="dropdown-menu" aria-labelledby="editioninterface">
{% for al in interface.domain.related_domain.all %}
<li>
<a href="http://{{ al }}">
{{ al }}
<i class="glyphicon glyphicon-share-alt"></i>
</a>
</li>
{% endfor %}
</ul>
</div>
{% else %}
{{ interface.domain }}
{% endif %}
@ -97,27 +92,53 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<i class="glyphicon glyphicon-edit"></i> <span class="caret"></span>
</button>
{% include 'buttons/history.html' with href='machines:history' name='interface' id=interface.id %}
{% can_delete interface %}
{% include 'buttons/suppr.html' with href='machines:del-interface' id=interface.id %}
{% acl_end %}
<ul class="dropdown-menu" aria-labelledby="editioninterface">
{% can_edit interface %}
<li>
<a href="{% url 'machines:edit-interface' interface.id %}">
<i class="glyphicon glyphicon-edit"></i> Editer
</a>
</li>
{% acl_end %}
{% can_create Domain interface.id %}
<li>
<a href="{% url 'machines:index-alias' interface.id %}">
<i class="glyphicon glyphicon-edit"></i> Gerer les alias
</a>
</li>
{% acl_end %}
{% can_create OuverturePortList %}
<li>
<a href="{% url 'machines:port-config' interface.id%}">
<i class="glyphicon glyphicon-edit"></i> Gerer la configuration des ports
</a>
</li>
{% acl_end %}
</ul>
</div>
</td>
</tr>
{% if interface.domain.related_domain.all %}
<tr>
<td colspan=5 style="border-top: none; padding: 1px;">
<div class="collapse in" id="collapseDomain_{{interface.id}}">
<ul class="list-group" style="margin-bottom: 0px;">
{% for al in interface.domain.related_domain.all %}
<li class="list-group-item col-xs-6 col-sm-4 col-md-3" style="border: none;">
<a href="http://{{ al }}">
{{ al }}
<i class="glyphicon glyphicon-share-alt"></i>
</a>
</li>
{% endfor %}
</ul>
</div>
</td>
<tr>
{% endif %}
{% endfor %}
<tr>
<td colspan="8"></td>
@ -126,6 +147,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</tbody>
</table>
<script>
$("#machines_table").ready( function() {
var alias_div = [{% for machine in machines_list %}{% for interface in machine.interface_set.all %}{% if interface.domain.related_domain.all %}$("#collapseDomain_{{interface.id}}"), {% endif %}{% endfor %}{% endfor %}];
for (var i=0 ; i<alias_div.length ; i++) {
alias_div[i].collapse('hide');
}
} );
</script>
{% if machines_list.paginator %}
{% include "pagination.html" with list=machines_list %}
{% endif %}

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -35,9 +37,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ type.type }}</td>
<td>{{ type.ip_type }}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit type %}
{% include 'buttons/edit.html' with href='machines:edit-machinetype' id=type.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='machinetype' id=type.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -38,9 +40,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ mx.priority }}</td>
<td>{{ mx.name }}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit mx %}
{% include 'buttons/edit.html' with href='machines:edit-mx' id=mx.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='mx' id=mx.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -41,9 +43,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ nas.port_access_mode }}</td>
<td>{{ nas.autocapture_mac }}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit nas %}
{% include 'buttons/edit.html' with href='machines:edit-nas' id=nas.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='nas' id=nas.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -36,9 +38,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ ns.zone }}</td>
<td>{{ ns.ns }}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit ns %}
{% include 'buttons/edit.html' with href='machines:edit-ns' id=ns.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='ns' id=ns.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -40,9 +42,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ service.regular_time_regen }}</td>
<td>{% for serv in service.servers.all %}{{ serv }}, {% endfor %}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit service %}
{% include 'buttons/edit.html' with href='machines:edit-service' id=service.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='service' id=service.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -44,9 +46,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ soa.expire }}</td>
<td>{{ soa.ttl }}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit soa %}
{% include 'buttons/edit.html' with href='machines:edit-soa' id=soa.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='soa' id=soa.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -48,9 +50,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ srv.port }}</td>
<td>{{ srv.target }}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit srv %}
{% include 'buttons/edit.html' with href='machines:edit-srv' id=srv.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='srv' id=srv.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -36,9 +38,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ txt.zone }}</td>
<td>{{ txt.dns_entry }}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit txt %}
{% include 'buttons/edit.html' with href='machines:edit-txt' id=txt.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='txt' id=txt.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -39,9 +41,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ vlan.comment }}</td>
<td>{% for range in vlan.iptype_set.all %}{{ range }}, {% endfor%}</td>
<td class="text-right">
{% if is_infra %}
{% can_create Vlan %}
{% include 'buttons/edit.html' with href='machines:edit-vlan' id=vlan.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='vlan' id=vlan.id %}
</td>
</tr>

View file

@ -25,45 +25,47 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load bootstrap3 %}
{% load acl %}
{% block title %}Machines{% endblock %}
{% block content %}
<h2>Liste des extensions</h2>
{% if is_infra %}
{% can_create Extension %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-extension' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter une extension</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-extension' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer une ou plusieurs extensions</a>
{% endif %}
{% include "machines/aff_extension.html" with extension_list=extension_list %}
<h2>Liste des enregistrements SOA</h2>
{% if is_infra %}
{% can_create SOA %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-soa' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un enregistrement SOA</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-soa' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un enregistrement SOA</a>
{% endif %}
{% include "machines/aff_soa.html" with soa_list=soa_list %}
<h2>Liste des enregistrements MX</h2>
{% if is_infra %}
{% can_create Mx %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-mx' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un enregistrement MX</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-mx' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un enregistrement MX</a>
{% endif %}
{% include "machines/aff_mx.html" with mx_list=mx_list %}
<h2>Liste des enregistrements NS</h2>
{% if is_infra %}
{% can_create Ns %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-ns' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un enregistrement NS</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-ns' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un enregistrement NS</a>
{% endif %}
{% include "machines/aff_ns.html" with ns_list=ns_list %}
<h2>Liste des enregistrements TXT</h2>
{% if is_infra %}
{% can_create Txt %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-txt' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un enregistrement TXT</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-txt' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un enregistrement TXT</a>
{% endif %}
{% include "machines/aff_txt.html" with txt_list=txt_list %}
<h2>Liste des enregistrements SRV</h2>
{% if is_infra %}
{% can_create Srv %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-srv' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un enregistrement SRV</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-srv' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un enregistrement SRV</a>
{% endif %}
{% include "machines/aff_srv.html" with srv_list=srv_list %}
<br />
<br />

View file

@ -25,14 +25,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load bootstrap3 %}
{% load acl %}
{% block title %}Ip{% endblock %}
{% block content %}
<h2>Liste des types d'ip</h2>
{% if is_infra %}
{% can_create IpType %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-iptype' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un type d'ip</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-iptype' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs types d'ip</a>
{% endif %}
{% include "machines/aff_iptype.html" with iptype_list=iptype_list %}
<br />
<br />

View file

@ -25,14 +25,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load bootstrap3 %}
{% load acl %}
{% block title %}Machines{% endblock %}
{% block content %}
<h2>Liste des types de machines</h2>
{% if is_infra %}
{% can_create MachineType %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-machinetype' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un type de machine</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-machinetype' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs types de machines</a>
{% endif %}
{% include "machines/aff_machinetype.html" with machinetype_list=machinetype_list %}
<br />
<br />

View file

@ -25,16 +25,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load bootstrap3 %}
{% load acl %}
{% block title %}Machines{% endblock %}
{% block content %}
<h2>Liste des nas</h2>
<h5>La correpondance nas-machinetype relie le type de nas à un type de machine.
Elle est utile pour l'autoenregistrement des macs par radius, et permet de choisir le type de machine à affecter aux machines en fonction du type de nas</h5>
{% if is_infra %}
{% can_create Nas %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-nas' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un type de nas</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-nas' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs types nas</a>
{% endif %}
{% include "machines/aff_nas.html" with nas_list=nas_list %}
<br />
<br />

View file

@ -2,11 +2,15 @@
{% load bootstrap3 %}
{% load acl %}
{% block title %}Configuration de ports{% endblock %}
{% block content %}
<h2>Liste des configurations de ports</h2>
{% can_create OuverturePortList %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-portlist' %}"><i class="glyphicon glyphicon-plus"></i>Ajouter une configuration</a>
{% acl_end %}
<table class="table table-striped">
<thead>
<tr>
@ -44,8 +48,12 @@
</div>
{% endif %}
<td class="text-right">
{% can_delete pl %}
{% include 'buttons/suppr.html' with href='machines:del-portlist' id=pl.id %}
{% acl_end %}
{% can_edit pl %}
{% include 'buttons/edit.html' with href='machines:edit-portlist' id=pl.id %}
{% acl_end %}
</td>
</tr>
{%endfor%}

View file

@ -24,15 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% block title %}Machines{% endblock %}
{% block content %}
<h2>Liste des services</h2>
{% if is_infra %}
{% can_create Service %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-service' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un service</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-service' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs service</a>
{% endif %}
{% include "machines/aff_service.html" with service_list=service_list %}
<h2>Etat des serveurs</h2>
{% include "machines/aff_servers.html" with servers_list=servers_list %}

View file

@ -25,14 +25,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load bootstrap3 %}
{% load acl %}
{% block title %}Machines{% endblock %}
{% block content %}
<h2>Liste des vlans</h2>
{% if is_infra %}
{% can_create Vlan %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-vlan' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un vlan</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-vlan' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs vlan</a>
{% endif %}
{% include "machines/aff_vlan.html" with vlan_list=vlan_list %}
<br />
<br />

View file

@ -23,42 +23,55 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
{% block sidebar %}
{% if is_cableur %}
{% can_view_all Machine %}
<a class="list-group-item list-group-item-info" href="{% url "machines:index" %}">
<i class="glyphicon glyphicon-list"></i>
Machines
</a>
{% acl_end %}
{% can_view_all MachineType %}
<a class="list-group-item list-group-item-info" href="{% url "machines:index-machinetype" %}">
<i class="glyphicon glyphicon-list"></i>
Types de machines
</a>
{% acl_end %}
{% can_view_all Extension %}
<a class="list-group-item list-group-item-info" href="{% url "machines:index-extension" %}">
<i class="glyphicon glyphicon-list"></i>
Extensions et zones
</a>
{% acl_end %}
{% can_view_all IpType %}
<a class="list-group-item list-group-item-info" href="{% url "machines:index-iptype" %}">
<i class="glyphicon glyphicon-list"></i>
Plages d'IP
</a>
{% acl_end %}
{% can_view_all Vlan %}
<a class="list-group-item list-group-item-info" href="{% url "machines:index-vlan" %}">
<i class="glyphicon glyphicon-list"></i>
Vlans
</a>
{% acl_end %}
{% can_view_all Nas %}
<a class="list-group-item list-group-item-info" href="{% url "machines:index-nas" %}">
<i class="glyphicon glyphicon-list"></i>
Gestion des nas
</a>
{% acl_end %}
{% can_view_all Service %}
<a class="list-group-item list-group-item-info" href="{% url "machines:index-service" %}">
<i class="glyphicon glyphicon-list"></i>
Services (dhcp, dns...)
</a>
{% endif %}
{% if is_cableur %}
{% acl_end %}
{% can_view_all OuverturePortList %}
<a class="list-group-item list-group-item-info" href="{% url "machines:index-portlist" %}">
<i class="glyphicon glyphicon-list"></i>
Ouverture de ports
</a>
{%endif%}
{% acl_end %}
{% endblock %}

View file

@ -24,7 +24,7 @@
from __future__ import unicode_literals
from django.conf.urls import url
import re2o
from . import views
urlpatterns = [
@ -61,7 +61,7 @@ urlpatterns = [
url(r'^del_srv/$', views.del_srv, name='del-srv'),
url(r'^index_extension/$', views.index_extension, name='index-extension'),
url(r'^add_alias/(?P<interfaceid>[0-9]+)$', views.add_alias, name='add-alias'),
url(r'^edit_alias/(?P<aliasid>[0-9]+)$', views.edit_alias, name='edit-alias'),
url(r'^edit_alias/(?P<domainid>[0-9]+)$', views.edit_alias, name='edit-alias'),
url(r'^del_alias/(?P<interfaceid>[0-9]+)$', views.del_alias, name='del-alias'),
url(r'^index_alias/(?P<interfaceid>[0-9]+)$', views.index_alias, name='index-alias'),
url(r'^add_service/$', views.add_service, name='add-service'),
@ -76,20 +76,12 @@ urlpatterns = [
url(r'^edit_nas/(?P<nasid>[0-9]+)$', views.edit_nas, name='edit-nas'),
url(r'^del_nas/$', views.del_nas, name='del-nas'),
url(r'^index_nas/$', views.index_nas, name='index-nas'),
url(r'^history/(?P<object>machine)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>interface)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>machinetype)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>extension)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>soa)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>mx)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>ns)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>txt)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>srv)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>iptype)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>alias)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>vlan)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>nas)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>service)/(?P<id>[0-9]+)$', views.history, name='history'),
url(
r'history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
re2o.views.history,
name='history',
kwargs={'application':'machines'},
),
url(r'^$', views.index, name='index'),
url(r'^rest/mac-ip/$', views.mac_ip, name='mac-ip'),
url(r'^rest/regen-achieved/$', views.regen_achieved, name='regen-achieved'),
@ -104,9 +96,9 @@ urlpatterns = [
url(r'^rest/service_servers/$', views.service_servers, name='service-servers'),
url(r'^rest/ouverture_ports/$', views.ouverture_ports, name='ouverture-ports'),
url(r'index_portlist/$', views.index_portlist, name='index-portlist'),
url(r'^edit_portlist/(?P<pk>[0-9]+)$', views.edit_portlist, name='edit-portlist'),
url(r'^del_portlist/(?P<pk>[0-9]+)$', views.del_portlist, name='del-portlist'),
url(r'^edit_portlist/(?P<ouvertureportlistid>[0-9]+)$', views.edit_portlist, name='edit-portlist'),
url(r'^del_portlist/(?P<ouvertureportlistid>[0-9]+)$', views.del_portlist, name='del-portlist'),
url(r'^add_portlist/$', views.add_portlist, name='add-portlist'),
url(r'^port_config/(?P<pk>[0-9]+)$', views.configure_ports, name='port-config'),
url(r'^port_config/(?P<interfaceid>[0-9]+)$', views.configure_ports, name='port-config'),
]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,2 @@
from .acl import *

40
preferences/acl.py Normal file
View file

@ -0,0 +1,40 @@
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""preferences.acl
Here are defined some functions to check acl on the application.
"""
def can_view(user):
"""Check if an user can view the application.
Args:
user: The user who wants to view the application.
Returns:
A couple (allowed, msg) where allowed is a boolean which is True if
viewing is granted and msg is a message (can be None).
"""
can = user.has_module_perms('preferences')
return can, None if can else "Vous ne pouvez pas voir cette application."

View file

@ -173,7 +173,15 @@ class ServiceForm(ModelForm):
class DelServiceForm(Form):
"""Suppression de services sur la page d'accueil"""
services = forms.ModelMultipleChoiceField(
queryset=Service.objects.all(),
queryset=Service.objects.none(),
label="Enregistrements service actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelServiceForm, self).__init__(*args, **kwargs)
if instances:
self.fields['services'].queryset = instances
else:
self.fields['services'].queryset = Service.objects.all()

View file

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 20:42
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('preferences', '0024_optionaluser_all_can_create'),
]
operations = [
migrations.AlterModelOptions(
name='assooption',
options={'permissions': (('view_assooption', "Peut voir les options de l'asso"),)},
),
migrations.AlterModelOptions(
name='generaloption',
options={'permissions': (('view_generaloption', 'Peut voir les options générales'),)},
),
migrations.AlterModelOptions(
name='mailmessageoption',
options={'permissions': (('view_mailmessageoption', 'Peut voir les options de mail'),)},
),
migrations.AlterModelOptions(
name='optionalmachine',
options={'permissions': (('view_optionalmachine', 'Peut voir les options de machine'),)},
),
migrations.AlterModelOptions(
name='optionaltopologie',
options={'permissions': (('view_optionaltopologie', 'Peut voir les options de topologie'),)},
),
migrations.AlterModelOptions(
name='optionaluser',
options={'permissions': (('view_optionaluser', "Peut voir les options de l'user"),)},
),
migrations.AlterModelOptions(
name='service',
options={'permissions': (('view_service', 'Peut voir les options de service'),)},
),
]

View file

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-06 19:19
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('preferences', '0025_auto_20171231_2142'),
('preferences', '0026_auto_20171216_0401'),
]
operations = [
]

View file

@ -26,7 +26,7 @@ Reglages généraux, machines, utilisateurs, mail, general pour l'application.
from __future__ import unicode_literals
from django.db import models
from cotisations.models import Paiement
import cotisations.models
class OptionalUser(models.Model):
@ -47,10 +47,67 @@ class OptionalUser(models.Model):
help_text="Tous les users peuvent en créer d'autres",
)
class Meta:
permissions = (
("view_optionaluser", "Peut voir les options de l'user"),
)
def get_instance(*args, **kwargs):
return OptionalUser.objects.get_or_create()
def can_create(user_request, *args, **kwargs):
"""Check if an user can create a OptionalUser object.
:param user_request: The user who wants to create a user object.
:return: a message and a boolean which is True if the user can create.
"""
return user_request.has_perm('preferences.add_optionaluser'), u"Vous n'avez pas le droit\
de créer les préférences concernant les users"
def can_edit(self, user_request, *args, **kwargs):
"""Check if an user can edit a OptionalUser object.
:param self: The OptionalUser which is to be edited.
:param user_request: The user who requests to edit self.
:return: a message and a boolean which is True if edition is granted.
"""
return user_request.has_perm('preferences.change_optionaluser'), u"Vous n'avez pas le droit\
d'éditer les préférences concernant les users"
def can_delete(self, user_request, *args, **kwargs):
"""Check if an user can delete a OptionalUser object.
:param self: The OptionalUser which is to be deleted.
:param user_request: The user who requests deletion.
:return: True if deletion is granted, and a message.
"""
return user_request.has_perm('preferences.delete_optionaluser'), u"Vous n'avez pas le droit\
de supprimer les préférences concernant les users"
def can_view_all(user_request, *args, **kwargs):
"""Check if an user can access to the list of every OptionalUser objects
:param user_request: The user who wants to view the list.
:return: True if the user can view the list and an explanation message.
"""
return user_request.has_perm('preferences.view_optionaluser'), u"Vous n'avez pas le droit\
de voir les préférences concernant les utilisateurs"
def can_view(self, user_request, *args, **kwargs):
"""Check if an user can view a OptionalUser object.
:param self: The targeted OptionalUser.
:param user_request: The user who ask for viewing the target.
:return: A boolean telling if the acces is granted and an explanation
text
"""
return user_request.has_perm('preferences.view_optionaluser'), u"Vous n'avez pas le droit\
de voir les préférences concernant les utilisateurs"
def clean(self):
"""Creation du mode de paiement par solde"""
if self.user_solde:
Paiement.objects.get_or_create(moyen="Solde")
cotisations.models.Paiement.objects.get_or_create(moyen="Solde")
class OptionalMachine(models.Model):
@ -63,6 +120,64 @@ class OptionalMachine(models.Model):
max_lambdauser_aliases = models.IntegerField(default=10)
ipv6 = models.BooleanField(default=False)
class Meta:
permissions = (
("view_optionalmachine", "Peut voir les options de machine"),
)
def get_instance(*args, **kwargs):
return OptionalMachine.objects.get_or_create()
def can_create(user_request, *args, **kwargs):
"""Check if an user can create a OptionalMachine object.
:param user_request: The user who wants to create an object.
:return: a message and a boolean which is True if the user can create.
"""
return user_request.has_perm('preferences.add_optionalmachine'), u"Vous n'avez pas le droit\
de créer les préférences concernant les machines"
def can_edit(self, user_request, *args, **kwargs):
"""Check if an user can edit a OptionalMachine object.
:param self: The OptionalMachine which is to be edited.
:param user_request: The user who requests to edit self.
:return: a message and a boolean which is True if edition is granted.
"""
return user_request.has_perm('preferences.change_optionalmachine'), u"Vous n'avez pas le droit\
d'éditer les préférences concernant les machines"
def can_delete(self, user_request, *args, **kwargs):
"""Check if an user can delete a OptionalMachine object.
:param self: The OptionalMachine which is to be deleted.
:param user_request: The user who requests deletion.
:return: True if deletion is granted, and a message.
"""
return user_request.has_perm('preferences.delete_optionalmachine'), u"Vous n'avez pas le droit\
de supprimer les préférences concernant les machines"
def can_view_all(user_request, *args, **kwargs):
"""Check if an user can access to the list of every OptionalMachine objects
:param user_request: The user who wants to view the list.
:return: True if the user can view the list and an explanation message.
"""
return user_request.has_perm('preferences.view_optionalmachine'), u"Vous n'avez pas le droit\
de voir les préférences concernant les machines"
def can_view(self, user_request, *args, **kwargs):
"""Check if an user can view a OptionalMachine object.
:param self: The targeted OptionalMachine.
:param user_request: The user who ask for viewing the target.
:return: A boolean telling if the acces is granted and an explanation
text
"""
return user_request.has_perm('preferences.view_optionalmachine'), u"Vous n'avez pas le droit\
de voir les préférences concernant les machines"
class OptionalTopologie(models.Model):
"""Reglages pour la topologie : mode d'accès radius, vlan où placer
@ -96,6 +211,63 @@ class OptionalTopologie(models.Model):
null=True
)
class Meta:
permissions = (
("view_optionaltopologie", "Peut voir les options de topologie"),
)
def get_instance(*args, **kwargs):
return OptionalTopologie.objects.get_or_create()
def can_create(user_request, *args, **kwargs):
"""Check if an user can create a OptionalTopologie object.
:param user_request: The user who wants to create an object.
:return: a message and a boolean which is True if the user can create.
"""
return user_request.has_perm('preferences.add_optionaltopologie'), u"Vous n'avez pas le droit\
de créer les préférences concernant la topologie"
def can_edit(self, user_request, *args, **kwargs):
"""Check if an user can edit a OptionalTopologie object.
:param self: The OptionalTopologie which is to be edited.
:param user_request: The user who requests to edit self.
:return: a message and a boolean which is True if edition is granted.
"""
return user_request.has_perm('preferences.change_optionaltopologie'), u"Vous n'avez pas le droit\
d'éditer les préférences concernant la topologie"
def can_delete(self, user_request, *args, **kwargs):
"""Check if an user can delete a OptionalTopologie object.
:param self: The OptionalTopologie which is to be deleted.
:param user_request: The user who requests deletion.
:return: True if deletion is granted, and a message.
"""
return user_request.has_perm('preferences.delete_optionaltoplogie'), u"Vous n'avez pas le droit\
d'éditer les préférences concernant la topologie"
def can_view_all(user_request, *args, **kwargs):
"""Check if an user can access to the list of every OptionalTopologie objects
:param user_request: The user who wants to view the list.
:return: True if the user can view the list and an explanation message.
"""
return user_request.has_perm('preferences.view_optionaltopologie'), u"Vous n'avez pas le droit\
de voir les préférences concernant la topologie"
def can_view(self, user_request, *args, **kwargs):
"""Check if an user can view a OptionalTopologie object.
:param self: The targeted OptionalTopologie.
:param user_request: The user who ask for viewing the target.
:return: A boolean telling if the acces is granted and an explanation
text
"""
return user_request.has_perm('preferences.view_optionaltopologie'), u"Vous n'avez pas le droit\
de voir les préférences concernant la topologie"
class GeneralOption(models.Model):
"""Options générales : nombre de resultats par page, nom du site,
@ -114,6 +286,64 @@ class GeneralOption(models.Model):
site_name = models.CharField(max_length=32, default="Re2o")
email_from = models.EmailField(default="www-data@serveur.net")
class Meta:
permissions = (
("view_generaloption", "Peut voir les options générales"),
)
def get_instance(*args, **kwargs):
return GeneralOption.objects.get_or_create()
def can_create(user_request, *args, **kwargs):
"""Check if an user can create a GeneralOption object.
:param user_request: The user who wants to create an object.
:return: a message and a boolean which is True if the user can create.
"""
return user_request.has_perm('preferences.add_generaloption'), u"Vous n'avez pas le droit\
de créer les préférences générales"
def can_edit(self, user_request, *args, **kwargs):
"""Check if an user can edit a GeneralOption object.
:param self: The GeneralOption which is to be edited.
:param user_request: The user who requests to edit self.
:return: a message and a boolean which is True if edition is granted.
"""
return user_request.has_perm('preferences.change_generaloption'), u"Vous n'avez pas le droit\
d'éditer les préférences générales"
def can_delete(self, user_request, *args, **kwargs):
"""Check if an user can delete a GeneralOption object.
:param self: The GeneralOption which is to be deleted.
:param user_request: The user who requests deletion.
:return: True if deletion is granted, and a message.
"""
return user_request.has_perm('preferences.delete_generaloption'), u"Vous n'avez pas le droit\
d'éditer les préférences générales"
def can_view_all(user_request, *args, **kwargs):
"""Check if an user can access to the list of every GeneralOption objects
:param user_request: The user who wants to view the list.
:return: True if the user can view the list and an explanation message.
"""
return user_request.has_perm('preferences.view_generaloption'), u"Vous n'avez pas le droit\
de voir les préférences générales"
def can_view(self, user_request, *args, **kwargs):
"""Check if an user can view a GeneralOption object.
:param self: The targeted GeneralOption.
:param user_request: The user who ask for viewing the target.
:return: A boolean telling if the acces is granted and an explanation
text
"""
return user_request.has_perm('preferences.view_generaloption'), u"Vous n'avez pas le droit\
de voir les préférences générales"
class Service(models.Model):
"""Liste des services affichés sur la page d'accueil : url, description,
@ -123,6 +353,65 @@ class Service(models.Model):
description = models.TextField()
image = models.ImageField(upload_to='logo', blank=True)
class Meta:
permissions = (
("view_service", "Peut voir les options de service"),
)
def get_instance(serviceid, *args, **kwargs):
return Service.objects.get(pk=serviceid)
def can_create(user_request, *args, **kwargs):
"""Check if an user can create a Service object.
:param user_request: The user who wants to create an object.
:return: a message and a boolean which is True if the user can create.
"""
return user_request.has_perm('preferences.add_service'), u"Vous n'avez pas le droit\
de créer un service pour la page d'accueil"
def can_edit(self, user_request, *args, **kwargs):
"""Check if an user can edit a Service object.
:param self: The Service which is to be edited.
:param user_request: The user who requests to edit self.
:return: a message and a boolean which is True if edition is granted.
"""
return user_request.has_perm('preferences.change_service'), u"Vous n'avez pas le droit\
d'éditer les services pour la page d'accueil"
def can_delete(self, user_request, *args, **kwargs):
"""Check if an user can delete a Service object.
:param self: The Right which is to be deleted.
:param user_request: The user who requests deletion.
:return: True if deletion is granted, and a message.
"""
return user_request.has_perm('preferences.delete_service'), u"Vous n'avez pas le droit\
de supprimer les services pour la page d'accueil"
def can_view_all(user_request, *args, **kwargs):
"""Check if an user can access to the list of every Service objects
:param user_request: The user who wants to view the list.
:return: True if the user can view the list and an explanation message.
"""
return user_request.has_perm('preferences.view_service'), u"Vous n'avez pas le droit\
de voir les services pour la page d'accueil"
def can_view(self, user_request, *args, **kwargs):
"""Check if an user can view a Service object.
:param self: The targeted Service.
:param user_request: The user who ask for viewing the target.
:return: A boolean telling if the acces is granted and an explanation
text
"""
return user_request.has_perm('preferences.view_service'), u"Vous n'avez pas le droit\
de voir les services pour la page d'accueil"
def __str__(self):
return str(self.name)
@ -148,6 +437,63 @@ class AssoOption(models.Model):
null=True
)
class Meta:
permissions = (
("view_assooption", "Peut voir les options de l'asso"),
)
def get_instance(*args, **kwargs):
return AssoOption.objects.get_or_create()
def can_create(user_request, *args, **kwargs):
"""Check if an user can create a AssoOption object.
:param user_request: The user who wants to create an object.
:return: a message and a boolean which is True if the user can create.
"""
return user_request.has_perm('preferences.add_assooption'), u"Vous n'avez pas le droit\
d'éditer les préférences concernant l'association"
def can_edit(self, user_request, *args, **kwargs):
"""Check if an user can edit a AssoOption object.
:param self: The AssoOption which is to be edited.
:param user_request: The user who requests to edit self.
:return: a message and a boolean which is True if edition is granted.
"""
return user_request.has_perm('preferences.change_assooption'), u"Vous n'avez pas le droit\
d'éditer les préférences concernant l'association"
def can_delete(self, user_request, *args, **kwargs):
"""Check if an user can delete a AssoOption object.
:param self: The AssoOption which is to be deleted.
:param user_request: The user who requests deletion.
:return: True if deletion is granted, and a message.
"""
return user_request.has_perm('preferences.delete_assooption'), u"Vous n'avez pas le droit\
d'éditer les préférences concernant l'association"
def can_view_all(user_request, *args, **kwargs):
"""Check if an user can access to the list of every AssoOption objects
:param user_request: The user who wants to view the list.
:return: True if the user can view the list and an explanation message.
"""
return user_request.has_perm('preferences.view_assooption'), u"Vous n'avez pas le droit\
de voir les préférences concernant l'association"
def can_view(self, user_request, *args, **kwargs):
"""Check if an user can view a AssoOption object.
:param self: The targeted AssoOption.
:param user_request: The user who ask for viewing the target.
:return: A boolean telling if the acces is granted and an explanation
text
"""
return user_request.has_perm('preferences.view_assooption'), u"Vous n'avez pas le droit\
de voir les préférences concernant l'association"
class MailMessageOption(models.Model):
"""Reglages, mail de bienvenue et autre"""
@ -155,3 +501,61 @@ class MailMessageOption(models.Model):
welcome_mail_fr = models.TextField(default="")
welcome_mail_en = models.TextField(default="")
class Meta:
permissions = (
("view_mailmessageoption", "Peut voir les options de mail"),
)
def get_instance(*args, **kwargs):
return MailMessageOption.objects.get_or_create()
def can_create(user_request, *args, **kwargs):
"""Check if an user can create a MailMessageOption object.
:param user_request: The user who wants to create an object.
:return: a message and a boolean which is True if the user can create.
"""
return user_request.has_perm('preferences.add_mailmessageoption'), u"Vous n'avez pas le droit\
d'éditer les préférences concernant les mails"
def can_edit(self, user_request, *args, **kwargs):
"""Check if an user can edit a MailMessageOption object.
:param self: The MailMessageOption which is to be edited.
:param user_request: The user who requests to edit self.
:return: a message and a boolean which is True if edition is granted.
"""
return user_request.has_perm('preferences.change_mailmessageoption'), u"Vous n'avez pas le droit\
d'éditer les préférences concernant les mails"
def can_delete(self, user_request, *args, **kwargs):
"""Check if an user can delete a AssoOption object.
:param self: The AssoOption which is to be deleted.
:param user_request: The user who requests deletion.
:return: True if deletion is granted, and a message.
"""
return user_request.has_perm('preferences.delete_mailmessageoption'), u"Vous n'avez pas le droit\
d'éditer les préférences concernant les mails"
def can_view_all(user_request, *args, **kwargs):
"""Check if an user can access to the list of every AssoOption objects
:param user_request: The user who wants to view the list.
:return: True if the user can view the list and an explanation message.
"""
return user_request.has_perm('preferences.view_mailmessageoption'), u"Vous n'avez pas le droit\
de voir les préférences concernant les mails"
def can_view(self, user_request, *args, **kwargs):
"""Check if an user can view a AssoOption object.
:param self: The targeted AssoOption.
:param user_request: The user who ask for viewing the target.
:return: A boolean telling if the acces is granted and an explanation
text
"""
return user_request.has_perm('preferences.view_mailmessageoption'), u"Vous n'avez pas le droit\
de voir les préférences concernant les mails"

View file

@ -21,7 +21,7 @@ You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -40,9 +40,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ service.description }}</td>
<td>{{ service.image }}</td>
<td class="text-right">
{% if is_admin %}
{% include 'buttons/edit.html' with href='preferences:edit-services' id=service.id %}
{% endif %}
{% can_edit service%}
{% include 'buttons/edit.html' with href='preferences:edit-service' id=service.id %}
{% acl_end %}
{% include 'buttons/history.html' with href='preferences:history' name='service' id=service.id %}
</td>
</tr>

View file

@ -24,17 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% block title %}Création et modification des préférences{% endblock %}
{% block content %}
<h4>Préférences utilisateur</h4>
{% if is_bureau %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'OptionalUser' %}">
<i class="glyphicon glyphicon-edit"></i>
Editer
</a>
{% endif %}
<p>
</p>
<table class="table table-striped">
@ -58,12 +57,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</tr>
</table>
<h4>Préférences machines</h4>
{% if is_bureau %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'OptionalMachine' %}">
<i class="glyphicon glyphicon-edit"></i>
Editer
</a>
{% endif %}
<p>
</p>
<table class="table table-striped">
@ -81,12 +78,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</tr>
</table>
<h4>Préférences topologie</h4>
{% if is_bureau %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'OptionalTopologie' %}">
<i class="glyphicon glyphicon-edit"></i>
Editer
</a>
{% endif %}
<p>
</p>
<table class="table table-striped">
@ -104,12 +99,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</tr>
</table>
<h4>Préférences generales</h4>
{% if is_bureau %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'GeneralOption' %}">
<i class="glyphicon glyphicon-edit"></i>
Editer
</a>
{% endif %}
<p>
</p>
<table class="table table-striped">
@ -137,12 +130,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<tr>
</table>
<h4>Données de l'association</h4>
{% if is_bureau %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'AssoOption' %}">
<i class="glyphicon glyphicon-edit"></i>
Editer
</a>
{% endif %}
<p>
</p>
<table class="table table-striped">
@ -171,12 +162,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</tr>
</table>
<h4>Messages personalisé dans les mails</h4>
{% if is_bureau %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'MailMessageOption' %}">
<i class="glyphicon glyphicon-edit"></i>
Editer
</a>
{% endif %}
<p>
</p>
<table class="table table-striped">
@ -190,10 +179,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</tr>
</table>
<h2>Liste des services page d'accueil</h2>
{% if is_infra %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:add-services' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un service</a>
{% can_create Service%}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:add-service' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un service</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'preferences:del-services' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs service</a>
{% endif %}
{% include "preferences/aff_service.html" with service_list=service_list %}
<br />
<br />

View file

@ -28,6 +28,7 @@ from __future__ import unicode_literals
from django.conf.urls import url
from . import views
import re2o
urlpatterns = [
@ -61,17 +62,18 @@ urlpatterns = [
views.edit_options,
name='edit-options'
),
url(r'^add_services/$', views.add_services, name='add-services'),
url(r'^add_service/$', views.add_service, name='add-service'),
url(
r'^edit_services/(?P<servicesid>[0-9]+)$',
views.edit_services,
name='edit-services'
r'^edit_service/(?P<serviceid>[0-9]+)$',
views.edit_service,
name='edit-service'
),
url(r'^del_services/$', views.del_services, name='del-services'),
url(
r'^history/(?P<object_name>service)/(?P<object_id>[0-9]+)$',
views.history,
name='history'
r'^history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
re2o.views.history,
name='history',
kwargs={'application':'preferences'},
),
url(r'^$', views.display_options, name='display-options'),
]

View file

@ -42,6 +42,7 @@ from reversion.models import Version
from reversion import revisions as reversion
from re2o.views import form
from re2o.acl import can_create, can_edit, can_delete_set, can_view_all
from .forms import ServiceForm, DelServiceForm
from .models import Service, OptionalUser, OptionalMachine, AssoOption
from .models import MailMessageOption, GeneralOption, OptionalTopologie
@ -50,7 +51,12 @@ from . import forms
@login_required
@permission_required('cableur')
@can_view_all(OptionalUser)
@can_view_all(OptionalMachine)
@can_view_all(OptionalTopologie)
@can_view_all(GeneralOption)
@can_view_all(AssoOption)
@can_view_all(MailMessageOption)
def display_options(request):
"""Vue pour affichage des options (en vrac) classé selon les models
correspondants dans un tableau"""
@ -80,6 +86,11 @@ def edit_options(request, section):
form_instance = getattr(forms, 'Edit' + section + 'Form', None)
if model and form:
options_instance, _created = model.objects.get_or_create()
can, msg = options_instance.can_edit(request.user)
if not can:
messages.error(request, msg or "Vous ne pouvez pas éditer cette\
option.")
return redirect('/')
options = form_instance(
request.POST or None,
instance=options_instance
@ -106,57 +117,52 @@ def edit_options(request, section):
@login_required
@permission_required('admin')
def add_services(request):
@can_create(Service)
def add_service(request):
"""Ajout d'un service de la page d'accueil"""
services = ServiceForm(request.POST or None)
if services.is_valid():
service = ServiceForm(request.POST or None)
if service.is_valid():
with transaction.atomic(), reversion.create_revision():
services.save()
service.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Ce service a été ajouté")
return redirect(reverse('preferences:display-options'))
return form(
{'preferenceform': services},
{'preferenceform': service},
'preferences/preferences.html',
request
)
@login_required
@permission_required('admin')
def edit_services(request, servicesid):
@can_edit(Service)
def edit_service(request, service_instance, serviceid):
"""Edition des services affichés sur la page d'accueil"""
try:
services_instance = Service.objects.get(pk=servicesid)
except Service.DoesNotExist:
messages.error(request, u"Entrée inexistante")
return redirect(reverse('preferences:display-options'))
services = ServiceForm(request.POST or None, instance=services_instance)
if services.is_valid():
service = ServiceForm(request.POST or None, instance=service_instance)
if service.is_valid():
with transaction.atomic(), reversion.create_revision():
services.save()
service.save()
reversion.set_user(request.user)
reversion.set_comment(
"Champs modifié(s) : %s" % ', '.join(
field for field in services.changed_data
field for field in service.changed_data
)
)
messages.success(request, "Service modifié")
return redirect(reverse('preferences:display-options'))
return form(
{'preferenceform': services},
{'preferenceform': service},
'preferences/preferences.html',
request
)
@login_required
@permission_required('admin')
def del_services(request):
@can_delete_set(Service)
def del_services(request, instances):
"""Suppression d'un service de la page d'accueil"""
services = DelServiceForm(request.POST or None)
services = DelServiceForm(request.POST or None, instances=instances)
if services.is_valid():
services_dels = services.cleaned_data['services']
for services_del in services_dels:
@ -164,7 +170,7 @@ def del_services(request):
with transaction.atomic(), reversion.create_revision():
services_del.delete()
reversion.set_user(request.user)
messages.success(request, "Le services a été supprimée")
messages.success(request, "Le service a été supprimée")
except ProtectedError:
messages.error(request, "Erreur le service\
suivant %s ne peut être supprimé" % services_del)
@ -174,33 +180,3 @@ def del_services(request):
'preferences/preferences.html',
request
)
@login_required
@permission_required('cableur')
def history(request, object_name, object_id):
"""Historique de creation et de modification d'un service affiché sur
la page d'accueil"""
if object_name == 'service':
try:
object_instance = Service.objects.get(pk=object_id)
except Service.DoesNotExist:
messages.error(request, "Service inexistant")
return redirect(reverse('preferences:display-options'))
options, _created = GeneralOption.objects.get_or_create()
pagination_number = options.pagination_number
reversions = Version.objects.get_for_object(object_instance)
paginator = Paginator(reversions, pagination_number)
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 results.
reversions = paginator.page(paginator.num_pages)
return render(request, 're2o/history.html', {
'reversions': reversions,
'object': object_instance
})

235
re2o/acl.py Normal file
View file

@ -0,0 +1,235 @@
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Handles ACL for re2o.
Here are defined some decorators that can be used in views to handle ACL.
"""
from __future__ import unicode_literals
import sys
from django.contrib import messages
from django.shortcuts import redirect
from django.urls import reverse
import cotisations, logs, machines, preferences, search, topologie, users
def can_create(model):
"""Decorator to check if an user can create a model.
It assumes that a valid user exists in the request and that the model has a
method can_create(user) which returns true if the user can create this kind
of models.
"""
def decorator(view):
def wrapper(request, *args, **kwargs):
can, msg = model.can_create(request.user, *args, **kwargs)
if not can:
messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil',
kwargs={'userid':str(request.user.id)}
))
return view(request, *args, **kwargs)
return wrapper
return decorator
def can_edit(model, *field_list):
"""Decorator to check if an user can edit a model.
It tries to get an instance of the model, using
`model.get_instance(*args, **kwargs)` and assumes that the model has a
method `can_edit(user)` which returns `true` if the user can edit this
kind of models.
"""
def decorator(view):
def wrapper(request, *args, **kwargs):
try:
instance = model.get_instance(*args, **kwargs)
except model.DoesNotExist:
messages.error(request, u"Entrée inexistante")
return redirect(reverse('users:profil',
kwargs={'userid':str(request.user.id)}
))
can, msg = instance.can_edit(request.user)
if not can:
messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil',
kwargs={'userid':str(request.user.id)}
))
for field in field_list:
can_change = getattr(instance, 'can_change_' + field)
can, msg = can_change(request.user, *args, **kwargs)
if not can:
messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil',
kwargs={'userid':str(request.user.id)}
))
return view(request, instance, *args, **kwargs)
return wrapper
return decorator
def can_change(model, *field_list):
"""Decorator to check if an user can edit a field of a model class.
Difference with can_edit : take a class and not an instance
"""
def decorator(view):
def wrapper(request, *args, **kwargs):
for field in field_list:
can_change = getattr(model, 'can_change_' + field)
can, msg = can_change(request.user, *args, **kwargs)
if not can:
messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil',
kwargs={'userid':str(request.user.id)}
))
return view(request, *args, **kwargs)
return wrapper
return decorator
def can_delete(model):
"""Decorator to check if an user can delete a model.
It tries to get an instance of the model, using
`model.get_instance(*args, **kwargs)` and assumes that the model has a
method `can_delete(user)` which returns `true` if the user can delete this
kind of models.
"""
def decorator(view):
def wrapper(request, *args, **kwargs):
try:
instance = model.get_instance(*args, **kwargs)
except model.DoesNotExist:
messages.error(request, u"Entrée inexistante")
return redirect(reverse('users:profil',
kwargs={'userid':str(request.user.id)}
))
can, msg = instance.can_delete(request.user)
if not can:
messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil',
kwargs={'userid':str(request.user.id)}
))
return view(request, instance, *args, **kwargs)
return wrapper
return decorator
def can_delete_set(model):
"""Decorator which returns a list of detable models by request user.
If none of them, return an error"""
def decorator(view):
def wrapper(request, *args, **kwargs):
all_objects = model.objects.all()
instances_id = []
for instance in all_objects:
can, msg = instance.can_delete(request.user)
if can:
instances_id.append(instance.id)
instances = model.objects.filter(id__in=instances_id)
if not instances:
messages.error(request, "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil',
kwargs={'userid':str(request.user.id)}
))
return view(request, instances, *args, **kwargs)
return wrapper
return decorator
def can_view(model):
"""Decorator to check if an user can view a model.
It tries to get an instance of the model, using
`model.get_instance(*args, **kwargs)` and assumes that the model has a
method `can_view(user)` which returns `true` if the user can view this
kind of models.
"""
def decorator(view):
def wrapper(request, *args, **kwargs):
try:
instance = model.get_instance(*args, **kwargs)
except model.DoesNotExist:
messages.error(request, u"Entrée inexistante")
return redirect(reverse('users:profil',
kwargs={'userid':str(request.user.id)}
))
can, msg = instance.can_view(request.user)
if not can:
messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil',
kwargs={'userid':str(request.user.id)}
))
return view(request, instance, *args, **kwargs)
return wrapper
return decorator
def can_view_all(model):
"""Decorator to check if an user can view a class of model.
"""
def decorator(view):
def wrapper(request, *args, **kwargs):
can, msg = model.can_view_all(request.user)
if not can:
messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil',
kwargs={'userid':str(request.user.id)}
))
return view(request, *args, **kwargs)
return wrapper
return decorator
def can_view_app(app_name):
"""Decorator to check if an user can view an application.
"""
assert app_name in sys.modules.keys()
def decorator(view):
def wrapper(request, *args, **kwargs):
app = sys.modules[app_name]
can,msg = app.can_view(request.user)
if can:
return view(request, *args, **kwargs)
messages.error(request, msg)
return redirect(reverse('users:profil',
kwargs={'userid':str(request.user.id)}
))
return wrapper
return decorator
def can_edit_history(view):
"""Decorator to check if an user can edit history."""
def wrapper(request, *args, **kwargs):
if request.user.has_perm('admin.change_logentry'):
return view(request, *args, **kwargs)
messages.error(
request,
"Vous ne pouvez pas éditer l'historique."
)
return redirect(reverse('users:profil',
kwargs={'userid':str(request.user.id)}
))
return wrapper

View file

@ -39,28 +39,10 @@ def context_user(request):
messages.warning(request, global_message)
if user.is_authenticated():
interfaces = user.user_interfaces()
is_cableur = user.is_cableur
is_bureau = user.is_bureau
is_bofh = user.is_bofh
is_trez = user.is_trez
is_infra = user.is_infra
is_admin = user.is_admin
else:
interfaces = None
is_cableur = False
is_bureau = False
is_bofh = False
is_trez = False
is_infra = False
is_admin = False
return {
'request_user': user,
'is_cableur': is_cableur,
'is_bureau': is_bureau,
'is_bofh': is_bofh,
'is_trez': is_trez,
'is_infra': is_infra,
'is_admin': is_admin,
'interfaces': interfaces,
'site_name': general_options.site_name,
'ipv6_enabled': machine_options.ipv6,

79
re2o/field_permissions.py Normal file
View file

@ -0,0 +1,79 @@
from django.db import models
from django import forms
from functools import partial
class FieldPermissionModelMixin:
field_permissions = {} # {'field_name': callable}
FIELD_PERM_CODENAME = 'can_change_{model}_{name}'
FIELD_PERMISSION_GETTER = 'can_change_{name}'
FIELD_PERMISSION_MISSING_DEFAULT = True
def has_field_perm(self, user, field):
if field in self.field_permissions:
checks = self.field_permissions[field]
if not isinstance(checks, (list, tuple)):
checks = [checks]
else:
checks = []
# Consult the optional field-specific hook.
getter_name = self.FIELD_PERMISSION_GETTER.format(name=field)
if hasattr(self, getter_name):
checks.append(getattr(self, getter_name))
# Try to find a static permission for the field
else:
perm_label = self.FIELD_PERM_CODENAME.format(**{
'model': self._meta.model_name,
'name': field,
})
if perm_label in dict(self._meta.permissions):
checks.append(perm_label)
# No requirements means no restrictions.
if not len(checks):
return self.FIELD_PERMISSION_MISSING_DEFAULT
# Try to find a user setting that qualifies them for permission.
for perm in checks:
if callable(perm):
result, reason = perm(user_request=user)
if result is not None:
return result
else:
result = user.has_perm(perm) # Don't supply 'obj', or else infinite recursion.
if result:
return True
# If no requirement can be met, then permission is denied.
return False
class FieldPermissionModel(FieldPermissionModelMixin, models.Model):
class Meta:
abstract = True
class FieldPermissionFormMixin:
"""
Construit le formulaire et retire les champs interdits
"""
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(FieldPermissionFormMixin, self).__init__(*args, **kwargs)
to_be_deleted = []
for name in self.fields:
if not self.instance.has_field_perm(user, field=name):
to_be_deleted.append(name)
for name in to_be_deleted:
self.remove_unauthorized_field(name)
def remove_unauthorized_field(self, name):
del self.fields[name]
class FieldPermissionForm(FieldPermissionFormMixin, forms.ModelForm):
pass

View file

@ -29,7 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% block title %}Accueil{% endblock %}
{% block content %}
<h1>Bienvenue sur {{ site_name }} !</h1>
<h1>Bienvenue sur {{ request.get_host }} !</h1>
<div class="row">
{% for service_list in services_urls %}

410
re2o/templatetags/acl.py Normal file
View file

@ -0,0 +1,410 @@
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Maël Kervella
#
# 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.
"""
Set of templatetags for using acl in templates:
- can_create (model)
- cannot_create (model)
- can_edit (instance)
- cannot_edit (instance)
Some templatetags require a model to calculate the acl while others are need
an instance of a model (either Model.can_xxx or instance.can_xxx)
**Parameters**:
model_name or instance - Either the model_name (if templatetag is based on
model) or an instantiated object (if templatetag is base on instance)
that needs to be checked for the current user
args - Any other argument that is interpreted as a python object and passed
to the acl function (can_xxx)
**Usage**:
{% <acl_name> <obj> [arg1 [arg2 [...]]]%}
<template stuff>
[{% acl_else %}
<template stuff>]
{% acl_end %}
where <acl_name> is one of the templatetag names available
(can_xxx or cannot_xxx)
**Example**:
{% can_create Machine targeted_user %}
<p>I'm authorized to create new machines.models.for this guy \\o/</p>
{% acl_else %}
<p>Why can't I create a little machine for this guy ? :(</p>
{% acl_end %}
{% can_edit user %}
<p>Oh I can edit myself oO</p>
{% acl_else %}
<p>Sniff can't edit my own infos ...</p>
{% acl_end %}
**How to modify**:
To add a new acl function (can_xxx or cannot_xxx),
- if it's based on a model (like can_create), add an entry in
'get_callback' and register your tag with the other ones juste before
'acl_model_generic' definition
- if it's bases on an instance (like can_edit), just register yout tag with
the other ones juste before 'acl_instance_generic' definition
To add support for a new model, add an entry in 'get_model' and be sure
the acl function exists in the model definition
"""
import sys
from django import template
from django.template.base import Node, NodeList
import cotisations
import machines
import preferences
import topologie
import users
register = template.Library()
MODEL_NAME = {
# cotisations
'Facture' : cotisations.models.Facture,
'Vente' : cotisations.models.Vente,
'Article' : cotisations.models.Article,
'Banque' : cotisations.models.Banque,
'Paiement' : cotisations.models.Paiement,
'Cotisation' : cotisations.models.Cotisation,
# machines
'Machine' : machines.models.Machine,
'MachineType' : machines.models.MachineType,
'IpType' : machines.models.IpType,
'Vlan' : machines.models.Vlan,
'Nas' : machines.models.Nas,
'SOA' : machines.models.SOA,
'Extension' : machines.models.Extension,
'Mx' : machines.models.Mx,
'Ns' : machines.models.Ns,
'Txt' : machines.models.Txt,
'Srv' : machines.models.Srv,
'Interface' : machines.models.Interface,
'Domain' : machines.models.Domain,
'IpList' : machines.models.IpList,
'Service' : machines.models.Service,
'Service_link' : machines.models.Service_link,
'OuverturePortList' : machines.models.OuverturePortList,
'OuverturePort' : machines.models.OuverturePort,
# preferences
'OptionalUser': preferences.models.OptionalUser,
'OptionalMachine': preferences.models.OptionalMachine,
'OptionalTopologie': preferences.models.OptionalTopologie,
'GeneralOption': preferences.models.GeneralOption,
'Service': preferences.models.Service,
'AssoOption': preferences.models.AssoOption,
'MailMessageOption': preferences.models.MailMessageOption,
# topologie
'Stack' : topologie.models.Stack,
'Switch' : topologie.models.Switch,
'ModelSwitch' : topologie.models.ModelSwitch,
'ConstructorSwitch' : topologie.models.ConstructorSwitch,
'Port' : topologie.models.Port,
'Room' : topologie.models.Room,
# users
'User' : users.models.User,
'Adherent' : users.models.Adherent,
'Club' : users.models.Club,
'ServiceUser' : users.models.ServiceUser,
'School' : users.models.School,
'ListRight' : users.models.ListRight,
'Ban' : users.models.Ban,
'Whitelist' : users.models.Whitelist,
}
def get_model(model_name):
"""Retrieve the model object from its name"""
try:
return MODEL_NAME[model_name]
except KeyError:
raise template.TemplateSyntaxError(
"%r is not a valid model for an acl tag" % model_name
)
def get_callback(tag_name, obj=None):
"""Return the right function to call back to check for acl"""
if tag_name == 'can_create':
return acl_fct(obj.can_create, False)
if tag_name == 'cannot_create':
return acl_fct(obj.can_create, True)
if tag_name == 'can_edit':
return acl_fct(obj.can_edit, False)
if tag_name == 'cannot_edit':
return acl_fct(obj.can_edit, True)
if tag_name == 'can_edit_all':
return acl_fct(obj.can_edit_all, False)
if tag_name == 'cannot_edit_all':
return acl_fct(obj.can_edit_all, True)
if tag_name == 'can_delete':
return acl_fct(obj.can_delete, False)
if tag_name == 'cannot_delete':
return acl_fct(obj.can_delete, True)
if tag_name == 'can_delete_all':
return acl_fct(obj.can_delete_all, False)
if tag_name == 'cannot_delete_all':
return acl_fct(obj.can_delete_all, True)
if tag_name == 'can_view':
return acl_fct(obj.can_view, False)
if tag_name == 'cannot_view':
return acl_fct(obj.can_view, True)
if tag_name == 'can_view_all':
return acl_fct(obj.can_view_all, False)
if tag_name == 'cannot_view_all':
return acl_fct(obj.can_view_all, True)
if tag_name == 'can_view_app':
return acl_fct(sys.modules[obj].can_view, False)
if tag_name == 'cannot_view_app':
return acl_fct(sys.modules[obj].can_view, True)
if tag_name == 'can_edit_history':
return acl_fct(lambda user:(user.has_perm('admin.change_logentry'),None),False)
if tag_name == 'cannot_edit_history':
return acl_fct(lambda user:(user.has_perm('admin.change_logentry'),None),True)
raise template.TemplateSyntaxError(
"%r tag is not a valid can_xxx tag" % tag_name
)
def acl_fct(callback, reverse):
"""Build a function to use as an acl checker"""
def acl_fct_normal(user, *args, **kwargs):
"""The can_xxx checker callback"""
return callback(user, *args, **kwargs)
def acl_fct_reverse(user, *args, **kwargs):
"""The cannot_xxx checker callback"""
can, msg = callback(user, *args, **kwargs)
return not can, msg
return acl_fct_reverse if reverse else acl_fct_normal
@register.tag('can_edit_history')
@register.tag('cannot_edit_history')
def acl_history_filter(parser, token):
"""Templatetag for acl checking on history."""
tag_name, = token.split_contents()
callback = get_callback(tag_name)
oknodes = parser.parse(('acl_else', 'acl_end'))
token = parser.next_token()
if token.contents == 'acl_else':
konodes = parser.parse(('acl_end'))
token = parser.next_token()
else:
konodes = NodeList()
assert token.contents == 'acl_end'
return AclNode(callback, oknodes, konodes)
@register.tag('can_view_app')
@register.tag('cannot_view_app')
def acl_app_filter(parser, token):
"""Templatetag for acl checking on applications."""
try:
tag_name, app_name = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError(
"%r tag require 1 argument : the application"
% token.contents.split()[0]
)
if not app_name in sys.modules.keys():
raise template.TemplateSyntaxError(
"%r is not a registered application for acl."
% app_name
)
callback = get_callback(tag_name, app_name)
oknodes = parser.parse(('acl_else', 'acl_end'))
token = parser.next_token()
if token.contents == 'acl_else':
konodes = parser.parse(('acl_end'))
token = parser.next_token()
else:
konodes = NodeList()
assert token.contents == 'acl_end'
return AclNode(callback, oknodes, konodes)
@register.tag('can_change')
@register.tag('cannot_change')
def acl_change_filter(parser, token):
"""Templatetag for acl checking a can_change_xxx function"""
try:
tag_content = token.split_contents()
tag_name = tag_content[0]
model_name = tag_content[1]
field_name = tag_content[2]
args = tag_content[3:]
except ValueError:
raise template.TemplateSyntaxError(
"%r tag require at least 2 argument : the model and the field"
% token.contents.split()[0]
)
model = get_model(model_name)
callback = getattr(model, 'can_change_'+field_name)
# {% can_create %}
oknodes = parser.parse(('acl_else', 'acl_end'))
token = parser.next_token()
# {% can_create_else %}
if token.contents == 'acl_else':
konodes = parser.parse(('acl_end'))
token = parser.next_token()
else:
konodes = NodeList()
# {% can_create_end %}
assert token.contents == 'acl_end'
return AclNode(callback, oknodes, konodes, *args)
@register.tag('can_create')
@register.tag('cannot_create')
@register.tag('can_edit_all')
@register.tag('cannot_edit_all')
@register.tag('can_delete_all')
@register.tag('cannot_delete_all')
@register.tag('can_view_all')
@register.tag('cannot_view_all')
def acl_model_filter(parser, token):
"""Generic definition of an acl templatetag for acl based on model"""
try:
tag_content = token.split_contents()
tag_name = tag_content[0]
model_name = tag_content[1]
args = tag_content[2:]
except ValueError:
raise template.TemplateSyntaxError(
"%r tag require at least 1 argument : the model"
% token.contents.split()[0]
)
model = get_model(model_name)
callback = get_callback(tag_name, model)
# {% can_create %}
oknodes = parser.parse(('acl_else', 'acl_end'))
token = parser.next_token()
# {% can_create_else %}
if token.contents == 'acl_else':
konodes = parser.parse(('acl_end'))
token = parser.next_token()
else:
konodes = NodeList()
# {% can_create_end %}
assert token.contents == 'acl_end'
return AclNode(callback, oknodes, konodes, *args)
@register.tag('can_edit')
@register.tag('cannot_edit')
@register.tag('can_delete')
@register.tag('cannot_delete')
@register.tag('can_view')
@register.tag('cannot_view')
def acl_instance_filter(parser, token):
"""Generic definition of an acl templatetag for acl based on instance"""
try:
tag_content = token.split_contents()
tag_name = tag_content[0]
instance_name = tag_content[1]
args = tag_content[2:]
except ValueError:
raise template.TemplateSyntaxError(
"%r tag require at least 1 argument : the instance"
% token.contents.split()[0]
)
# {% can_create %}
oknodes = parser.parse(('acl_else', 'acl_end'))
token = parser.next_token()
# {% can_create_else %}
if token.contents == 'acl_else':
konodes = parser.parse(('acl_end'))
token = parser.next_token()
else:
konodes = NodeList()
# {% can_create_end %}
assert token.contents == 'acl_end'
return AclInstanceNode(tag_name, instance_name, oknodes, konodes, *args)
class AclNode(Node):
"""A node for the compiled ACL block when acl callback doesn't require
context."""
def __init__(self, callback, oknodes, konodes, *args):
self.callback = callback
self.oknodes = oknodes
self.konodes = konodes
self.args = [template.Variable(arg) for arg in args]
def render(self, context):
resolved_args = [arg.resolve(context) for arg in self.args]
can, _ = self.callback(context['user'], *(resolved_args))
if can:
return self.oknodes.render(context)
return self.konodes.render(context)
class AclInstanceNode(Node):
"""A node for the compiled ACL block when acl is based on instance"""
def __init__(self, tag_name, instance_name, oknodes, konodes, *args):
self.tag_name = tag_name
self.instance = template.Variable(instance_name)
self.oknodes = oknodes
self.konodes = konodes
self.args = [template.Variable(arg) for arg in args]
def render(self, context):
callback = get_callback(self.tag_name, self.instance.resolve(context))
resolved_args = [arg.resolve(context) for arg in self.args]
can, _ = callback(context['user'], *(resolved_args))
if can:
return self.oknodes.render(context)
return self.konodes.render(context)

View file

@ -39,6 +39,9 @@ from __future__ import unicode_literals
from django.utils import timezone
from django.db.models import Q
from django.contrib import messages
from django.shortcuts import redirect
from django.urls import reverse
from cotisations.models import Cotisation, Facture, Paiement, Vente
from machines.models import Domain, Interface, Machine

View file

@ -26,10 +26,17 @@ les views
from __future__ import unicode_literals
from django.shortcuts import render
from django.http import Http404
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
from preferences.models import OptionalUser, GeneralOption
import users, preferences, cotisations, topologie, machines
def form(ctx, template, request):
"""Form générique, raccourci importé par les fonctions views du site"""
@ -44,3 +51,106 @@ def index(request):
for indice, serv in enumerate(Service.objects.all()):
services[indice % 3].append(serv)
return form({'services_urls': services}, 're2o/index.html', request)
#: Binding the corresponding char sequence of history url to re2o models.
HISTORY_BIND = {
'users' : {
'user' : users.models.User,
'ban' : users.models.Ban,
'whitelist' : users.models.Whitelist,
'school' : users.models.School,
'listright' : users.models.ListRight,
'serviceuser' : users.models.ServiceUser,
},
'preferences' : {
'service' : preferences.models.Service,
},
'cotisations' : {
'facture' : cotisations.models.Facture,
'article' : cotisations.models.Article,
'paiement' : cotisations.models.Paiement,
'banque' : cotisations.models.Banque,
},
'topologie' : {
'switch' : topologie.models.Switch,
'port' : topologie.models.Port,
'room' : topologie.models.Room,
'stack' : topologie.models.Stack,
'model_switch' : topologie.models.ModelSwitch,
'constructor_switch' : topologie.models.ConstructorSwitch,
},
'machines' : {
'machine' : machines.models.Machine,
'interface' : machines.models.Interface,
'alias' : machines.models.Domain,
'machinetype' : machines.models.MachineType,
'iptype' : machines.models.IpType,
'extension' : machines.models.Extension,
'soa' : machines.models.SOA,
'mx' : machines.models.Mx,
'txt' : machines.models.Txt,
'srv' : machines.models.Srv,
'ns' : machines.models.Ns,
'service' : machines.models.Service,
'vlan' : machines.models.Vlan,
'nas' : machines.models.Vlan,
},
}
@login_required
def history(request, application, object_name, object_id):
"""Render history for a model.
The model is determined using the `HISTORY_BIND` dictionnary if none is
found, raises a Http404. The view checks if the user is allowed to see the
history using the `can_view` method of the model.
Args:
request: The request sent by the user.
object_name: Name of the model.
object_id: Id of the object you want to acces history.
Returns:
The rendered page of history if access is granted, else the user is
redirected to their profile page, with an error message.
Raises:
Http404: This kind of models doesn't have history.
"""
try:
model = HISTORY_BIND[application][object_name]
except KeyError as e:
raise Http404(u"Il n'existe pas d'historique pour ce modèle.")
try:
instance = model.get_instance(object_id)
except model.DoesNotExist:
messages.error(request, u"Entrée inexistante")
return redirect(reverse('users:profil',
kwargs={'userid':str(request.user.id)}
))
can, msg = instance.can_view(request.user)
if not can:
messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse(
'users:profil',
kwargs={'userid':str(request.user.id)}
))
options, _created = GeneralOption.objects.get_or_create()
pagination_number = options.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)
return render(
request,
're2o/history.html',
{'reversions': reversions, 'object': instance}
)

View file

@ -21,3 +21,4 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from .acl import *

39
search/acl.py Normal file
View file

@ -0,0 +1,39 @@
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""search.acl
Here are defined some functions to check acl on the application.
"""
def can_view(user):
"""Check if an user can view the application.
Args:
user: The user who wants to view the application.
Returns:
A couple (allowed, msg) where allowed is a boolean which is True if
viewing is granted and msg is a message (can be None).
"""
return True, None

View file

@ -120,7 +120,7 @@ def finish_results(results, col, order):
return results
def search_single_word(word, filters, is_cableur, user_id,
def search_single_word(word, filters, user,
start, end, user_state, aff):
""" Construct the correct filters to match differents fields of some models
with the given query according to the given filters.
@ -144,8 +144,8 @@ def search_single_word(word, filters, is_cableur, user_id,
adherent__room__name__icontains=word
)
) & Q(state__in=user_state)
if not is_cableur:
filter_users &= Q(id=user_id)
if not User.can_view_all(user)[0]:
filter_users &= Q(id=user.id)
filters['users'] |= filter_users
# Machines
@ -167,8 +167,8 @@ def search_single_word(word, filters, is_cableur, user_id,
) | Q(
interface__ipv4__ipv4__icontains=word
)
if not is_cableur:
filter_machines &= Q(user__id=user_id)
if not Machine.can_view_all(user)[0]:
filter_machines &= Q(user__id=user.id)
filters['machines'] |= filter_machines
# Factures
@ -243,7 +243,7 @@ def search_single_word(word, filters, is_cableur, user_id,
filters['whitelists'] |= filter_whitelists
# Rooms
if '5' in aff and is_cableur:
if '5' in aff and Room.can_view_all(user):
filter_rooms = Q(
details__icontains=word
) | Q(
@ -254,7 +254,7 @@ def search_single_word(word, filters, is_cableur, user_id,
filters['rooms'] |= filter_rooms
# Switch ports
if '6' in aff and is_cableur:
if '6' in aff and User.can_view_all(user):
filter_ports = Q(
room__name__icontains=word
) | Q(
@ -275,7 +275,7 @@ def search_single_word(word, filters, is_cableur, user_id,
filters['ports'] |= filter_ports
# Switches
if '7' in aff and is_cableur:
if '7' in aff and Switch.can_view_all(user):
filter_switches = Q(
switch_interface__domain__name__icontains=word
) | Q(
@ -374,8 +374,7 @@ def get_results(query, request, params):
filters = search_single_word(
word,
filters,
request.user.has_perms(('cableur',)),
request.user.id,
request.user,
start,
end,
user_state,

View file

@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{# Load the tag library #}
{% load bootstrap3 %}
{% load acl %}
<!DOCTYPE html>
<html lang="fr">
<head prefix="og: http://ogp.me/ns#">
@ -73,13 +73,22 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<div class="collapse navbar-collapse" id="myNavbar">
<ul class="nav navbar-nav">
<li><a href="{% url "users:mon-profil" %}">Mon profil</a></li>
{% if is_cableur %}
{% can_view_app users %}
<li><a href="{% url "users:index" %}">Adhérents</a></li>
{% acl_end %}
{% can_view_app machines %}
<li><a href="{% url "machines:index" %}">Machines</a></li>
{% acl_end %}
{% can_view_app cotisations %}
<li><a href="{% url "cotisations:index" %}">Cotisations</a></li>
{% acl_end %}
{% can_view_app topologie %}
<li><a href="{% url "topologie:index" %}">Topologie</a></li>
{% acl_end %}
{% can_view_app logs %}
<li><a href="{% url "logs:index" %}">Statistiques</a></li>
{% endif %}
{% acl_end %}
</ul>
<div class="col-sm-3 col-md-3 navbar-right">
<form action="{% url "search:search"%}" class="navbar-form" role="search">
@ -105,7 +114,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endif %}
</li>
</ul>
{% if is_cableur %}
{% can_view_app preferences %}
<ul class="nav navbar-nav navbar-right">
<li>
<a href="{% url 'preferences:display-options' %}">
@ -113,7 +122,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</a>
</li>
</ul>
{% endif %}
{% acl_end %}
</div>
</div>
</nav>
@ -205,7 +214,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<footer class="navbar">
<div class="containerfluid text-center">
<p>Re2o 2016 - Gabriel Détraz, <a href="https://gitlab.rezometz.org/lhark">Goulven Kermarec</a>, Augustin Lemesle, Maël Kervella</p>
<p>Re2o 2016-2018 - Gabriel Détraz, <a href="https://gitlab.rezometz.org/lhark">Goulven Kermarec</a>, Augustin Lemesle, Maël Kervella, Hugo Levy-Falk</p>
</div>
</footer>
{# Read the documentation for more information #}

View file

@ -21,3 +21,4 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from .acl import *

40
topologie/acl.py Normal file
View file

@ -0,0 +1,40 @@
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""topologie.acl
Here are defined some functions to check acl on the application.
"""
def can_view(user):
"""Check if an user can view the application.
Args:
user: The user who wants to view the application.
Returns:
A couple (allowed, msg) where allowed is a boolean which is True if
viewing is granted and msg is a message (can be None).
"""
can = user.has_module_perms('topologie')
return can, None if can else "Vous ne pouvez pas voir cette application."

View file

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 16:43
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('topologie', '0032_auto_20171026_0338'),
]
operations = [
migrations.AlterModelOptions(
name='constructorswitch',
options={'permissions': (('view_constructorswitch', 'Peut voir un objet constructorswitch'),)},
),
migrations.AlterModelOptions(
name='modelswitch',
options={'permissions': (('view_modelswitch', 'Peut voir un objet modelswitch'),)},
),
migrations.AlterModelOptions(
name='port',
options={'permissions': (('view_port', 'Peut voir un objet port'),)},
),
migrations.AlterModelOptions(
name='room',
options={'ordering': ['name'], 'permissions': (('view_room', 'Peut voir un objet chambre'),)},
),
migrations.AlterModelOptions(
name='stack',
options={'permissions': (('view_stack', 'Peut voir un objet stack'),)},
),
migrations.AlterModelOptions(
name='switch',
options={'permissions': (('view_switch', 'Peut voir un objet switch'),)},
),
]

View file

@ -60,6 +60,38 @@ class Stack(models.Model):
member_id_min = models.PositiveIntegerField()
member_id_max = models.PositiveIntegerField()
class Meta:
permissions = (
("view_stack", "Peut voir un objet stack"),
)
def get_instance(stack_id, *args, **kwargs):
return Stack.objects.get(pk=stack_id)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('topologie.add_stack') , u"Vous n'avez pas le droit\
de créer un stack"
def can_edit(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.change_stack'):
return False, u"Vous n'avez pas le droit d'éditer des stack"
return True, None
def can_delete(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.delete_stack'):
return False, u"Vous n'avez pas le droit de supprimer une stack"
return True, None
def can_view_all(user_request, *args, **kwargs):
if not user_request.has_perm('topologie.view_stack'):
return False, u"Vous n'avez pas le droit de voir une stack"
return True, None
def can_view(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.view_stack'):
return False, u"Vous n'avez pas le droit de voir une stack"
return True, None
def __str__(self):
return " ".join([self.name, self.stack_id])
@ -113,6 +145,36 @@ class Switch(models.Model):
class Meta:
unique_together = ('stack', 'stack_member_id')
permissions = (
("view_switch", "Peut voir un objet switch"),
)
def get_instance(switch_id, *args, **kwargs):
return Switch.objects.get(pk=switch_id)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('topologie.add_switch') , u"Vous n'avez pas le droit\
de créer un switch"
def can_edit(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.change_switch'):
return False, u"Vous n'avez pas le droit d'éditer des switch"
return True, None
def can_delete(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.delete_switch'):
return False, u"Vous n'avez pas le droit de supprimer un switch"
return True, None
def can_view_all(user_request, *args, **kwargs):
if not user_request.has_perm('topologie.view_switch'):
return False, u"Vous n'avez pas le droit de coir les switch"
return True, None
def can_view(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.view_switch'):
return False, u"Vous n'avez pas le droit de coir les switch"
return True, None
def __str__(self):
return self.location + ' ' + str(self.switch_interface)
@ -167,6 +229,38 @@ class ModelSwitch(models.Model):
on_delete=models.PROTECT
)
class Meta:
permissions = (
("view_modelswitch", "Peut voir un objet modelswitch"),
)
def get_instance(model_switch_id, *args, **kwargs):
return ModelSwitch.objects.get(pk=model_switch_id)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('topologie.add_modelswitch') , u"Vous n'avez pas le droit\
de créer un modèle de switch"
def can_edit(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.change_modelswitch'):
return False, u"Vous n'avez pas le droit d'éditer des modèle de switchs"
return True, None
def can_delete(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.delete_modelswitch'):
return False, u"Vous n'avez pas le droit de supprimer un modèle switch"
return True, None
def can_view(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.view_modelswitch'):
return False, u"Vous n'avez pas le droit de voir un modèle switch"
return True, None
def can_view_all(user_request, *args, **kwargs):
if not user_request.has_perm('topologie.view_modelswitch'):
return False, u"Vous n'avez pas le droit de voir un modèle switch"
return True, None
def __str__(self):
return str(self.constructor) + ' ' + self.reference
@ -176,6 +270,39 @@ class ConstructorSwitch(models.Model):
PRETTY_NAME = "Constructeur de switch"
name = models.CharField(max_length=255)
class Meta:
permissions = (
("view_constructorswitch", "Peut voir un objet constructorswitch"),
)
def get_instance(constructor_switch_id, *args, **kwargs):
return ConstructorSwitch.objects.get(pk=constructor_switch_id)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('topologie.add_constructorswitch') , u"Vous n'avez pas le droit\
de créer un constructeur de switch"
def can_edit(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.change_constructorswitch'):
return False, u"Vous n'avez pas le droit d'éditer des\
constructeurs de switchs"
return True, None
def can_delete(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.delete_constructorswitch'):
return False, u"Vous n'avez pas le droit de supprimer un constructeur"
return True, None
def can_view_all(user_request, *args, **kwargs):
if not user_request.has_perm('topologie.view_constructorswitch'):
return False, u"Vous n'avez pas le droit de voir un constructeur"
return True, None
def can_view(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.view_constructorswitch'):
return False, u"Vous n'avez pas le droit de voir un constructeur"
return True, None
def __str__(self):
return self.name
@ -239,6 +366,42 @@ class Port(models.Model):
class Meta:
unique_together = ('switch', 'port')
permissions = (
("view_port", "Peut voir un objet port"),
)
def get_instance(port_id, *args, **kwargs):
return Port.objects\
.select_related('switch__switch_interface__domain__extension')\
.select_related('machine_interface__domain__extension')\
.select_related('machine_interface__switch')\
.select_related('room')\
.select_related('related')\
.get(pk=port_id)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('topologie.add_port') , u"Vous n'avez pas le droit\
de créer un port"
def can_edit(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.change_port'):
return False, u"Vous n'avez pas le droit d'éditer des ports"
return True, None
def can_delete(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.delete_port'):
return False, u"Vous n'avez pas le droit de supprimer un port"
return True, None
def can_view_all(user_request, *args, **kwargs):
if not user_request.has_perm('topologie.view_port'):
return False, u"Vous n'avez pas le droit de voir les ports"
return True, None
def can_view(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.view_port'):
return False, u"Vous n'avez pas le droit de voir les ports"
return True, None
def make_port_related(self):
""" Synchronise le port distant sur self"""
@ -293,6 +456,36 @@ class Room(models.Model):
class Meta:
ordering = ['name']
permissions = (
("view_room", "Peut voir un objet chambre"),
)
def get_instance(room_id, *args, **kwargs):
return Room.objects.get(pk=room_id)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('topologie.add_room') , u"Vous n'avez pas le droit\
de créer une chambre"
def can_edit(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.change_room'):
return False, u"Vous n'avez pas le droit d'éditer une chambre"
return True, None
def can_delete(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.delete_room'):
return False, u"Vous n'avez pas le droit de supprimer une chambre"
return True, None
def can_view_all(user_request, *args, **kwargs):
if not user_request.has_perm('topologie.view_room'):
return False, u"Vous n'avez pas le droit de voir les chambres"
return True, None
def can_view(self, user_request, *args, **kwargs):
if not user_request.has_perm('topologie.view_room'):
return False, u"Vous n'avez pas le droit de voir les chambres"
return True, None
def __str__(self):
return self.name

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
{% if room_list.paginator %}
{% include "pagination.html" with list=room_list %}
{% endif %}
@ -42,14 +44,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'room' room.pk %}">
<i class="glyphicon glyphicon-time"></i>
</a>
{% if is_infra %}
{% can_edit room %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-room' room.id %}">
<i class="glyphicon glyphicon-edit"></i>
</a>
{% acl_end %}
{% can_delete room %}
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-room' room.id %}">
<i class="glyphicon glyphicon-trash"></i>
</a>
{% endif %}
{% acl_end %}
</td>
</tr>
{% endfor %}

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
{% if constructor_switch_list.paginator %}
{% include "pagination.html" with list=constructor_switch_list %}
{% endif %}
@ -40,14 +42,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'constructor_switch' constructor_switch.pk %}">
<i class="glyphicon glyphicon-time"></i>
</a>
{% if is_infra %}
{% can_edit constructor_switch %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-constructor-switch' constructor_switch.id %}">
<i class="glyphicon glyphicon-edit"></i>
</a>
{% acl_end %}
{% can_delete constructor_switch %}
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-constructor-switch' constructor_switch.id %}">
<i class="glyphicon glyphicon-trash"></i>
</a>
{% endif %}
{% acl_end %}
</td>
</tr>
{% endfor %}

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
{% if model_switch_list.paginator %}
{% include "pagination.html" with list=model_switch_list %}
{% endif %}
@ -42,14 +44,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'model_switch' model_switch.pk %}">
<i class="glyphicon glyphicon-time"></i>
</a>
{% if is_infra %}
{% can_edit model_switch %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-model-switch' model_switch.id %}">
<i class="glyphicon glyphicon-edit"></i>
</a>
{% acl_end %}
{% can_delete model_switch %}
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-model-switch' model_switch.id %}">
<i class="glyphicon glyphicon-trash"></i>
</a>
{% endif %}
{% acl_end %}
</td>
</tr>
{% endfor %}

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -60,14 +62,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'port' port.pk %}">
<i class="glyphicon glyphicon-time"></i>
</a>
{% if is_infra %}
{% can_edit port %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-port' port.id %}">
<i class="glyphicon glyphicon-edit"></i>
</a>
{% acl_end %}
{% can_delete port %}
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-port' port.pk %}">
<i class="glyphicon glyphicon-trash"></i>
</a>
{% endif %}
{% acl_end %}
</td>
</tr>
{% endfor %}

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -46,14 +48,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'stack' stack.pk %}">
<i class="glyphicon glyphicon-time"></i>
</a>
{% if is_infra %}
{% can_edit stack %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-stack' stack.id %}">
<i class="glyphicon glyphicon-edit"></i>
</a>
{% acl_end %}
{% can_delete stack %}
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-stack' stack.pk %}">
<i class="glyphicon glyphicon-trash"></i>
</a>
{% endif %}
{% acl_end %}
</td>
{% endif %}
</tr>
@ -67,14 +71,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'stack' stack.pk %}">
<i class="glyphicon glyphicon-time"></i>
</a>
{% if is_infra %}
{% can_edit stack %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-stack' stack.id %}">
<i class="glyphicon glyphicon-edit"></i>
</a>
{% acl_end %}
{% can_delete stack %}
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-stack' stack.pk %}">
<i class="glyphicon glyphicon-trash"></i>
</a>
{% endif %}
{% acl_end %}
</td>
{% endfor %}
</tbody>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
{% if switch_list.paginator %}
{% include "pagination.html" with list=switch_list %}
{% endif %}
@ -56,11 +58,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{switch.details}}</td>
<td class="text-right">
{% include 'buttons/history.html' with href='topologie:history' name='switch' id=switch.pk%}
{% if is_infra %}
{% can_edit switch %}
{% include 'buttons/edit.html' with href='topologie:edit-switch' id=switch.pk %}
{% acl_end %}
{% can_delete switch %}
{% include 'buttons/suppr.html' with href='machines:del-interface' id=switch.switch_interface.id %}
{% acl_end %}
{% can_create Port %}
{% include 'buttons/add.html' with href='topologie:create-ports' id=switch.pk desc='Création de ports'%}
{% endif %}
{% acl_end %}
</td>
</tr>
{% endfor %}

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -39,14 +41,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'stack' stack.pk %}">
<i class="glyphicon glyphicon-time"></i>
</a>
{% if is_infra %}
{% can_edit stack %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-stack' stack.id %}">
<i class="glyphicon glyphicon-edit"></i>
</a>
{% acl_end %}
{% can_delete stack %}
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-stack' stack.pk %}">
<i class="glyphicon glyphicon-trash"></i>
</a>
{% endif %}
{% acl_end %}
</td>
</tr>
{% endfor %}

View file

@ -24,15 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% block title %}Switchs{% endblock %}
{% block content %}
<h2>Switchs</h2>
{% if is_infra %}
{% can_create Switch %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:new-switch' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un switch</a>
<hr>
{% endif %}
{% acl_end %}
{% include "topologie/aff_switch.html" with switch_list=switch_list %}
<br />
<br />

View file

@ -24,21 +24,22 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% block title %}Modèles de switches{% endblock %}
{% block content %}
<h2>Modèles de switches</h2>
{% if is_infra %}
{% can_create ModelSwitch %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:new-model-switch' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un modèle</a>
<hr>
{% endif %}
{% acl_end %}
{% include "topologie/aff_model_switch.html" with model_switch_list=model_switch_list %}
<h2>Constructeurs de switches</h2>
{% if is_infra %}
{% can_create ConstructorSwitch %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:new-constructor-switch' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un constructeur</a>
<hr>
{% endif %}
{% acl_end %}
{% include "topologie/aff_constructor_switch.html" with constructor_switch_list=constructor_switch_list %}
<br />
<br />

View file

@ -24,16 +24,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% block title %}Ports du switch{% endblock %}
{% block content %}
<h2>Switch {{ nom_switch }}</h2>
{% if is_infra %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:edit-switch' id_switch %}"><i class="glyphicon glyphicon-edit"></i> Editer</a>
{% can_create Port %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:new-port' id_switch %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un port</a>
{% acl_end %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:create-ports' id_switch %}"><i class="glyphicon glyphicon-plus"></i> Ajouter des ports</a>
{% endif %}
{% include "topologie/aff_port.html" with port_list=port_list %}
<br />
<br />

View file

@ -24,15 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% block title %}Chambres{% endblock %}
{% block content %}
<h2>Chambres</h2>
{% if is_infra %}
{% can_create Room %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:new-room' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter une chambre</a>
<hr>
{% endif %}
{% acl_end %}
{% include "topologie/aff_chambres.html" with room_list=room_list %}
<br />
<br />

View file

@ -24,14 +24,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% block title %}Stacks{% endblock %}
{% block content %}
<h2>Stacks</h2>
{% if is_infra %}
{% can_create Stack %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:new-stack' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter une stack</a>
{% endif %}
{% acl_end %}
{% include "topologie/aff_stacks.html" with stack_list=stack_list %}
<br />
<br />

View file

@ -30,6 +30,7 @@ from __future__ import unicode_literals
from django.conf.urls import url
import re2o
from . import views
urlpatterns = [
@ -45,24 +46,12 @@ urlpatterns = [
url(r'^switch/(?P<switch_id>[0-9]+)$',
views.index_port,
name='index-port'),
url(r'^history/(?P<object_name>switch)/(?P<object_id>[0-9]+)$',
views.history,
name='history'),
url(r'^history/(?P<object_name>port)/(?P<object_id>[0-9]+)$',
views.history,
name='history'),
url(r'^history/(?P<object_name>room)/(?P<object_id>[0-9]+)$',
views.history,
name='history'),
url(r'^history/(?P<object_name>stack)/(?P<object_id>[0-9]+)$',
views.history,
name='history'),
url(r'^history/(?P<object_name>model_switch)/(?P<object_id>[0-9]+)$',
views.history,
name='history'),
url(r'^history/(?P<object_name>constructor_switch)/(?P<object_id>[0-9]+)$',
views.history,
name='history'),
url(
r'^history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
re2o.views.history,
name='history',
kwargs={'application':'topologie'},
),
url(r'^edit_port/(?P<port_id>[0-9]+)$', views.edit_port, name='edit-port'),
url(r'^new_port/(?P<switch_id>[0-9]+)$', views.new_port, name='new-port'),
url(r'^del_port/(?P<port_id>[0-9]+)$', views.del_port, name='del-port'),

View file

@ -66,6 +66,13 @@ from topologie.forms import (
)
from users.views import form
from re2o.utils import SortTable
from re2o.acl import (
can_create,
can_edit,
can_delete,
can_view,
can_view_all,
)
from machines.forms import (
DomainForm,
NewMachineForm,
@ -78,7 +85,7 @@ from preferences.models import AssoOption, GeneralOption
@login_required
@permission_required('cableur')
@can_view_all(Switch)
def index(request):
""" Vue d'affichage de tous les swicthes"""
switch_list = Switch.objects\
@ -110,76 +117,10 @@ def index(request):
@login_required
@permission_required('cableur')
def history(request, object_name, object_id):
""" Vue générique pour afficher l'historique complet d'un objet"""
if object_name == 'switch':
try:
object_instance = Switch.objects.get(pk=object_id)
except Switch.DoesNotExist:
messages.error(request, "Switch inexistant")
return redirect(reverse('topologie:index'))
elif object_name == 'port':
try:
object_instance = Port.objects.get(pk=object_id)
except Port.DoesNotExist:
messages.error(request, "Port inexistant")
return redirect(reverse('topologie:index'))
elif object_name == 'room':
try:
object_instance = Room.objects.get(pk=object_id)
except Room.DoesNotExist:
messages.error(request, "Chambre inexistante")
return redirect(reverse('topologie:index'))
elif object_name == 'stack':
try:
object_instance = Stack.objects.get(pk=object_id)
except Room.DoesNotExist:
messages.error(request, "Stack inexistante")
return redirect(reverse('topologie:index'))
elif object_name == 'model_switch':
try:
object_instance = ModelSwitch.objects.get(pk=object_id)
except ModelSwitch.DoesNotExist:
messages.error(request, "SwitchModel inexistant")
return redirect(reverse('topologie:index'))
elif object_name == 'constructor_switch':
try:
object_instance = ConstructorSwitch.objects.get(pk=object_id)
except ConstructorSwitch.DoesNotExist:
messages.error(request, "SwitchConstructor inexistant")
return redirect(reverse('topologie:index'))
else:
messages.error(request, "Objet inconnu")
return redirect(reverse('topologie:index'))
options, _created = GeneralOption.objects.get_or_create()
pagination_number = options.pagination_number
reversions = Version.objects.get_for_object(object_instance)
paginator = Paginator(reversions, pagination_number)
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 results.
reversions = paginator.page(paginator.num_pages)
return render(request, 're2o/history.html', {
'reversions': reversions,
'object': object_instance
})
@login_required
@permission_required('cableur')
def index_port(request, switch_id):
@can_view_all(Port)
@can_view(Switch)
def index_port(request, switch, switch_id):
""" Affichage de l'ensemble des ports reliés à un switch particulier"""
try:
switch = Switch.objects.get(pk=switch_id)
except Switch.DoesNotExist:
messages.error(request, u"Switch inexistant")
return redirect(reverse('topologie:index'))
port_list = Port.objects.filter(switch=switch)\
.select_related('room')\
.select_related('machine_interface__domain__extension')\
@ -202,7 +143,7 @@ def index_port(request, switch_id):
@login_required
@permission_required('cableur')
@can_view_all(Room)
def index_room(request):
""" Affichage de l'ensemble des chambres"""
room_list = Room.objects
@ -230,7 +171,7 @@ def index_room(request):
@login_required
@permission_required('infra')
@can_view_all(Stack)
def index_stack(request):
"""Affichage de la liste des stacks (affiche l'ensemble des switches)"""
stack_list = Stack.objects\
@ -247,7 +188,8 @@ def index_stack(request):
@login_required
@permission_required('cableur')
@can_view_all(ModelSwitch)
@can_view_all(ConstructorSwitch)
def index_model_switch(request):
""" Affichage de l'ensemble des modèles de switches"""
model_switch_list = ModelSwitch.objects
@ -271,7 +213,7 @@ def index_model_switch(request):
@login_required
@permission_required('infra')
@can_create(Port)
def new_port(request, switch_id):
""" Nouveau port"""
try:
@ -299,21 +241,11 @@ def new_port(request, switch_id):
@login_required
@permission_required('infra')
def edit_port(request, port_id):
@can_edit(Port)
def edit_port(request, port_object, port_id):
""" Edition d'un port. Permet de changer le switch parent et
l'affectation du port"""
try:
port_object = Port.objects\
.select_related('switch__switch_interface__domain__extension')\
.select_related('machine_interface__domain__extension')\
.select_related('machine_interface__switch')\
.select_related('room')\
.select_related('related')\
.get(pk=port_id)
except Port.DoesNotExist:
messages.error(request, u"Port inexistant")
return redirect(reverse('topologie:index'))
port = EditPortForm(request.POST or None, instance=port_object)
if port.is_valid():
with transaction.atomic(), reversion.create_revision():
@ -331,14 +263,9 @@ def edit_port(request, port_id):
@login_required
@permission_required('infra')
def del_port(request, port_id):
@can_delete(Port)
def del_port(request, port, port_id):
""" Supprime le port"""
try:
port = Port.objects.get(pk=port_id)
except Port.DoesNotExist:
messages.error(request, u"Port inexistant")
return redirect(reverse('topologie:index'))
if request.method == "POST":
try:
with transaction.atomic(), reversion.create_revision():
@ -357,7 +284,7 @@ def del_port(request, port_id):
@login_required
@permission_required('infra')
@can_create(Stack)
def new_stack(request):
"""Ajoute un nouveau stack : stack_id_min, max, et nombre de switches"""
stack = StackForm(request.POST or None)
@ -371,14 +298,10 @@ def new_stack(request):
@login_required
@permission_required('infra')
def edit_stack(request, stack_id):
@can_edit(Stack)
def edit_stack(request, stack, stack_id):
"""Edition d'un stack (nombre de switches, nom...)"""
try:
stack = Stack.objects.get(pk=stack_id)
except Stack.DoesNotExist:
messages.error(request, u"Stack inexistante")
return redirect(reverse('topologie:index-stack'))
stack = StackForm(request.POST or None, instance=stack)
if stack.is_valid():
with transaction.atomic(), reversion.create_revision():
@ -394,14 +317,9 @@ def edit_stack(request, stack_id):
@login_required
@permission_required('infra')
def del_stack(request, stack_id):
@can_delete(Stack)
def del_stack(request, stack, stack_id):
"""Supprime un stack"""
try:
stack = Stack.objects.get(pk=stack_id)
except Stack.DoesNotExist:
messages.error(request, u"Stack inexistante")
return redirect(reverse('topologie:index-stack'))
if request.method == "POST":
try:
with transaction.atomic(), reversion.create_revision():
@ -417,14 +335,10 @@ def del_stack(request, stack_id):
@login_required
@permission_required('infra')
def edit_switchs_stack(request, stack_id):
@can_edit(Stack)
def edit_switchs_stack(request, stack, stack_id):
"""Permet d'éditer la liste des switches dans une stack et l'ajouter"""
try:
stack = Stack.objects.get(pk=stack_id)
except Stack.DoesNotExist:
messages.error(request, u"Stack inexistante")
return redirect(reverse('topologie:index-stack'))
if request.method == "POST":
pass
else:
@ -434,16 +348,19 @@ def edit_switchs_stack(request, stack_id):
@login_required
@permission_required('infra')
@can_create(Switch)
def new_switch(request):
""" Creation d'un switch. Cree en meme temps l'interface et la machine
associée. Vue complexe. Appelle successivement les 4 models forms
adaptés : machine, interface, domain et switch"""
switch = NewSwitchForm(request.POST or None)
machine = NewMachineForm(request.POST or None)
machine = NewMachineForm(
request.POST or None,
user=request.user
)
interface = AddInterfaceForm(
request.POST or None,
infra=request.user.has_perms(('infra',))
user=request.user
)
domain = DomainForm(
request.POST or None,
@ -492,7 +409,7 @@ def new_switch(request):
@login_required
@permission_required('infra')
@can_create(Port)
def create_ports(request, switch_id):
""" Création d'une liste de ports pour un switch."""
try:
@ -528,15 +445,11 @@ def create_ports(request, switch_id):
@login_required
@permission_required('infra')
def edit_switch(request, switch_id):
@can_edit(Switch)
def edit_switch(request, switch, switch_id):
""" Edition d'un switch. Permet de chambre nombre de ports,
place dans le stack, interface et machine associée"""
try:
switch = Switch.objects.get(pk=switch_id)
except Switch.DoesNotExist:
messages.error(request, u"Switch inexistant")
return redirect(reverse('topologie:index'))
switch_form = EditSwitchForm(request.POST or None, instance=switch)
machine_form = EditMachineForm(
request.POST or None,
@ -596,7 +509,7 @@ def edit_switch(request, switch_id):
@login_required
@permission_required('infra')
@can_create(Room)
def new_room(request):
"""Nouvelle chambre """
room = EditRoomForm(request.POST or None)
@ -611,14 +524,10 @@ def new_room(request):
@login_required
@permission_required('infra')
def edit_room(request, room_id):
@can_edit(Room)
def edit_room(request, room, room_id):
""" Edition numero et details de la chambre"""
try:
room = Room.objects.get(pk=room_id)
except Room.DoesNotExist:
messages.error(request, u"Chambre inexistante")
return redirect(reverse('topologie:index-room'))
room = EditRoomForm(request.POST or None, instance=room)
if room.is_valid():
with transaction.atomic(), reversion.create_revision():
@ -633,14 +542,9 @@ def edit_room(request, room_id):
@login_required
@permission_required('infra')
def del_room(request, room_id):
@can_delete(Room)
def del_room(request, room, room_id):
""" Suppression d'un chambre"""
try:
room = Room.objects.get(pk=room_id)
except Room.DoesNotExist:
messages.error(request, u"Chambre inexistante")
return redirect(reverse('topologie:index-room'))
if request.method == "POST":
try:
with transaction.atomic(), reversion.create_revision():
@ -659,7 +563,7 @@ def del_room(request, room_id):
@login_required
@permission_required('infra')
@can_create(ModelSwitch)
def new_model_switch(request):
"""Nouveau modèle de switch"""
model_switch = EditModelSwitchForm(request.POST or None)
@ -674,14 +578,10 @@ def new_model_switch(request):
@login_required
@permission_required('infra')
def edit_model_switch(request, model_switch_id):
@can_edit(ModelSwitch)
def edit_model_switch(request, model_switch, model_switch_id):
""" Edition d'un modèle de switch"""
try:
model_switch = ModelSwitch.objects.get(pk=model_switch_id)
except ModelSwitch.DoesNotExist:
messages.error(request, u"Modèle inconnu")
return redirect("/topologie/index_model_switch/")
model_switch = EditModelSwitchForm(request.POST or None, instance=model_switch)
if model_switch.is_valid():
with transaction.atomic(), reversion.create_revision():
@ -696,14 +596,9 @@ def edit_model_switch(request, model_switch_id):
@login_required
@permission_required('infra')
@can_delete(ModelSwitch)
def del_model_switch(request, model_switch_id):
""" Suppression d'un modèle de switch"""
try:
model_switch = ModelSwitch.objects.get(pk=model_switch_id)
except ModelSwitch.DoesNotExist:
messages.error(request, u"Modèle inexistant")
return redirect("/topologie/index_model_switch/")
if request.method == "POST":
try:
with transaction.atomic(), reversion.create_revision():
@ -722,7 +617,7 @@ def del_model_switch(request, model_switch_id):
@login_required
@permission_required('infra')
@can_create(ConstructorSwitch)
def new_constructor_switch(request):
"""Nouveau constructeur de switch"""
constructor_switch = EditConstructorSwitchForm(request.POST or None)
@ -737,14 +632,10 @@ def new_constructor_switch(request):
@login_required
@permission_required('infra')
def edit_constructor_switch(request, constructor_switch_id):
@can_edit(ConstructorSwitch)
def edit_constructor_switch(request, constructor_switch, constructor_switch_id):
""" Edition d'un constructeur de switch"""
try:
constructor_switch = ConstructorSwitch.objects.get(pk=constructor_switch_id)
except ConstructorSwitch.DoesNotExist:
messages.error(request, u"Constructeur inconnu")
return redirect("/topologie/index_model_switch/")
constructor_switch = EditConstructorSwitchForm(request.POST or None, instance=constructor_switch)
if constructor_switch.is_valid():
with transaction.atomic(), reversion.create_revision():
@ -759,14 +650,9 @@ def edit_constructor_switch(request, constructor_switch_id):
@login_required
@permission_required('infra')
@can_delete(ConstructorSwitch)
def del_constructor_switch(request, constructor_switch_id):
""" Suppression d'un constructeur de switch"""
try:
constructor_switch = ConstructorSwitch.objects.get(pk=constructor_switch_id)
except ConstructorSwitch.DoesNotExist:
messages.error(request, u"Constructeur inexistant")
return redirect("/topologie/index_model_switch/")
if request.method == "POST":
try:
with transaction.atomic(), reversion.create_revision():

View file

@ -21,3 +21,4 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from .acl import *

40
users/acl.py Normal file
View file

@ -0,0 +1,40 @@
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""users.acl
Here are defined some functions to check acl on the application.
"""
def can_view(user):
"""Check if an user can view the application.
Args:
user: The user who wants to view the application.
Returns:
A couple (allowed, msg) where allowed is a boolean which is True if
viewing is granted and msg is a message (can be None).
"""
can = user.has_module_perms('users')
return can, None if can else "Vous ne pouvez pas voir cette application."

View file

@ -32,7 +32,7 @@ from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from reversion.admin import VersionAdmin
from .models import User, ServiceUser, School, Right, ListRight, ListShell
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
@ -86,7 +86,7 @@ class SchoolAdmin(VersionAdmin):
class ListRightAdmin(VersionAdmin):
"""Gestion de la liste des droits existants
Ne permet pas l'edition du gid (primarykey pour ldap)"""
list_display = ('listright',)
list_display = ('unix_name',)
class ListShellAdmin(VersionAdmin):
@ -94,11 +94,6 @@ class ListShellAdmin(VersionAdmin):
pass
class RightAdmin(VersionAdmin):
"""Gestion de la liste des droits affectés"""
pass
class RequestAdmin(admin.ModelAdmin):
"""Gestion des request objet, ticket pour lien de reinit mot de passe"""
list_display = ('user', 'type', 'created_at', 'expires_at')
@ -206,7 +201,6 @@ admin.site.register(LdapUserGroup, LdapUserGroupAdmin)
admin.site.register(LdapServiceUser, LdapServiceUserAdmin)
admin.site.register(LdapServiceUserGroup, LdapServiceUserGroupAdmin)
admin.site.register(School, SchoolAdmin)
admin.site.register(Right, RightAdmin)
admin.site.register(ListRight, ListRightAdmin)
admin.site.register(ListShell, ListShellAdmin)
admin.site.register(Ban, BanAdmin)

View file

@ -38,12 +38,15 @@ from django.forms import ModelForm, Form
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.core.validators import MinLengthValidator
from django.utils import timezone
from django.contrib.auth.models import Group, Permission
from preferences.models import OptionalUser
from .models import User, ServiceUser, Right, School, ListRight, Whitelist
from .models import User, ServiceUser, School, ListRight, Whitelist
from .models import Ban, Adherent, Club
from re2o.utils import remove_user_room
from re2o.field_permissions import FieldPermissionFormMixin
NOW = timezone.now()
@ -253,7 +256,7 @@ class MassArchiveForm(forms.Form):
utilisateurs dont la fin d'accès se situe dans le futur !")
class AdherentForm(ModelForm):
class AdherentForm(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"""
@ -278,6 +281,7 @@ class AdherentForm(ModelForm):
'school',
'comment',
'room',
'shell',
'telephone',
]
@ -306,7 +310,7 @@ class AdherentForm(ModelForm):
return
class ClubForm(ModelForm):
class ClubForm(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"""
@ -330,6 +334,7 @@ class ClubForm(ModelForm):
'comment',
'room',
'telephone',
'shell',
]
def clean_telephone(self):
@ -344,41 +349,6 @@ class ClubForm(ModelForm):
return telephone
class FullAdherentForm(AdherentForm):
"""Edition complète d'un user. Utilisé par admin,
permet d'editer normalement la chambre, ou le shell
Herite de la base"""
class Meta(AdherentForm.Meta):
fields = [
'name',
'surname',
'pseudo',
'email',
'school',
'comment',
'room',
'shell',
'telephone',
]
class FullClubForm(ClubForm):
"""Edition complète d'un user. Utilisé par admin,
permet d'editer normalement la chambre, ou le shell
Herite de la base"""
class Meta(ClubForm.Meta):
fields = [
'surname',
'pseudo',
'email',
'school',
'comment',
'room',
'shell',
'telephone',
]
class ClubAdminandMembersForm(ModelForm):
"""Permet d'éditer la liste des membres et des administrateurs
d'un club"""
@ -440,6 +410,23 @@ class StateForm(ModelForm):
super(StateForm, self).__init__(*args, prefix=prefix, **kwargs)
class GroupForm(ModelForm):
""" Gestion des groupes d'un user"""
groups = forms.ModelMultipleChoiceField(
Group.objects.all(),
widget=forms.CheckboxSelectMultiple,
required=False
)
class Meta:
model = User
fields = ['groups']
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(GroupForm, self).__init__(*args, prefix=prefix, **kwargs)
class SchoolForm(ModelForm):
"""Edition, creation d'un école"""
class Meta:
@ -455,14 +442,20 @@ class SchoolForm(ModelForm):
class ListRightForm(ModelForm):
"""Edition, d'un groupe , équivalent à un droit
Ne peremet pas d'editer le gid, car il sert de primary key"""
permissions = forms.ModelMultipleChoiceField(
Permission.objects.all(),
widget=forms.CheckboxSelectMultiple,
required=False
)
class Meta:
model = ListRight
fields = ['listright', 'details']
fields = ['name', 'unix_name', 'permissions', 'details']
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(ListRightForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['listright'].label = 'Nom du droit/groupe'
self.fields['unix_name'].label = 'Nom du droit/groupe'
class NewListRightForm(ListRightForm):
@ -479,45 +472,35 @@ class NewListRightForm(ListRightForm):
class DelListRightForm(Form):
"""Suppression d'un ou plusieurs groupes"""
listrights = forms.ModelMultipleChoiceField(
queryset=ListRight.objects.all(),
queryset=ListRight.objects.none(),
label="Droits actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelListRightForm, self).__init__(*args, **kwargs)
if instances:
self.fields['listrights'].queryset = instances
else:
self.fields['listrights'].queryset = ListRight.objects.all()
class DelSchoolForm(Form):
"""Suppression d'une ou plusieurs écoles"""
schools = forms.ModelMultipleChoiceField(
queryset=School.objects.all(),
queryset=School.objects.none(),
label="Etablissements actuels",
widget=forms.CheckboxSelectMultiple
)
class RightForm(ModelForm):
"""Assignation d'un droit à un user"""
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(RightForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['right'].label = 'Droit'
self.fields['right'].empty_label = "Choisir un nouveau droit"
class Meta:
model = Right
fields = ['right']
class DelRightForm(Form):
"""Suppression d'un droit d'un user"""
rights = forms.ModelMultipleChoiceField(
queryset=Right.objects.select_related('user'),
widget=forms.CheckboxSelectMultiple
)
def __init__(self, right, *args, **kwargs):
super(DelRightForm, self).__init__(*args, **kwargs)
self.fields['rights'].queryset = Right.objects.select_related('user')\
.select_related('right').filter(right=right)
instances = kwargs.pop('instances', None)
super(DelSchoolForm, self).__init__(*args, **kwargs)
if instances:
self.fields['schools'].queryset = instances
else:
self.fields['schools'].queryset = School.objects.all()
class BanForm(ModelForm):
@ -531,14 +514,6 @@ class BanForm(ModelForm):
model = Ban
exclude = ['user']
def clean_date_end(self):
"""Verification que date_end est après now"""
date_end = self.cleaned_data['date_end']
if date_end < NOW:
raise forms.ValidationError("Triple buse, la date de fin ne peut\
pas être avant maintenant... Re2o ne voyage pas dans le temps")
return date_end
class WhitelistForm(ModelForm):
"""Creation, edition d'un objet whitelist"""
@ -550,11 +525,3 @@ class WhitelistForm(ModelForm):
class Meta:
model = Whitelist
exclude = ['user']
def clean_date_end(self):
"""Verification que la date_end est posterieur à now"""
date_end = self.cleaned_data['date_end']
if date_end < NOW:
raise forms.ValidationError("Triple buse, la date de fin ne peut pas\
être avant maintenant... Re2o ne voyage pas dans le temps")
return date_end

View file

@ -0,0 +1,32 @@
from django.core.management.base import BaseCommand, CommandError
from datetime import datetime, timedelta
from pytz
from users.models import User
UTC = pytz.timezone('UTC')
class Command(BaseCommand):
commands = ['email_remainder',]
args = '[command]'
help = 'Send email remainders'
def handle(self, *args, **options):
'''
Sends an email before the end of a user's subscription
'''
users = User.objects.filter(state="STATE_ACTIVE")
for user in users:
remaining = user.end_adhesion() - datetime.today(tz=UTC)
if (timedelta(weeks=4) - remaining).days == 1:
4_weeks_reminder()
elif (timedelta(weeks=1) - remaining).days == 1:
week_reminder()
elif remaining.days == 1:
last_day_reminder()
def month_reminder():
pass

View file

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-30 19:33
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('auth', '0008_alter_user_username_max_length'),
('users', '0060_auto_20171120_0317'),
]
operations = [
migrations.AddField(
model_name='user',
name='groups',
field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'),
),
migrations.AddField(
model_name='user',
name='is_superuser',
field=models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status'),
),
migrations.AddField(
model_name='user',
name='user_permissions',
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
),
]

View file

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-30 23:56
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('auth', '0008_alter_user_username_max_length'),
('users', '0061_auto_20171230_2033'),
]
def create_groups(apps, schema_editor):
group = apps.get_model("auth", "Group")
listrights = apps.get_model("users", "ListRight")
db_alias = schema_editor.connection.alias
for gr in listrights.objects.using(db_alias).all():
grp = group()
grp.name=gr.unix_name
grp.save()
gr.group_ptr=grp
gr.save()
def delete_groups(apps, schema_editor):
group = apps.get_model("auth", "Group")
db_alias = schema_editor.connection.alias
group.objects.using(db_alias).all().delete()
operations = [
migrations.RenameField(
model_name='listright',
old_name='listright',
new_name='unix_name',
),
migrations.AddField(
model_name='listright',
name='group_ptr',
field=models.OneToOneField(blank=True, null=True, auto_created=True, on_delete=django.db.models.deletion.CASCADE, serialize=False, to='auth.Group'),
preserve_default=False,
),
migrations.RunPython(create_groups, delete_groups),
]

View file

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 00:40
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0062_auto_20171231_0056'),
]
def transfer_right(apps, schema_editor):
rights = apps.get_model("users", "Right")
db_alias = schema_editor.connection.alias
for rg in rights.objects.using(db_alias).all():
group = rg.right
u=rg.user
u.groups.add(group.group_ptr)
u.save()
def untransfer_right(apps, schema_editor):
return
operations = [
migrations.RunPython(transfer_right, untransfer_right),
]

View file

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 00:50
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0063_auto_20171231_0140'),
]
operations = [
migrations.AlterUniqueTogether(
name='right',
unique_together=set([]),
),
migrations.RemoveField(
model_name='right',
name='right',
),
migrations.RemoveField(
model_name='right',
name='user',
),
migrations.DeleteModel(
name='Right',
),
migrations.RemoveField(
model_name='listright',
name='id',
),
migrations.AlterField(
model_name='listright',
name='group_ptr',
field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='auth.Group'),
),
]

View file

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 19:53
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('users', '0064_auto_20171231_0150'),
]
operations = [
migrations.AlterModelOptions(
name='ban',
options={'permissions': (('view_ban', "Peut voir un objet ban quelqu'il soit"),)},
),
migrations.AlterModelOptions(
name='listright',
options={'permissions': (('view_listright', 'Peut voir un objet Group/ListRight'),)},
),
migrations.AlterModelOptions(
name='school',
options={'permissions': (('view_school', 'Peut voir un objet school'),)},
),
migrations.AlterModelOptions(
name='serviceuser',
options={'permissions': (('view_serviceuser', 'Peut voir un objet serviceuser'),)},
),
migrations.AlterModelOptions(
name='user',
options={'permissions': (('change_user_password', "Peut changer le mot de passe d'un user"), ('change_user_state', "Peut éditer l'etat d'un user"), ('change_user_force', 'Peut forcer un déménagement'), ('change_user_shell', "Peut éditer le shell d'un user"), ('change_user_groups', "Peut éditer les groupes d'un user ! Permission critique"), ('view_user', 'Peut voir un objet user quelquonque'))},
),
migrations.AlterModelOptions(
name='whitelist',
options={'permissions': (('view_whitelist', 'Peut voir un objet whitelist'),)},
),
]

View file

@ -0,0 +1,254 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 19:53
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('users', '0065_auto_20171231_2053'),
('cotisations', '0028_auto_20171231_0007'),
('machines', '0071_auto_20171231_2100'),
('preferences', '0025_auto_20171231_2142'),
('topologie', '0033_auto_20171231_1743'),
]
def transfer_permissions(apps, schema_editor):
permission_groups = {'bofh': ['add_ban',
'change_ban',
'delete_ban',
'view_ban',
'add_club',
'change_club',
'delete_club',
'add_user',
'change_user',
'change_user_force',
'change_user_password',
'change_user_shell',
'view_user',
'add_whitelist',
'change_whitelist',
'delete_whitelist',
'view_whitelist'],
'bureau': ['add_logentry',
'change_logentry',
'delete_logentry',
'add_group',
'change_group',
'delete_group',
'add_permission',
'change_permission',
'delete_permission',
'add_adherent',
'change_adherent',
'delete_adherent',
'add_ban',
'change_ban',
'delete_ban',
'view_ban',
'add_club',
'change_club',
'delete_club',
'add_listright',
'change_listright',
'delete_listright',
'view_listright',
'add_school',
'change_school',
'delete_school',
'view_school',
'add_user',
'change_user',
'change_user_force',
'change_user_groups',
'change_user_password',
'change_user_shell',
'change_user_state',
'delete_user',
'view_user',
'add_whitelist',
'change_whitelist',
'delete_whitelist',
'view_whitelist'],
'cableur': ['add_logentry',
'view_article',
'add_banque',
'change_banque',
'delete_banque',
'view_banque',
'add_cotisation',
'change_cotisation',
'delete_cotisation',
'view_cotisation',
'add_facture',
'can_create',
'can_delete',
'can_edit',
'can_view',
'can_view_all',
'change_facture',
'delete_facture',
'view_facture',
'view_paiement',
'add_vente',
'change_vente',
'delete_vente',
'view_vente',
'add_domain',
'change_domain',
'delete_domain',
'view_domain',
'use_all_extension',
'view_extension',
'add_interface',
'change_interface',
'delete_interface',
'view_interface',
'view_iplist',
'view_iptype',
'add_machine',
'change_machine',
'view_machine',
'view_machinetype',
'view_mx',
'view_nas',
'view_ns',
'view_ouvertureportlist',
'view_service',
'view_soa',
'view_soa',
'view_txt',
'view_vlan',
'view_assooption',
'view_generaloption',
'view_mailmessageoption',
'view_optionalmachine',
'view_optionaltopologie',
'view_optionaluser',
'view_service',
'view_constructorswitch',
'view_modelswitch',
'view_port',
'view_room',
'view_stack',
'view_switch',
'add_adherent',
'change_adherent',
'view_ban',
'add_club',
'change_club',
'view_listright',
'add_school',
'change_school',
'delete_school',
'view_school',
'view_serviceuser',
'add_user',
'change_user',
'change_user_force',
'change_user_password',
'view_user',
'add_whitelist',
'change_whitelist',
'delete_whitelist',
'view_whitelist'],
'tresorier': ['add_article',
'change_article',
'delete_article',
'view_article',
'add_banque',
'change_banque',
'delete_banque',
'view_banque',
'add_cotisation',
'change_all_cotisation',
'change_cotisation',
'delete_cotisation',
'view_cotisation',
'add_facture',
'can_change_control',
'can_change_pdf',
'can_create',
'can_delete',
'can_edit',
'can_view',
'can_view_all',
'change_all_facture',
'change_facture',
'change_facture_control',
'change_facture_pdf',
'delete_facture',
'view_facture',
'add_paiement',
'change_paiement',
'delete_paiement',
'view_paiement',
'add_vente',
'change_all_vente',
'change_vente',
'delete_vente',
'view_vente'],
'admin': ['add_logentry',
'change_logentry',
'delete_logentry',
'add_assooption',
'change_assooption',
'delete_assooption',
'view_assooption',
'add_generaloption',
'change_generaloption',
'delete_generaloption',
'view_generaloption',
'add_mailmessageoption',
'change_mailmessageoption',
'delete_mailmessageoption',
'view_mailmessageoption',
'add_optionalmachine',
'change_optionalmachine',
'delete_optionalmachine',
'view_optionalmachine',
'add_optionaltopologie',
'change_optionaltopologie',
'delete_optionaltopologie',
'view_optionaltopologie',
'add_optionaluser',
'change_optionaluser',
'delete_optionaluser',
'view_optionaluser',
'add_service',
'add_services',
'change_service',
'change_services',
'delete_service',
'delete_services',
'view_service']}
rights = apps.get_model("users", "ListRight")
permissions = apps.get_model("auth", "Permission")
groups = apps.get_model("auth", "Group")
db_alias = schema_editor.connection.alias
for group in permission_groups:
lr_object = rights.objects.using(db_alias).filter(unix_name=group).first()
if not lr_object:
last = rights.objects.using(db_alias).all().order_by('gid').last()
if last:
gid = last.gid + 1
else:
gid = 501
group_object = groups.objects.using(db_alias).create(name=group)
lr_object = rights.objects.using(db_alias).create(unix_name=group, gid=gid, group_ptr=group_object)
lr_object = lr_object.group_ptr
for permission in permission_groups[group]:
perm = permissions.objects.using(db_alias).filter(codename=permission).first()
if perm:
lr_object.permissions.add(perm)
lr_object.save()
def untransfer_permissions(apps, schema_editor):
return
operations = [
migrations.RunPython(transfer_permissions, untransfer_permissions),
]

Some files were not shown because too many files have changed in this diff Show more