mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-23 03:43:12 +00:00
Merge branch 'master' into ouverture_des_ports
This commit is contained in:
commit
7e0d805256
64 changed files with 5478 additions and 1536 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,3 +4,4 @@ settings_local.py
|
|||
re2o.png
|
||||
__pycache__/*
|
||||
static_files/*
|
||||
static/logo/*
|
||||
|
|
|
@ -28,23 +28,37 @@ from reversion.admin import VersionAdmin
|
|||
|
||||
from .models import Facture, Article, Banque, Paiement, Cotisation, Vente
|
||||
|
||||
|
||||
class FactureAdmin(VersionAdmin):
|
||||
list_display = ('user','paiement','date','valid','control')
|
||||
"""Class admin d'une facture, tous les champs"""
|
||||
pass
|
||||
|
||||
|
||||
class VenteAdmin(VersionAdmin):
|
||||
list_display = ('facture','name','prix','number','iscotisation','duration')
|
||||
"""Class admin d'une vente, tous les champs (facture related)"""
|
||||
pass
|
||||
|
||||
|
||||
class ArticleAdmin(VersionAdmin):
|
||||
list_display = ('name','prix','iscotisation','duration')
|
||||
"""Class admin d'un article en vente"""
|
||||
pass
|
||||
|
||||
|
||||
class BanqueAdmin(VersionAdmin):
|
||||
list_display = ('name',)
|
||||
"""Class admin de la liste des banques (facture related)"""
|
||||
pass
|
||||
|
||||
|
||||
class PaiementAdmin(VersionAdmin):
|
||||
list_display = ('moyen','type_paiement')
|
||||
"""Class admin d'un moyen de paiement (facture related"""
|
||||
pass
|
||||
|
||||
|
||||
class CotisationAdmin(VersionAdmin):
|
||||
list_display = ('vente','date_start','date_end')
|
||||
"""Class admin d'une cotisation (date de debut et de fin),
|
||||
Vente related"""
|
||||
pass
|
||||
|
||||
|
||||
admin.site.register(Facture, FactureAdmin)
|
||||
admin.site.register(Article, ArticleAdmin)
|
||||
|
|
|
@ -19,24 +19,44 @@
|
|||
# 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.
|
||||
"""
|
||||
Forms de l'application cotisation de re2o. Dépendance avec les models,
|
||||
importé par les views.
|
||||
|
||||
Permet de créer une nouvelle facture pour un user (NewFactureForm),
|
||||
et de l'editer (soit l'user avec EditFactureForm,
|
||||
soit le trésorier avec TrezEdit qui a plus de possibilités que self
|
||||
notamment sur le controle trésorier)
|
||||
|
||||
SelectArticleForm est utilisée lors de la creation d'une facture en
|
||||
parrallèle de NewFacture pour le choix des articles désirés.
|
||||
(la vue correspondante est unique)
|
||||
|
||||
ArticleForm, BanqueForm, PaiementForm permettent aux admin d'ajouter,
|
||||
éditer ou supprimer une banque/moyen de paiement ou un article
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.forms import ModelForm, Form
|
||||
from django import forms
|
||||
from django.core.validators import MinValueValidator
|
||||
from .models import Article, Paiement, Facture, Banque, Vente
|
||||
from .models import Article, Paiement, Facture, Banque
|
||||
|
||||
|
||||
class NewFactureForm(ModelForm):
|
||||
"""Creation d'une facture, moyen de paiement, banque et numero
|
||||
de cheque"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NewFactureForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(NewFactureForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['cheque'].required = False
|
||||
self.fields['banque'].required = False
|
||||
self.fields['cheque'].label = 'Numero de chèque'
|
||||
self.fields['banque'].empty_label = "Non renseigné"
|
||||
self.fields['paiement'].empty_label = "Séléctionner un moyen de paiement"
|
||||
self.fields['paiement'].widget.attrs['data-cheque'] = Paiement.objects.filter(type_paiement=1).first().id
|
||||
self.fields['paiement'].empty_label = "Séléctionner\
|
||||
un moyen de paiement"
|
||||
self.fields['paiement'].widget.attrs['data-cheque'] = Paiement.objects\
|
||||
.filter(type_paiement=1).first().id
|
||||
|
||||
class Meta:
|
||||
model = Facture
|
||||
|
@ -48,45 +68,76 @@ class NewFactureForm(ModelForm):
|
|||
cheque = cleaned_data.get("cheque")
|
||||
banque = cleaned_data.get("banque")
|
||||
if not paiement:
|
||||
raise forms.ValidationError("Le moyen de paiement est obligatoire.")
|
||||
raise forms.ValidationError("Le moyen de paiement est obligatoire")
|
||||
elif paiement.type_paiement == "check" and not (cheque and banque):
|
||||
raise forms.ValidationError("Le numéro de chèque et la banque sont obligatoires.")
|
||||
raise forms.ValidationError("Le numéro de chèque et\
|
||||
la banque sont obligatoires.")
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class CreditSoldeForm(NewFactureForm):
|
||||
"""Permet de faire des opérations sur le solde si il est activé"""
|
||||
class Meta(NewFactureForm.Meta):
|
||||
model = Facture
|
||||
fields = ['paiement', 'banque', 'cheque']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreditSoldeForm, self).__init__(*args, **kwargs)
|
||||
self.fields['paiement'].queryset = Paiement.objects.exclude(moyen='solde').exclude(moyen="Solde")
|
||||
|
||||
self.fields['paiement'].queryset = Paiement.objects.exclude(
|
||||
moyen='solde'
|
||||
).exclude(moyen="Solde")
|
||||
|
||||
montant = forms.DecimalField(max_digits=5, decimal_places=2, required=True)
|
||||
|
||||
|
||||
class SelectArticleForm(Form):
|
||||
article = forms.ModelChoiceField(queryset=Article.objects.all(), label="Article", required=True)
|
||||
quantity = forms.IntegerField(label="Quantité", validators=[MinValueValidator(1)], required=True)
|
||||
"""Selection d'un article lors de la creation d'une facture"""
|
||||
article = forms.ModelChoiceField(
|
||||
queryset=Article.objects.all(),
|
||||
label="Article",
|
||||
required=True
|
||||
)
|
||||
quantity = forms.IntegerField(
|
||||
label="Quantité",
|
||||
validators=[MinValueValidator(1)],
|
||||
required=True
|
||||
)
|
||||
|
||||
|
||||
class NewFactureFormPdf(Form):
|
||||
article = forms.ModelMultipleChoiceField(queryset=Article.objects.all(), label="Article")
|
||||
number = forms.IntegerField(label="Quantité", validators=[MinValueValidator(1)])
|
||||
"""Creation d'un pdf facture par le trésorier"""
|
||||
article = forms.ModelMultipleChoiceField(
|
||||
queryset=Article.objects.all(),
|
||||
label="Article"
|
||||
)
|
||||
number = forms.IntegerField(
|
||||
label="Quantité",
|
||||
validators=[MinValueValidator(1)]
|
||||
)
|
||||
paid = forms.BooleanField(label="Payé", required=False)
|
||||
dest = forms.CharField(required=True, max_length=255, label="Destinataire")
|
||||
chambre = forms.CharField(required=False, max_length=10, label="Adresse")
|
||||
fid = forms.CharField(required=True, max_length=10, label="Numéro de la facture")
|
||||
fid = forms.CharField(
|
||||
required=True,
|
||||
max_length=10,
|
||||
label="Numéro de la facture"
|
||||
)
|
||||
|
||||
|
||||
class EditFactureForm(NewFactureForm):
|
||||
"""Edition d'une facture : moyen de paiement, banque, user parent"""
|
||||
class Meta(NewFactureForm.Meta):
|
||||
fields = ['paiement', 'banque', 'cheque', 'user']
|
||||
|
||||
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"
|
||||
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__'
|
||||
|
||||
|
@ -97,38 +148,67 @@ class TrezEditFactureForm(EditFactureForm):
|
|||
|
||||
|
||||
class ArticleForm(ModelForm):
|
||||
"""Creation d'un article. Champs : nom, cotisation, durée"""
|
||||
class Meta:
|
||||
model = Article
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ArticleForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(ArticleForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['name'].label = "Désignation de l'article"
|
||||
|
||||
|
||||
class DelArticleForm(Form):
|
||||
articles = forms.ModelMultipleChoiceField(queryset=Article.objects.all(), label="Articles actuels", widget=forms.CheckboxSelectMultiple)
|
||||
"""Suppression d'un ou plusieurs articles en vente. Choix
|
||||
parmis les modèles"""
|
||||
articles = forms.ModelMultipleChoiceField(
|
||||
queryset=Article.objects.all(),
|
||||
label="Articles actuels",
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
||||
class PaiementForm(ModelForm):
|
||||
"""Creation d'un moyen de paiement, champ text moyen et type
|
||||
permettant d'indiquer si il s'agit d'un chèque ou non pour le form"""
|
||||
class Meta:
|
||||
model = Paiement
|
||||
fields = ['moyen', 'type_paiement']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PaiementForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(PaiementForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['moyen'].label = 'Moyen de paiement à ajouter'
|
||||
self.fields['type_paiement'].label = 'Type de paiement à ajouter'
|
||||
|
||||
|
||||
class DelPaiementForm(Form):
|
||||
paiements = forms.ModelMultipleChoiceField(queryset=Paiement.objects.all(), label="Moyens de paiement actuels", widget=forms.CheckboxSelectMultiple)
|
||||
"""Suppression d'un ou plusieurs moyens de paiements, selection
|
||||
parmis les models"""
|
||||
paiements = forms.ModelMultipleChoiceField(
|
||||
queryset=Paiement.objects.all(),
|
||||
label="Moyens de paiement actuels",
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
||||
class BanqueForm(ModelForm):
|
||||
"""Creation d'une banque, field name"""
|
||||
class Meta:
|
||||
model = Banque
|
||||
fields = ['name']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(BanqueForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(BanqueForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['name'].label = 'Banque à ajouter'
|
||||
|
||||
|
||||
class DelBanqueForm(Form):
|
||||
banques = forms.ModelMultipleChoiceField(queryset=Banque.objects.all(), label="Banques actuelles", widget=forms.CheckboxSelectMultiple)
|
||||
"""Selection d'une ou plusieurs banques, pour suppression"""
|
||||
banques = forms.ModelMultipleChoiceField(
|
||||
queryset=Banque.objects.all(),
|
||||
label="Banques actuelles",
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
|
|
@ -20,59 +20,111 @@
|
|||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
"""
|
||||
Definition des models bdd pour les factures et cotisation.
|
||||
Pièce maitresse : l'ensemble du code intelligent se trouve ici,
|
||||
dans les clean et save des models ainsi que de leur methodes supplémentaires.
|
||||
|
||||
Facture : reliée à un user, elle a un moyen de paiement, une banque (option),
|
||||
une ou plusieurs ventes
|
||||
|
||||
Article : liste des articles en vente, leur prix, etc
|
||||
|
||||
Vente : ensemble des ventes effectuées, reliées à une facture (foreignkey)
|
||||
|
||||
Banque : liste des banques existantes
|
||||
|
||||
Cotisation : objets de cotisation, contenant un début et une fin. Reliées
|
||||
aux ventes, en onetoone entre une vente et une cotisation.
|
||||
Crées automatiquement au save des ventes.
|
||||
|
||||
Post_save et Post_delete : sychronisation des services et régénération
|
||||
des services d'accès réseau (ex dhcp) lors de la vente d'une cotisation
|
||||
par exemple
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from django.db import models
|
||||
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
from django.dispatch import receiver
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from django.forms import ValidationError
|
||||
from django.core.validators import MinValueValidator
|
||||
|
||||
from django.db.models import Max
|
||||
from django.utils import timezone
|
||||
|
||||
from machines.models import regen
|
||||
|
||||
|
||||
class Facture(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
|
||||
valides et controle (trésorerie)"""
|
||||
PRETTY_NAME = "Factures émises"
|
||||
|
||||
user = models.ForeignKey('users.User', on_delete=models.PROTECT)
|
||||
paiement = models.ForeignKey('Paiement', on_delete=models.PROTECT)
|
||||
banque = models.ForeignKey('Banque', on_delete=models.PROTECT, blank=True, null=True)
|
||||
banque = models.ForeignKey(
|
||||
'Banque',
|
||||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True)
|
||||
cheque = models.CharField(max_length=255, blank=True)
|
||||
date = models.DateTimeField(auto_now_add=True)
|
||||
valid = models.BooleanField(default=True)
|
||||
control = models.BooleanField(default=False)
|
||||
|
||||
def prix(self):
|
||||
prix = Vente.objects.filter(facture=self).aggregate(models.Sum('prix'))['prix__sum']
|
||||
"""Renvoie le prix brut sans les quantités. Méthode
|
||||
dépréciée"""
|
||||
prix = Vente.objects.filter(
|
||||
facture=self
|
||||
).aggregate(models.Sum('prix'))['prix__sum']
|
||||
return prix
|
||||
|
||||
def prix_total(self):
|
||||
return Vente.objects.filter(facture=self).aggregate(total=models.Sum(models.F('prix')*models.F('number'), output_field=models.FloatField()))['total']
|
||||
"""Prix total : somme des produits prix_unitaire et quantité des
|
||||
ventes de l'objet"""
|
||||
return Vente.objects.filter(
|
||||
facture=self
|
||||
).aggregate(
|
||||
total=models.Sum(
|
||||
models.F('prix')*models.F('number'),
|
||||
output_field=models.FloatField()
|
||||
)
|
||||
)['total']
|
||||
|
||||
def name(self):
|
||||
name = ' - '.join(Vente.objects.filter(facture=self).values_list('name', flat=True))
|
||||
"""String, somme des name des ventes de self"""
|
||||
name = ' - '.join(Vente.objects.filter(
|
||||
facture=self
|
||||
).values_list('name', flat=True))
|
||||
return name
|
||||
|
||||
def __str__(self):
|
||||
return str(self.user) + ' ' + str(self.date)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Facture)
|
||||
def facture_post_save(sender, **kwargs):
|
||||
"""Post save d'une facture, synchronise l'user ldap"""
|
||||
facture = kwargs['instance']
|
||||
user = facture.user
|
||||
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=Facture)
|
||||
def facture_post_delete(sender, **kwargs):
|
||||
"""Après la suppression d'une facture, on synchronise l'user ldap"""
|
||||
user = kwargs['instance'].user
|
||||
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
||||
|
||||
|
||||
class Vente(models.Model):
|
||||
"""Objet vente, contient une quantité, une facture parente, un nom,
|
||||
un prix. Peut-être relié à un objet cotisation, via le boolean
|
||||
iscotisation"""
|
||||
PRETTY_NAME = "Ventes effectuées"
|
||||
|
||||
facture = models.ForeignKey('Facture', on_delete=models.CASCADE)
|
||||
|
@ -80,44 +132,67 @@ class Vente(models.Model):
|
|||
name = models.CharField(max_length=255)
|
||||
prix = models.DecimalField(max_digits=5, decimal_places=2)
|
||||
iscotisation = models.BooleanField()
|
||||
duration = models.IntegerField(help_text="Durée exprimée en mois entiers", blank=True, null=True)
|
||||
duration = models.IntegerField(
|
||||
help_text="Durée exprimée en mois entiers",
|
||||
blank=True,
|
||||
null=True)
|
||||
|
||||
def prix_total(self):
|
||||
"""Renvoie le prix_total de self (nombre*prix)"""
|
||||
return self.prix*self.number
|
||||
|
||||
def update_cotisation(self):
|
||||
"""Mets à jour l'objet related cotisation de la vente, si
|
||||
il existe : update la date de fin à partir de la durée de
|
||||
la vente"""
|
||||
if hasattr(self, 'cotisation'):
|
||||
cotisation = self.cotisation
|
||||
cotisation.date_end = cotisation.date_start + relativedelta(months=self.duration*self.number)
|
||||
cotisation.date_end = cotisation.date_start + relativedelta(
|
||||
months=self.duration*self.number)
|
||||
return
|
||||
|
||||
def create_cotis(self, date_start=False):
|
||||
""" Update et crée l'objet cotisation associé à une facture, prend en argument l'user, la facture pour la quantitéi, et l'article pour la durée"""
|
||||
"""Update et crée l'objet cotisation associé à une facture, prend
|
||||
en argument l'user, la facture pour la quantitéi, et l'article pour
|
||||
la durée"""
|
||||
if not hasattr(self, 'cotisation'):
|
||||
cotisation = Cotisation(vente=self)
|
||||
if date_start:
|
||||
end_adhesion = Cotisation.objects.filter(vente__in=Vente.objects.filter(facture__in=Facture.objects.filter(user=self.facture.user).exclude(valid=False))).filter(date_start__lt=date_start).aggregate(Max('date_end'))['date_end__max']
|
||||
end_adhesion = Cotisation.objects.filter(
|
||||
vente__in=Vente.objects.filter(
|
||||
facture__in=Facture.objects.filter(
|
||||
user=self.facture.user
|
||||
).exclude(valid=False))
|
||||
).filter(
|
||||
date_start__lt=date_start
|
||||
).aggregate(Max('date_end'))['date_end__max']
|
||||
else:
|
||||
end_adhesion = self.facture.user.end_adhesion()
|
||||
date_start = date_start or timezone.now()
|
||||
end_adhesion = end_adhesion or date_start
|
||||
date_max = max(end_adhesion, date_start)
|
||||
cotisation.date_start = date_max
|
||||
cotisation.date_end = cotisation.date_start + relativedelta(months=self.duration*self.number)
|
||||
cotisation.date_end = cotisation.date_start + relativedelta(
|
||||
months=self.duration*self.number
|
||||
)
|
||||
return
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# On verifie que si iscotisation, duration est présent
|
||||
if self.iscotisation and not self.duration:
|
||||
raise ValidationError("Cotisation et durée doivent être présents ensembles")
|
||||
raise ValidationError("Cotisation et durée doivent être présents\
|
||||
ensembles")
|
||||
self.update_cotisation()
|
||||
super(Vente, self).save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name) + ' ' + str(self.facture)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Vente)
|
||||
def vente_post_save(sender, **kwargs):
|
||||
"""Post save d'une vente, déclencge la création de l'objet cotisation
|
||||
si il y a lieu(si iscotisation) """
|
||||
vente = kwargs['instance']
|
||||
if hasattr(vente, 'cotisation'):
|
||||
vente.cotisation.vente = vente
|
||||
|
@ -128,14 +203,20 @@ def vente_post_save(sender, **kwargs):
|
|||
user = vente.facture.user
|
||||
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=Vente)
|
||||
def vente_post_delete(sender, **kwargs):
|
||||
"""Après suppression d'une vente, on synchronise l'user ldap (ex
|
||||
suppression d'une cotisation"""
|
||||
vente = kwargs['instance']
|
||||
if vente.iscotisation:
|
||||
user = vente.facture.user
|
||||
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
||||
|
||||
|
||||
class Article(models.Model):
|
||||
"""Liste des articles en vente : prix, nom, et attribut iscotisation
|
||||
et duree si c'est une cotisation"""
|
||||
PRETTY_NAME = "Articles en vente"
|
||||
|
||||
name = models.CharField(max_length=255, unique=True)
|
||||
|
@ -154,7 +235,9 @@ class Article(models.Model):
|
|||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Banque(models.Model):
|
||||
"""Liste des banques"""
|
||||
PRETTY_NAME = "Banques enregistrées"
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
|
@ -162,7 +245,9 @@ class Banque(models.Model):
|
|||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Paiement(models.Model):
|
||||
"""Moyens de paiement"""
|
||||
PRETTY_NAME = "Moyens de paiement"
|
||||
PAYMENT_TYPES = (
|
||||
(0, 'Autre'),
|
||||
|
@ -179,11 +264,15 @@ class Paiement(models.Model):
|
|||
self.moyen = self.moyen.title()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Un seul type de paiement peut-etre cheque..."""
|
||||
if Paiement.objects.filter(type_paiement=1).count() > 1:
|
||||
raise ValidationError("On ne peut avoir plusieurs mode de paiement chèque")
|
||||
raise ValidationError("On ne peut avoir plusieurs mode de paiement\
|
||||
chèque")
|
||||
super(Paiement, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
class Cotisation(models.Model):
|
||||
"""Objet cotisation, debut et fin, relié en onetoone à une vente"""
|
||||
PRETTY_NAME = "Cotisations"
|
||||
|
||||
vente = models.OneToOneField('Vente', on_delete=models.CASCADE, null=True)
|
||||
|
@ -193,15 +282,19 @@ class Cotisation(models.Model):
|
|||
def __str__(self):
|
||||
return str(self.vente)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Cotisation)
|
||||
def cotisation_post_save(sender, **kwargs):
|
||||
"""Après modification d'une cotisation, regeneration des services"""
|
||||
regen('dns')
|
||||
regen('dhcp')
|
||||
regen('mac_ip_list')
|
||||
regen('mailing')
|
||||
|
||||
|
||||
@receiver(post_delete, sender=Cotisation)
|
||||
def vente_post_delete(sender, **kwargs):
|
||||
"""Après suppression d'une vente, régénération des services"""
|
||||
cotisation = kwargs['instance']
|
||||
regen('mac_ip_list')
|
||||
regen('mailing')
|
||||
|
|
|
@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load bootstrap3 %}
|
||||
{% load staticfiles%}
|
||||
{% load massive_bootstrap_form %}
|
||||
|
||||
{% block title %}Création et modification de factures{% endblock %}
|
||||
|
||||
|
@ -34,7 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
<h3>Editer la facture</h3>
|
||||
{% bootstrap_form factureform %}
|
||||
{% massive_bootstrap_form factureform 'user' %}
|
||||
{{ venteform.management_form }}
|
||||
<h3>Articles de la facture</h3>
|
||||
<table class="table table-striped">
|
||||
|
|
|
@ -38,18 +38,20 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{{ venteform.management_form }}
|
||||
<!-- TODO: FIXME to include data-type="check" for right option in id_cheque select -->
|
||||
<h3>Articles de la facture</h3>
|
||||
<div id="form_set">
|
||||
<div id="form_set" class="form-group">
|
||||
{% for form in venteform.forms %}
|
||||
<div class='product_to_sell'>
|
||||
<p>
|
||||
{{ form.as_table }}
|
||||
</p>
|
||||
<div class='product_to_sell form-inline'>
|
||||
Article :
|
||||
{% bootstrap_form form label_class='sr-only' %}
|
||||
|
||||
<button class="btn btn-danger btn-sm"
|
||||
id="id_form-0-article-remove" type="button">
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<p>
|
||||
<input class="btn btn-primary btn-sm" role="button" value="Ajouter un article" id="add_one">
|
||||
</p>
|
||||
<p>
|
||||
Prix total : <span id="total_price">0,00</span> €
|
||||
</p>
|
||||
|
@ -63,19 +65,23 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
prices[{{ article.id|escapejs }}] = {{ article.prix }};
|
||||
{% endfor %}
|
||||
|
||||
var template = `<p>{{ venteform.empty_form.as_table }}</p>`;
|
||||
var template = `Article :
|
||||
{% bootstrap_form venteform.empty_form label_class='sr-only' %}
|
||||
|
||||
<button class="btn btn-danger btn-sm"
|
||||
id="id_form-__prefix__-article-remove" type="button">
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
</button>`
|
||||
|
||||
function add_article(){
|
||||
// Index start at 0 => new_index = number of items
|
||||
var new_index =
|
||||
document.getElementsByClassName('product_to_sell').length;
|
||||
document.getElementById('id_form-TOTAL_FORMS').value =
|
||||
parseInt(document.getElementById('id_form-TOTAL_FORMS').value) + 1;
|
||||
document.getElementById('id_form-TOTAL_FORMS').value ++;
|
||||
var new_article = document.createElement('div');
|
||||
new_article.className = 'product_to_sell';
|
||||
new_article.className = 'product_to_sell form-inline';
|
||||
new_article.innerHTML = template.replace(/__prefix__/g, new_index);
|
||||
document.getElementById('form_set')
|
||||
.appendChild(new_article);
|
||||
document.getElementById('form_set').appendChild(new_article);
|
||||
add_listenner_for_id(new_index);
|
||||
}
|
||||
|
||||
|
@ -106,18 +112,28 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
.addEventListener("onkeypress", update_price, true);
|
||||
document.getElementById('id_form-' + i.toString() + '-quantity')
|
||||
.addEventListener("change", update_price, true);
|
||||
document.getElementById('id_form-' + i.toString() + '-article-remove')
|
||||
.addEventListener("click", function(event) {
|
||||
var article = event.target.parentNode;
|
||||
article.parentNode.removeChild(article);
|
||||
document.getElementById('id_form-TOTAL_FORMS').value --;
|
||||
update_price();
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function set_cheque_info_visibility() {
|
||||
var visible = document.getElementById("id_paiement").value == document.getElementById("id_paiement").getAttribute('data-cheque');
|
||||
p = document.getElementById("id_paiement")
|
||||
console.log(p);
|
||||
var paiement = document.getElementById("id_Facture-paiement");
|
||||
var visible = paiement.value == paiement.getAttribute('data-cheque');
|
||||
p = document.getElementById("id_Facture-paiement");
|
||||
var display = 'none';
|
||||
if (visible) {
|
||||
display = 'block';
|
||||
}
|
||||
document.getElementById("id_cheque").parentNode.style.display = display;
|
||||
document.getElementById("id_banque").parentNode.style.display = display;
|
||||
document.getElementById("id_Facture-cheque")
|
||||
.parentNode.style.display = display;
|
||||
document.getElementById("id_Facture-banque")
|
||||
.parentNode.style.display = display;
|
||||
}
|
||||
|
||||
// Add events manager when DOM is fully loaded
|
||||
|
@ -129,7 +145,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
for (i = 0; i < product_count; ++i){
|
||||
add_listenner_for_id(i);
|
||||
}
|
||||
document.getElementById("id_paiement")
|
||||
document.getElementById("id_Facture-paiement")
|
||||
.addEventListener("change", set_cheque_info_visibility, true);
|
||||
set_cheque_info_visibility();
|
||||
update_price();
|
||||
|
|
|
@ -27,30 +27,96 @@ from django.conf.urls import url
|
|||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^new_facture/(?P<userid>[0-9]+)$', views.new_facture, name='new-facture'),
|
||||
url(r'^edit_facture/(?P<factureid>[0-9]+)$', views.edit_facture, name='edit-facture'),
|
||||
url(r'^del_facture/(?P<factureid>[0-9]+)$', views.del_facture, name='del-facture'),
|
||||
url(r'^facture_pdf/(?P<factureid>[0-9]+)$', views.facture_pdf, name='facture-pdf'),
|
||||
url(r'^new_facture_pdf/$', views.new_facture_pdf, name='new-facture-pdf'),
|
||||
url(r'^credit_solde/(?P<userid>[0-9]+)$', views.credit_solde, name='credit-solde'),
|
||||
url(r'^add_article/$', views.add_article, name='add-article'),
|
||||
url(r'^edit_article/(?P<articleid>[0-9]+)$', views.edit_article, name='edit-article'),
|
||||
url(r'^del_article/$', views.del_article, name='del-article'),
|
||||
url(r'^add_paiement/$', views.add_paiement, name='add-paiement'),
|
||||
url(r'^edit_paiement/(?P<paiementid>[0-9]+)$', views.edit_paiement, name='edit-paiement'),
|
||||
url(r'^del_paiement/$', views.del_paiement, name='del-paiement'),
|
||||
url(r'^add_banque/$', views.add_banque, name='add-banque'),
|
||||
url(r'^edit_banque/(?P<banqueid>[0-9]+)$', views.edit_banque, name='edit-banque'),
|
||||
url(r'^del_banque/$', views.del_banque, name='del-banque'),
|
||||
url(r'^index_article/$', views.index_article, name='index-article'),
|
||||
url(r'^index_banque/$', views.index_banque, name='index-banque'),
|
||||
url(r'^index_paiement/$', views.index_paiement, name='index-paiement'),
|
||||
url(r'^history/(?P<object>facture)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||
url(r'^history/(?P<object>article)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||
url(r'^history/(?P<object>paiement)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||
url(r'^history/(?P<object>banque)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||
url(r'^control/$', views.control, name='control'),
|
||||
url(r'^new_facture/(?P<userid>[0-9]+)$',
|
||||
views.new_facture,
|
||||
name='new-facture'
|
||||
),
|
||||
url(r'^edit_facture/(?P<factureid>[0-9]+)$',
|
||||
views.edit_facture,
|
||||
name='edit-facture'
|
||||
),
|
||||
url(r'^del_facture/(?P<factureid>[0-9]+)$',
|
||||
views.del_facture,
|
||||
name='del-facture'
|
||||
),
|
||||
url(r'^facture_pdf/(?P<factureid>[0-9]+)$',
|
||||
views.facture_pdf,
|
||||
name='facture-pdf'
|
||||
),
|
||||
url(r'^new_facture_pdf/$',
|
||||
views.new_facture_pdf,
|
||||
name='new-facture-pdf'
|
||||
),
|
||||
url(r'^credit_solde/(?P<userid>[0-9]+)$',
|
||||
views.credit_solde,
|
||||
name='credit-solde'
|
||||
),
|
||||
url(r'^add_article/$',
|
||||
views.add_article,
|
||||
name='add-article'
|
||||
),
|
||||
url(r'^edit_article/(?P<articleid>[0-9]+)$',
|
||||
views.edit_article,
|
||||
name='edit-article'
|
||||
),
|
||||
url(r'^del_article/$',
|
||||
views.del_article,
|
||||
name='del-article'
|
||||
),
|
||||
url(r'^add_paiement/$',
|
||||
views.add_paiement,
|
||||
name='add-paiement'
|
||||
),
|
||||
url(r'^edit_paiement/(?P<paiementid>[0-9]+)$',
|
||||
views.edit_paiement,
|
||||
name='edit-paiement'
|
||||
),
|
||||
url(r'^del_paiement/$',
|
||||
views.del_paiement,
|
||||
name='del-paiement'
|
||||
),
|
||||
url(r'^add_banque/$',
|
||||
views.add_banque,
|
||||
name='add-banque'
|
||||
),
|
||||
url(r'^edit_banque/(?P<banqueid>[0-9]+)$',
|
||||
views.edit_banque,
|
||||
name='edit-banque'
|
||||
),
|
||||
url(r'^del_banque/$',
|
||||
views.del_banque,
|
||||
name='del-banque'
|
||||
),
|
||||
url(r'^index_article/$',
|
||||
views.index_article,
|
||||
name='index-article'
|
||||
),
|
||||
url(r'^index_banque/$',
|
||||
views.index_banque,
|
||||
name='index-banque'
|
||||
),
|
||||
url(r'^index_paiement/$',
|
||||
views.index_paiement,
|
||||
name='index-paiement'
|
||||
),
|
||||
url(r'^history/(?P<object>facture)/(?P<id>[0-9]+)$',
|
||||
views.history,
|
||||
name='history'
|
||||
),
|
||||
url(r'^history/(?P<object>article)/(?P<id>[0-9]+)$',
|
||||
views.history,
|
||||
name='history'
|
||||
),
|
||||
url(r'^history/(?P<object>paiement)/(?P<id>[0-9]+)$',
|
||||
views.history,
|
||||
name='history'),
|
||||
url(r'^history/(?P<object>banque)/(?P<id>[0-9]+)$',
|
||||
views.history,
|
||||
name='history'
|
||||
),
|
||||
url(r'^control/$',
|
||||
views.control,
|
||||
name='control'
|
||||
),
|
||||
url(r'^$', views.index, name='index'),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -24,40 +24,41 @@
|
|||
# Goulven Kermarec, Gabriel Détraz
|
||||
# Gplv2
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
from django.shortcuts import render, redirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template.context_processors import csrf
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.template import Context, RequestContext, loader
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.contrib import messages
|
||||
from django.db.models import Max, ProtectedError
|
||||
from django.db.models import ProtectedError
|
||||
from django.db import transaction
|
||||
from django.forms import modelformset_factory, formset_factory
|
||||
import os
|
||||
from django.utils import timezone
|
||||
from reversion import revisions as reversion
|
||||
from reversion.models import Version
|
||||
|
||||
from .models import Facture, Article, Vente, Cotisation, Paiement, Banque
|
||||
from .forms import NewFactureForm, TrezEditFactureForm, EditFactureForm, ArticleForm, DelArticleForm, PaiementForm, DelPaiementForm, BanqueForm, DelBanqueForm, NewFactureFormPdf, CreditSoldeForm, SelectArticleForm
|
||||
# Import des models, forms et fonctions re2o
|
||||
from users.models import User
|
||||
from .tex import render_tex
|
||||
from re2o.settings import LOGO_PATH
|
||||
from re2o import settings
|
||||
from re2o.views import form
|
||||
from preferences.models import OptionalUser, AssoOption, GeneralOption
|
||||
from .models import Facture, Article, Vente, Paiement, Banque
|
||||
from .forms import NewFactureForm, TrezEditFactureForm, EditFactureForm
|
||||
from .forms import ArticleForm, DelArticleForm, PaiementForm, DelPaiementForm
|
||||
from .forms import BanqueForm, DelBanqueForm, NewFactureFormPdf
|
||||
from .forms import SelectArticleForm, CreditSoldeForm
|
||||
from .tex import render_tex
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from django.utils import timezone
|
||||
|
||||
def form(ctx, template, request):
|
||||
c = ctx
|
||||
c.update(csrf(request))
|
||||
return render(request, template, c)
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def new_facture(request, 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.
|
||||
Parse les article et boucle dans le formset puis save les ventes,
|
||||
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:
|
||||
|
@ -70,50 +71,80 @@ def new_facture(request, userid):
|
|||
facture_form = NewFactureForm(request.POST or None, instance=facture)
|
||||
article_formset = formset_factory(SelectArticleForm)(request.POST or None)
|
||||
if facture_form.is_valid() and article_formset.is_valid():
|
||||
new_facture = facture_form.save(commit=False)
|
||||
new_facture_instance = facture_form.save(commit=False)
|
||||
articles = article_formset
|
||||
# Si au moins un article est rempli
|
||||
if any(art.cleaned_data for art in articles):
|
||||
options, created = OptionalUser.objects.get_or_create()
|
||||
options, _created = OptionalUser.objects.get_or_create()
|
||||
user_solde = options.user_solde
|
||||
solde_negatif = options.solde_negatif
|
||||
# Si on paye par solde, que l'option est activée, on vérifie que le négatif n'est pas atteint
|
||||
# Si on paye par solde, que l'option est activée,
|
||||
# on vérifie que le négatif n'est pas atteint
|
||||
if user_solde:
|
||||
if new_facture.paiement == Paiement.objects.get_or_create(moyen='solde')[0]:
|
||||
if new_facture_instance.paiement == Paiement.objects.get_or_create(
|
||||
moyen='solde'
|
||||
)[0]:
|
||||
prix_total = 0
|
||||
for art_item in articles:
|
||||
if art_item.cleaned_data:
|
||||
prix_total += art_item.cleaned_data['article'].prix*art_item.cleaned_data['quantity']
|
||||
prix_total += art_item.cleaned_data['article']\
|
||||
.prix*art_item.cleaned_data['quantity']
|
||||
if float(user.solde) - float(prix_total) < solde_negatif:
|
||||
messages.error(request, "Le solde est insuffisant pour effectuer l'opération")
|
||||
messages.error(request, "Le solde est insuffisant pour\
|
||||
effectuer l'opération")
|
||||
return redirect("/users/profil/" + userid)
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
new_facture.save()
|
||||
new_facture_instance.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Création")
|
||||
for art_item in articles:
|
||||
if art_item.cleaned_data:
|
||||
article = art_item.cleaned_data['article']
|
||||
quantity = art_item.cleaned_data['quantity']
|
||||
new_vente = Vente.objects.create(facture=new_facture, name=article.name, prix=article.prix, iscotisation=article.iscotisation, duration=article.duration, number=quantity)
|
||||
new_vente = Vente.objects.create(
|
||||
facture=new_facture_instance,
|
||||
name=article.name,
|
||||
prix=article.prix,
|
||||
iscotisation=article.iscotisation,
|
||||
duration=article.duration,
|
||||
number=quantity
|
||||
)
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
new_vente.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Création")
|
||||
if any(art_item.cleaned_data['article'].iscotisation for art_item in articles if art_item.cleaned_data):
|
||||
messages.success(request, "La cotisation a été prolongée pour l'adhérent %s jusqu'au %s" % (user.pseudo, user.end_adhesion()) )
|
||||
if any(art_item.cleaned_data['article'].iscotisation
|
||||
for art_item in articles if art_item.cleaned_data):
|
||||
messages.success(
|
||||
request,
|
||||
"La cotisation a été prolongée\
|
||||
pour l'adhérent %s jusqu'au %s" % (
|
||||
user.pseudo, user.end_adhesion()
|
||||
)
|
||||
)
|
||||
else:
|
||||
messages.success(request, "La facture a été crée")
|
||||
return redirect("/users/profil/" + userid)
|
||||
messages.error(request, u"Il faut au moins un article valide pour créer une facture" )
|
||||
return form({'factureform': facture_form, 'venteform': article_formset, 'articlelist': article_list}, 'cotisations/new_facture.html', request)
|
||||
messages.error(
|
||||
request,
|
||||
u"Il faut au moins un article valide pour créer une facture"
|
||||
)
|
||||
return form({
|
||||
'factureform': facture_form,
|
||||
'venteform': article_formset,
|
||||
'articlelist': article_list
|
||||
}, 'cotisations/new_facture.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('tresorier')
|
||||
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
|
||||
Vente ou Facture correspondant en bdd"""
|
||||
facture_form = NewFactureFormPdf(request.POST or None)
|
||||
if facture_form.is_valid():
|
||||
options, created = AssoOption.objects.get_or_create()
|
||||
options, _created = AssoOption.objects.get_or_create()
|
||||
tbl = []
|
||||
article = facture_form.cleaned_data['article']
|
||||
quantite = facture_form.cleaned_data['number']
|
||||
|
@ -121,71 +152,131 @@ def new_facture_pdf(request):
|
|||
destinataire = facture_form.cleaned_data['dest']
|
||||
chambre = facture_form.cleaned_data['chambre']
|
||||
fid = facture_form.cleaned_data['fid']
|
||||
for a in article:
|
||||
tbl.append([a, quantite, a.prix * quantite])
|
||||
for art in article:
|
||||
tbl.append([art, quantite, art.prix * quantite])
|
||||
prix_total = sum(a[2] for a in tbl)
|
||||
user = {'name': destinataire, 'room': chambre}
|
||||
return render_tex(request, 'cotisations/factures.tex', {'DATE' : timezone.now(),'dest':user,'fid':fid, 'article':tbl, 'total':prix_total, 'paid':paid, 'asso_name':options.name, 'line1':options.adresse1, 'line2':options.adresse2, 'siret':options.siret, 'email':options.contact, 'phone':options.telephone, 'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH)})
|
||||
return form({'factureform': facture_form}, 'cotisations/facture.html', request)
|
||||
return render_tex(request, 'cotisations/factures.tex', {
|
||||
'DATE': timezone.now(),
|
||||
'dest': user,
|
||||
'fid': fid,
|
||||
'article': tbl,
|
||||
'total': prix_total,
|
||||
'paid': paid,
|
||||
'asso_name': options.name,
|
||||
'line1': options.adresse1,
|
||||
'line2': options.adresse2,
|
||||
'siret': options.siret,
|
||||
'email': options.contact,
|
||||
'phone': options.telephone,
|
||||
'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH)
|
||||
})
|
||||
return form({
|
||||
'factureform': facture_form
|
||||
}, 'cotisations/facture.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
def facture_pdf(request, 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("/cotisations/")
|
||||
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")
|
||||
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("/users/profil/" + str(request.user.id))
|
||||
if not facture.valid:
|
||||
messages.error(request, "Vous ne pouvez pas afficher une facture non valide")
|
||||
messages.error(request, "Vous ne pouvez pas afficher\
|
||||
une facture non valide")
|
||||
return redirect("/users/profil/" + str(request.user.id))
|
||||
vente = Vente.objects.all().filter(facture=facture)
|
||||
ventes_objects = Vente.objects.all().filter(facture=facture)
|
||||
ventes = []
|
||||
options, created = AssoOption.objects.get_or_create()
|
||||
for v in vente:
|
||||
ventes.append([v, v.number, v.prix_total])
|
||||
return render_tex(request, 'cotisations/factures.tex', {'paid':True, 'fid':facture.id, 'DATE':facture.date,'dest':facture.user, 'article':ventes, 'total': facture.prix_total(), 'asso_name':options.name, 'line1': options.adresse1, 'line2':options.adresse2, 'siret':options.siret, 'email':options.contact, 'phone':options.telephone, 'tpl_path':os.path.join(settings.BASE_DIR, LOGO_PATH)})
|
||||
options, _created = AssoOption.objects.get_or_create()
|
||||
for vente in ventes_objects:
|
||||
ventes.append([vente, vente.number, vente.prix_total])
|
||||
return render_tex(request, 'cotisations/factures.tex', {
|
||||
'paid': True,
|
||||
'fid': facture.id,
|
||||
'DATE': facture.date,
|
||||
'dest': facture.user,
|
||||
'article': ventes,
|
||||
'total': facture.prix_total(),
|
||||
'asso_name': options.name,
|
||||
'line1': options.adresse1,
|
||||
'line2': options.adresse2,
|
||||
'siret': options.siret,
|
||||
'email': options.contact,
|
||||
'phone': options.telephone,
|
||||
'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH)
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def edit_facture(request, 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("/cotisations/")
|
||||
if request.user.has_perms(['tresorier']):
|
||||
facture_form = TrezEditFactureForm(request.POST or None, instance=facture)
|
||||
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")
|
||||
messages.error(request, "Vous ne pouvez pas editer une facture\
|
||||
controlée ou invalidée par le trésorier")
|
||||
return redirect("/cotisations/")
|
||||
else:
|
||||
facture_form = EditFactureForm(request.POST or None, instance=facture)
|
||||
ventes_objects = Vente.objects.filter(facture=facture)
|
||||
vente_form_set = modelformset_factory(Vente, fields=('name','number'), extra=0, max_num=len(ventes_objects))
|
||||
vente_form_set = modelformset_factory(
|
||||
Vente,
|
||||
fields=('name', 'number'),
|
||||
extra=0,
|
||||
max_num=len(ventes_objects)
|
||||
)
|
||||
vente_form = vente_form_set(request.POST or None, queryset=ventes_objects)
|
||||
if facture_form.is_valid() and vente_form.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
facture_form.save()
|
||||
vente_form.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for form in vente_form for field in facture_form.changed_data + form.changed_data))
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
|
||||
field for form in vente_form for field
|
||||
in facture_form.changed_data + form.changed_data))
|
||||
messages.success(request, "La facture a bien été modifiée")
|
||||
return redirect("/cotisations/")
|
||||
return form({'factureform': facture_form, 'venteform': vente_form}, 'cotisations/edit_facture.html', request)
|
||||
return form({
|
||||
'factureform': facture_form,
|
||||
'venteform': vente_form
|
||||
}, 'cotisations/edit_facture.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def del_facture(request, 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("/cotisations/")
|
||||
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")
|
||||
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("/cotisations/")
|
||||
if request.method == "POST":
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
|
@ -193,7 +284,11 @@ def del_facture(request, factureid):
|
|||
reversion.set_user(request.user)
|
||||
messages.success(request, "La facture a été détruite")
|
||||
return redirect("/cotisations/")
|
||||
return form({'objet': facture, 'objet_name': 'facture'}, 'cotisations/delete.html', request)
|
||||
return form({
|
||||
'objet': facture,
|
||||
'objet_name': 'facture'
|
||||
}, 'cotisations/delete.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
|
@ -212,7 +307,14 @@ def credit_solde(request, userid):
|
|||
facture_instance.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Création")
|
||||
new_vente = Vente.objects.create(facture=facture_instance, name="solde", prix=facture.cleaned_data['montant'], iscotisation=False, duration=0, number=1)
|
||||
new_vente = Vente.objects.create(
|
||||
facture=facture_instance,
|
||||
name="solde",
|
||||
prix=facture.cleaned_data['montant'],
|
||||
iscotisation=False,
|
||||
duration=0,
|
||||
number=1
|
||||
)
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
new_vente.save()
|
||||
reversion.set_user(request.user)
|
||||
|
@ -225,6 +327,13 @@ def credit_solde(request, userid):
|
|||
@login_required
|
||||
@permission_required('tresorier')
|
||||
def add_article(request):
|
||||
"""Ajoute un article. Champs : désignation,
|
||||
prix, est-ce une cotisation et si oui sa durée
|
||||
Réservé au trésorier
|
||||
Nota bene : les ventes déjà effectuées ne sont pas reliées
|
||||
aux articles en vente. La désignation, le prix... sont
|
||||
copiés à la création de la facture. Un changement de prix n'a
|
||||
PAS de conséquence sur les ventes déjà faites"""
|
||||
article = ArticleForm(request.POST or None)
|
||||
if article.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
|
@ -235,9 +344,12 @@ def add_article(request):
|
|||
return redirect("/cotisations/index_article/")
|
||||
return form({'factureform': article}, 'cotisations/facture.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('tresorier')
|
||||
def edit_article(request, articleid):
|
||||
"""Edition d'un article (designation, prix, etc)
|
||||
Réservé au trésorier"""
|
||||
try:
|
||||
article_instance = Article.objects.get(pk=articleid)
|
||||
except Article.DoesNotExist:
|
||||
|
@ -248,14 +360,20 @@ def edit_article(request, articleid):
|
|||
with transaction.atomic(), reversion.create_revision():
|
||||
article.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in article.changed_data))
|
||||
reversion.set_comment(
|
||||
"Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in article.changed_data
|
||||
)
|
||||
)
|
||||
messages.success(request, "Type d'article modifié")
|
||||
return redirect("/cotisations/index_article/")
|
||||
return form({'factureform': article}, 'cotisations/facture.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('tresorier')
|
||||
def del_article(request):
|
||||
"""Suppression d'un article en vente"""
|
||||
article = DelArticleForm(request.POST or None)
|
||||
if article.is_valid():
|
||||
article_del = article.cleaned_data['articles']
|
||||
|
@ -266,9 +384,12 @@ def del_article(request):
|
|||
return redirect("/cotisations/index_article")
|
||||
return form({'factureform': article}, 'cotisations/facture.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('tresorier')
|
||||
def add_paiement(request):
|
||||
"""Ajoute un moyen de paiement. Relié aux factures
|
||||
via foreign key"""
|
||||
paiement = PaiementForm(request.POST or None)
|
||||
if paiement.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
|
@ -279,9 +400,11 @@ def add_paiement(request):
|
|||
return redirect("/cotisations/index_paiement/")
|
||||
return form({'factureform': paiement}, 'cotisations/facture.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('tresorier')
|
||||
def edit_paiement(request, paiementid):
|
||||
"""Edition d'un moyen de paiement"""
|
||||
try:
|
||||
paiement_instance = Paiement.objects.get(pk=paiementid)
|
||||
except Paiement.DoesNotExist:
|
||||
|
@ -292,14 +415,20 @@ def edit_paiement(request, paiementid):
|
|||
with transaction.atomic(), reversion.create_revision():
|
||||
paiement.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in paiement.changed_data))
|
||||
reversion.set_comment(
|
||||
"Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in paiement.changed_data
|
||||
)
|
||||
)
|
||||
messages.success(request, "Type de paiement modifié")
|
||||
return redirect("/cotisations/index_paiement/")
|
||||
return form({'factureform': paiement}, 'cotisations/facture.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('tresorier')
|
||||
def del_paiement(request):
|
||||
"""Suppression d'un moyen de paiement"""
|
||||
paiement = DelPaiementForm(request.POST or None)
|
||||
if paiement.is_valid():
|
||||
paiement_dels = paiement.cleaned_data['paiements']
|
||||
|
@ -309,15 +438,24 @@ def del_paiement(request):
|
|||
paiement_del.delete()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Destruction")
|
||||
messages.success(request, "Le moyen de paiement a été supprimé")
|
||||
messages.success(
|
||||
request,
|
||||
"Le moyen de paiement a été supprimé"
|
||||
)
|
||||
except ProtectedError:
|
||||
messages.error(request, "Le moyen de paiement %s est affecté à au moins une facture, vous ne pouvez pas le supprimer" % paiement_del)
|
||||
messages.error(
|
||||
request,
|
||||
"Le moyen de paiement %s est affecté à au moins une\
|
||||
facture, vous ne pouvez pas le supprimer" % paiement_del
|
||||
)
|
||||
return redirect("/cotisations/index_paiement/")
|
||||
return form({'factureform': paiement}, 'cotisations/facture.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def add_banque(request):
|
||||
"""Ajoute une banque à la liste des banques"""
|
||||
banque = BanqueForm(request.POST or None)
|
||||
if banque.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
|
@ -328,9 +466,11 @@ def add_banque(request):
|
|||
return redirect("/cotisations/index_banque/")
|
||||
return form({'factureform': banque}, 'cotisations/facture.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('tresorier')
|
||||
def edit_banque(request, banqueid):
|
||||
"""Edite le nom d'une banque"""
|
||||
try:
|
||||
banque_instance = Banque.objects.get(pk=banqueid)
|
||||
except Banque.DoesNotExist:
|
||||
|
@ -341,14 +481,20 @@ def edit_banque(request, banqueid):
|
|||
with transaction.atomic(), reversion.create_revision():
|
||||
banque.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in banque.changed_data))
|
||||
reversion.set_comment(
|
||||
"Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in banque.changed_data
|
||||
)
|
||||
)
|
||||
messages.success(request, "Banque modifiée")
|
||||
return redirect("/cotisations/index_banque/")
|
||||
return form({'factureform': banque}, 'cotisations/facture.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('tresorier')
|
||||
def del_banque(request):
|
||||
"""Supprime une banque"""
|
||||
banque = DelBanqueForm(request.POST or None)
|
||||
if banque.is_valid():
|
||||
banque_dels = banque.cleaned_data['banques']
|
||||
|
@ -360,17 +506,25 @@ def del_banque(request):
|
|||
reversion.set_comment("Destruction")
|
||||
messages.success(request, "La banque a été supprimée")
|
||||
except ProtectedError:
|
||||
messages.error(request, "La banque %s est affectée à au moins une facture, vous ne pouvez pas la supprimer" % banque_del)
|
||||
messages.error(request, "La banque %s est affectée à au moins\
|
||||
une facture, vous ne pouvez pas la supprimer" % banque_del)
|
||||
return redirect("/cotisations/index_banque/")
|
||||
return form({'factureform': banque}, 'cotisations/facture.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('tresorier')
|
||||
def control(request):
|
||||
options, created = GeneralOption.objects.get_or_create()
|
||||
"""Pour le trésorier, vue pour controler en masse les
|
||||
factures.Case à cocher, pratique"""
|
||||
options, _created = GeneralOption.objects.get_or_create()
|
||||
pagination_number = options.pagination_number
|
||||
facture_list = Facture.objects.order_by('date').reverse()
|
||||
controlform_set = modelformset_factory(Facture, fields=('control','valid'), extra=0)
|
||||
controlform_set = modelformset_factory(
|
||||
Facture,
|
||||
fields=('control', 'valid'),
|
||||
extra=0
|
||||
)
|
||||
paginator = Paginator(facture_list, pagination_number)
|
||||
page = request.GET.get('page')
|
||||
try:
|
||||
|
@ -379,7 +533,9 @@ def control(request):
|
|||
facture_list = paginator.page(1)
|
||||
except EmptyPage:
|
||||
facture_list = paginator.page(paginator.num.pages)
|
||||
page_query = Facture.objects.order_by('date').reverse().filter(id__in=[facture.id for facture in facture_list])
|
||||
page_query = Facture.objects.order_by('date').reverse().filter(
|
||||
id__in=[facture.id for facture in facture_list]
|
||||
)
|
||||
controlform = controlform_set(request.POST or None, queryset=page_query)
|
||||
if controlform.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
|
@ -387,32 +543,50 @@ def control(request):
|
|||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Controle trésorier")
|
||||
return redirect("/cotisations/control/")
|
||||
return render(request, 'cotisations/control.html', {'facture_list': facture_list, 'controlform': controlform})
|
||||
return render(request, 'cotisations/control.html', {
|
||||
'facture_list': facture_list,
|
||||
'controlform': controlform
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def index_article(request):
|
||||
"""Affiche l'ensemble des articles en vente"""
|
||||
article_list = Article.objects.order_by('name')
|
||||
return render(request, 'cotisations/index_article.html', {'article_list':article_list})
|
||||
return render(request, 'cotisations/index_article.html', {
|
||||
'article_list': article_list
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def index_paiement(request):
|
||||
"""Affiche l'ensemble des moyens de paiement en vente"""
|
||||
paiement_list = Paiement.objects.order_by('moyen')
|
||||
return render(request, 'cotisations/index_paiement.html', {'paiement_list':paiement_list})
|
||||
return render(request, 'cotisations/index_paiement.html', {
|
||||
'paiement_list': paiement_list
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def index_banque(request):
|
||||
"""Affiche l'ensemble des banques"""
|
||||
banque_list = Banque.objects.order_by('name')
|
||||
return render(request, 'cotisations/index_banque.html', {'banque_list':banque_list})
|
||||
return render(request, 'cotisations/index_banque.html', {
|
||||
'banque_list': banque_list
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def index(request):
|
||||
options, created = GeneralOption.objects.get_or_create()
|
||||
"""Affiche l'ensemble des factures, pour les cableurs et +"""
|
||||
options, _created = GeneralOption.objects.get_or_create()
|
||||
pagination_number = options.pagination_number
|
||||
facture_list = Facture.objects.order_by('date').select_related('user').select_related('paiement').prefetch_related('vente_set').reverse()
|
||||
facture_list = Facture.objects.order_by('date').select_related('user')\
|
||||
.select_related('paiement').prefetch_related('vente_set').reverse()
|
||||
paginator = Paginator(facture_list, pagination_number)
|
||||
page = request.GET.get('page')
|
||||
try:
|
||||
|
@ -423,41 +597,47 @@ def index(request):
|
|||
except EmptyPage:
|
||||
# If page is out of range (e.g. 9999), deliver last page of results.
|
||||
facture_list = paginator.page(paginator.num_pages)
|
||||
return render(request, 'cotisations/index.html', {'facture_list': facture_list})
|
||||
return render(request, 'cotisations/index.html', {
|
||||
'facture_list': facture_list
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
def history(request, object, id):
|
||||
def history(request, object, object_id):
|
||||
"""Affiche l'historique de chaque objet"""
|
||||
if object == 'facture':
|
||||
try:
|
||||
object_instance = Facture.objects.get(pk=id)
|
||||
object_instance = Facture.objects.get(pk=object_id)
|
||||
except Facture.DoesNotExist:
|
||||
messages.error(request, "Facture inexistante")
|
||||
return redirect("/cotisations/")
|
||||
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")
|
||||
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("/users/profil/" + str(request.user.id))
|
||||
elif object == 'paiement' and request.user.has_perms(('cableur',)):
|
||||
try:
|
||||
object_instance = Paiement.objects.get(pk=id)
|
||||
object_instance = Paiement.objects.get(pk=object_id)
|
||||
except Paiement.DoesNotExist:
|
||||
messages.error(request, "Paiement inexistant")
|
||||
return redirect("/cotisations/")
|
||||
elif object == 'article' and request.user.has_perms(('cableur',)):
|
||||
try:
|
||||
object_instance = Article.objects.get(pk=id)
|
||||
object_instance = Article.objects.get(pk=object_id)
|
||||
except Article.DoesNotExist:
|
||||
messages.error(request, "Article inexistante")
|
||||
return redirect("/cotisations/")
|
||||
elif object == 'banque' and request.user.has_perms(('cableur',)):
|
||||
try:
|
||||
object_instance = Banque.objects.get(pk=id)
|
||||
object_instance = Banque.objects.get(pk=object_id)
|
||||
except Banque.DoesNotExist:
|
||||
messages.error(request, "Banque inexistante")
|
||||
return redirect("/cotisations/")
|
||||
else:
|
||||
messages.error(request, "Objet inconnu")
|
||||
return redirect("/cotisations/")
|
||||
options, created = GeneralOption.objects.get_or_create()
|
||||
options, _created = GeneralOption.objects.get_or_create()
|
||||
pagination_number = options.pagination_number
|
||||
reversions = Version.objects.get_for_object(object_instance)
|
||||
paginator = Paginator(reversions, pagination_number)
|
||||
|
@ -470,4 +650,7 @@ def history(request, object, id):
|
|||
except EmptyPage:
|
||||
# If page is out of range (e.g. 9999), deliver last page of results.
|
||||
reversions = paginator.page(paginator.num_pages)
|
||||
return render(request, 're2o/history.html', {'reversions': reversions, 'object': object_instance})
|
||||
return render(request, 're2o/history.html', {
|
||||
'reversions': reversions,
|
||||
'object': object_instance
|
||||
})
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# se veut agnostique au réseau considéré, de manière à être installable en
|
||||
# quelques clics.
|
||||
#
|
||||
# Copyirght © 2017 Daniel Stan
|
||||
# Copyright © 2017 Gabriel Détraz
|
||||
# Copyright © 2017 Goulven Kermarec
|
||||
# Copyright © 2017 Augustin Lemesle
|
||||
|
@ -30,20 +31,18 @@ moment de l'authentification, en WiFi, filaire, ou par les NAS eux-mêmes.
|
|||
|
||||
Inspirés d'autres exemples trouvés ici :
|
||||
https://github.com/FreeRADIUS/freeradius-server/blob/master/src/modules/rlm_python/
|
||||
|
||||
Inspiré du travail de Daniel Stan au Crans
|
||||
"""
|
||||
|
||||
import logging
|
||||
import netaddr
|
||||
import radiusd # Module magique freeradius (radiusd.py is dummy)
|
||||
import os
|
||||
import binascii
|
||||
import hashlib
|
||||
|
||||
import os, sys
|
||||
|
||||
|
||||
import os, sys
|
||||
|
||||
proj_path = "/var/www/re2o/"
|
||||
# This is so Django knows where to find stuff.
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "re2o.settings")
|
||||
|
|
|
@ -19,7 +19,10 @@
|
|||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
"""
|
||||
Urls de l'application logs, pointe vers les fonctions de views.
|
||||
Inclu dans le re2o.urls
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf.urls import url
|
||||
|
@ -29,7 +32,9 @@ from . import views
|
|||
urlpatterns = [
|
||||
url(r'^$', views.index, name='index'),
|
||||
url(r'^stats_logs$', views.stats_logs, name='stats-logs'),
|
||||
url(r'^revert_action/(?P<revision_id>[0-9]+)$', views.revert_action, name='revert-action'),
|
||||
url(r'^revert_action/(?P<revision_id>[0-9]+)$',
|
||||
views.revert_action,
|
||||
name='revert-action'),
|
||||
url(r'^stats_general/$', views.stats_general, name='stats-general'),
|
||||
url(r'^stats_models/$', views.stats_models, name='stats-models'),
|
||||
url(r'^stats_users/$', views.stats_users, name='stats-users'),
|
||||
|
|
217
logs/views.py
217
logs/views.py
|
@ -23,35 +23,41 @@
|
|||
# App de gestion des statistiques pour re2o
|
||||
# Gabriel Détraz
|
||||
# Gplv2
|
||||
"""
|
||||
Vues des logs et statistiques générales.
|
||||
|
||||
La vue index générale affiche une selection des dernières actions,
|
||||
classées selon l'importance, avec date, et user formatés.
|
||||
|
||||
Stats_logs renvoie l'ensemble des logs.
|
||||
|
||||
Les autres vues sont thématiques, ensemble des statistiques et du
|
||||
nombre d'objets par models, nombre d'actions par user, etc
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render, redirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template.context_processors import csrf
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.template import Context, RequestContext, loader
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.db.models import ProtectedError
|
||||
from django.forms import ValidationError
|
||||
from django.db import transaction
|
||||
from django.db.models import Count
|
||||
|
||||
from reversion.models import Revision
|
||||
from reversion.models import Version, ContentType
|
||||
|
||||
from users.models import User, ServiceUser, Right, School, ListRight, ListShell, Ban, Whitelist
|
||||
from users.models import all_has_access, all_whitelisted, all_baned, all_adherent
|
||||
from cotisations.models import Facture, Vente, Article, Banque, Paiement, Cotisation
|
||||
from machines.models import Machine, MachineType, IpType, Extension, Interface, Domain, IpList
|
||||
from machines.views import all_active_assigned_interfaces_count, all_active_interfaces_count
|
||||
from users.models import User, ServiceUser, Right, School, ListRight, ListShell
|
||||
from users.models import Ban, Whitelist
|
||||
from cotisations.models import Facture, Vente, Article, Banque, Paiement
|
||||
from cotisations.models import Cotisation
|
||||
from machines.models import Machine, MachineType, IpType, Extension, Interface
|
||||
from machines.models import Domain, IpList
|
||||
from topologie.models import Switch, Port, Room
|
||||
from preferences.models import GeneralOption
|
||||
|
||||
from django.utils import timezone
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from re2o.views import form
|
||||
from re2o.utils import all_whitelisted, all_baned, all_has_access, all_adherent
|
||||
from re2o.utils import all_active_assigned_interfaces_count
|
||||
from re2o.utils import all_active_interfaces_count
|
||||
|
||||
STATS_DICT = {
|
||||
0: ["Tout", 36],
|
||||
|
@ -62,23 +68,22 @@ STATS_DICT = {
|
|||
5: ["2 an", 24],
|
||||
}
|
||||
|
||||
def form(ctx, template, request):
|
||||
c = ctx
|
||||
c.update(csrf(request))
|
||||
return render(request, template, c)
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def index(request):
|
||||
options, created = GeneralOption.objects.get_or_create()
|
||||
"""Affiche les logs affinés, date reformatées, selectionne
|
||||
les event importants (ajout de droits, ajout de ban/whitelist)"""
|
||||
options, _created = GeneralOption.objects.get_or_create()
|
||||
pagination_number = options.pagination_number
|
||||
|
||||
# The types of content kept for display
|
||||
content_type_filter = ['ban', 'whitelist', 'vente', 'interface', 'user']
|
||||
|
||||
# Select only wanted versions
|
||||
versions = Version.objects.filter(content_type__in=ContentType.objects.filter(model__in=content_type_filter)).order_by('revision__date_created').reverse().select_related('revision')
|
||||
|
||||
versions = Version.objects.filter(
|
||||
content_type__in=ContentType.objects.filter(
|
||||
model__in=content_type_filter
|
||||
)
|
||||
).order_by('revision__date_created').reverse().select_related('revision')
|
||||
paginator = Paginator(versions, pagination_number)
|
||||
page = request.GET.get('page')
|
||||
try:
|
||||
|
@ -97,28 +102,36 @@ def index(request):
|
|||
# Parse every item (max = pagination_number)
|
||||
for i in range(len(versions.object_list)):
|
||||
if versions.object_list[i].object:
|
||||
v = versions.object_list[i]
|
||||
version = versions.object_list[i]
|
||||
versions.object_list[i] = {
|
||||
'rev_id' : v.revision.id,
|
||||
'comment': v.revision.comment,
|
||||
'datetime': v.revision.date_created.strftime('%d/%m/%y %H:%M:%S'),
|
||||
'username': v.revision.user.get_username() if v.revision.user else '?',
|
||||
'user_id': v.revision.user_id,
|
||||
'version': v }
|
||||
'rev_id': version.revision.id,
|
||||
'comment': version.revision.comment,
|
||||
'datetime': version.revision.date_created.strftime(
|
||||
'%d/%m/%y %H:%M:%S'
|
||||
),
|
||||
'username':
|
||||
version.revision.user.get_username()
|
||||
if version.revision.user else '?',
|
||||
'user_id': version.revision.user_id,
|
||||
'version': version}
|
||||
else:
|
||||
to_remove.insert(0, i)
|
||||
# Remove all tagged invalid items
|
||||
for i in to_remove:
|
||||
versions.object_list.pop(i)
|
||||
|
||||
return render(request, 'logs/index.html', {'versions_list': versions})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def stats_logs(request):
|
||||
options, created = GeneralOption.objects.get_or_create()
|
||||
"""Affiche l'ensemble des logs et des modifications sur les objets,
|
||||
classés par date croissante, en vrac"""
|
||||
options, _created = GeneralOption.objects.get_or_create()
|
||||
pagination_number = options.pagination_number
|
||||
revisions = Revision.objects.all().order_by('date_created').reverse().select_related('user').prefetch_related('version_set__object')
|
||||
revisions = Revision.objects.all().order_by('date_created')\
|
||||
.reverse().select_related('user')\
|
||||
.prefetch_related('version_set__object')
|
||||
paginator = Paginator(revisions, pagination_number)
|
||||
page = request.GET.get('page')
|
||||
try:
|
||||
|
@ -129,7 +142,10 @@ def stats_logs(request):
|
|||
except EmptyPage:
|
||||
# If page is out of range (e.g. 9999), deliver last page of results.
|
||||
revisions = paginator.page(paginator.num_pages)
|
||||
return render(request, 'logs/stats_logs.html', {'revisions_list': revisions})
|
||||
return render(request, 'logs/stats_logs.html', {
|
||||
'revisions_list': revisions
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('bureau')
|
||||
|
@ -143,31 +159,59 @@ def revert_action(request, revision_id):
|
|||
revision.revert()
|
||||
messages.success(request, "L'action a été supprimée")
|
||||
return redirect("/logs/")
|
||||
return form({'objet': revision, 'objet_name': revision.__class__.__name__ }, 'logs/delete.html', request)
|
||||
return form({
|
||||
'objet': revision,
|
||||
'objet_name': revision.__class__.__name__
|
||||
}, 'logs/delete.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def stats_general(request):
|
||||
all_active_users = User.objects.filter(state=User.STATE_ACTIVE)
|
||||
ip = dict()
|
||||
"""Statistiques générales affinées sur les ip, activées, utilisées par
|
||||
range, et les statistiques générales sur les users : users actifs,
|
||||
cotisants, activés, archivés, etc"""
|
||||
ip_dict = dict()
|
||||
for ip_range in IpType.objects.all():
|
||||
all_ip = IpList.objects.filter(ip_type=ip_range)
|
||||
used_ip = Interface.objects.filter(ipv4__in=all_ip).count()
|
||||
active_ip = all_active_assigned_interfaces_count().filter(ipv4__in=IpList.objects.filter(ip_type=ip_range)).count()
|
||||
ip[ip_range] = [ip_range, all_ip.count(), used_ip, active_ip, all_ip.count()-used_ip]
|
||||
active_ip = all_active_assigned_interfaces_count().filter(
|
||||
ipv4__in=IpList.objects.filter(ip_type=ip_range)
|
||||
).count()
|
||||
ip_dict[ip_range] = [ip_range, all_ip.count(),
|
||||
used_ip, active_ip, all_ip.count()-used_ip]
|
||||
stats = [
|
||||
[["Categorie", "Nombre d'utilisateurs"], {
|
||||
'active_users' : ["Users actifs", User.objects.filter(state=User.STATE_ACTIVE).count()],
|
||||
'inactive_users' : ["Users désactivés", User.objects.filter(state=User.STATE_DISABLED).count()],
|
||||
'archive_users' : ["Users archivés", User.objects.filter(state=User.STATE_ARCHIVE).count()],
|
||||
'adherent_users' : ["Adhérents à l'association", all_adherent().count()],
|
||||
'connexion_users' : ["Utilisateurs bénéficiant d'une connexion", all_has_access().count()],
|
||||
'ban_users' : ["Utilisateurs bannis", all_baned().count()],
|
||||
'whitelisted_user' : ["Utilisateurs bénéficiant d'une connexion gracieuse", all_whitelisted().count()],
|
||||
'actives_interfaces' : ["Interfaces actives (ayant accès au reseau)", all_active_interfaces_count().count()],
|
||||
'actives_assigned_interfaces' : ["Interfaces actives et assignées ipv4", all_active_assigned_interfaces_count().count()]
|
||||
'active_users': [
|
||||
"Users actifs",
|
||||
User.objects.filter(state=User.STATE_ACTIVE).count()],
|
||||
'inactive_users': [
|
||||
"Users désactivés",
|
||||
User.objects.filter(state=User.STATE_DISABLED).count()],
|
||||
'archive_users': [
|
||||
"Users archivés",
|
||||
User.objects.filter(state=User.STATE_ARCHIVE).count()],
|
||||
'adherent_users': [
|
||||
"Adhérents à l'association",
|
||||
all_adherent().count()],
|
||||
'connexion_users': [
|
||||
"Utilisateurs bénéficiant d'une connexion",
|
||||
all_has_access().count()],
|
||||
'ban_users': [
|
||||
"Utilisateurs bannis",
|
||||
all_baned().count()],
|
||||
'whitelisted_user': [
|
||||
"Utilisateurs bénéficiant d'une connexion gracieuse",
|
||||
all_whitelisted().count()],
|
||||
'actives_interfaces': [
|
||||
"Interfaces actives (ayant accès au reseau)",
|
||||
all_active_interfaces_count().count()],
|
||||
'actives_assigned_interfaces': [
|
||||
"Interfaces actives et assignées ipv4",
|
||||
all_active_assigned_interfaces_count().count()]
|
||||
}],
|
||||
[["Range d'ip", "Nombre d'ip totales", "Ip assignées", "Ip assignées à une machine active", "Ip non assignées"] ,ip]
|
||||
[["Range d'ip", "Nombre d'ip totales", "Ip assignées",
|
||||
"Ip assignées à une machine active", "Ip non assignées"], ip_dict]
|
||||
]
|
||||
return render(request, 'logs/stats_general.html', {'stats_list': stats})
|
||||
|
||||
|
@ -175,11 +219,14 @@ def stats_general(request):
|
|||
@login_required
|
||||
@permission_required('cableur')
|
||||
def stats_models(request):
|
||||
all_active_users = User.objects.filter(state=User.STATE_ACTIVE)
|
||||
"""Statistiques générales, affiche les comptages par models:
|
||||
nombre d'users, d'écoles, de droits, de bannissements,
|
||||
de factures, de ventes, de banque, de machines, etc"""
|
||||
stats = {
|
||||
'Users': {
|
||||
'users': [User.PRETTY_NAME, User.objects.count()],
|
||||
'serviceuser' : [ServiceUser.PRETTY_NAME, ServiceUser.objects.count()],
|
||||
'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()],
|
||||
|
@ -193,15 +240,16 @@ def stats_models(request):
|
|||
'cotisation': [Cotisation.PRETTY_NAME, Cotisation.objects.count()],
|
||||
'article': [Article.PRETTY_NAME, Article.objects.count()],
|
||||
'banque': [Banque.PRETTY_NAME, Banque.objects.count()],
|
||||
'cotisation' : [Cotisation.PRETTY_NAME, Cotisation.objects.count()],
|
||||
},
|
||||
'Machines': {
|
||||
'machine': [Machine.PRETTY_NAME, Machine.objects.count()],
|
||||
'typemachine' : [MachineType.PRETTY_NAME, MachineType.objects.count()],
|
||||
'typemachine': [MachineType.PRETTY_NAME,
|
||||
MachineType.objects.count()],
|
||||
'typeip': [IpType.PRETTY_NAME, IpType.objects.count()],
|
||||
'extension': [Extension.PRETTY_NAME, Extension.objects.count()],
|
||||
'interface': [Interface.PRETTY_NAME, Interface.objects.count()],
|
||||
'alias' : [Domain.PRETTY_NAME, Domain.objects.exclude(cname=None).count()],
|
||||
'alias': [Domain.PRETTY_NAME,
|
||||
Domain.objects.exclude(cname=None).count()],
|
||||
'iplist': [IpList.PRETTY_NAME, IpList.objects.count()],
|
||||
},
|
||||
'Topologie': {
|
||||
|
@ -216,43 +264,72 @@ def stats_models(request):
|
|||
}
|
||||
return render(request, 'logs/stats_models.html', {'stats_list': stats})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def stats_users(request):
|
||||
"""Affiche les statistiques base de données aggrégées par user :
|
||||
nombre de machines par user, d'etablissements par user,
|
||||
de moyens de paiements par user, de banque par user,
|
||||
de bannissement par user, etc"""
|
||||
onglet = request.GET.get('onglet')
|
||||
try:
|
||||
search_field = STATS_DICT[onglet]
|
||||
except:
|
||||
search_field = STATS_DICT[0]
|
||||
_search_field = STATS_DICT[onglet]
|
||||
except KeyError:
|
||||
_search_field = STATS_DICT[0]
|
||||
onglet = 0
|
||||
start_date = timezone.now() + relativedelta(months=-search_field[1])
|
||||
stats = {
|
||||
'Utilisateur': {
|
||||
'Machines' : User.objects.annotate(num=Count('machine')).order_by('-num')[:10],
|
||||
'Facture' : User.objects.annotate(num=Count('facture')).order_by('-num')[:10],
|
||||
'Bannissement' : User.objects.annotate(num=Count('ban')).order_by('-num')[:10],
|
||||
'Accès gracieux' : User.objects.annotate(num=Count('whitelist')).order_by('-num')[:10],
|
||||
'Droits' : User.objects.annotate(num=Count('right')).order_by('-num')[:10],
|
||||
'Machines': User.objects.annotate(
|
||||
num=Count('machine')
|
||||
).order_by('-num')[:10],
|
||||
'Facture': User.objects.annotate(
|
||||
num=Count('facture')
|
||||
).order_by('-num')[:10],
|
||||
'Bannissement': User.objects.annotate(
|
||||
num=Count('ban')
|
||||
).order_by('-num')[:10],
|
||||
'Accès gracieux': User.objects.annotate(
|
||||
num=Count('whitelist')
|
||||
).order_by('-num')[:10],
|
||||
'Droits': User.objects.annotate(
|
||||
num=Count('right')
|
||||
).order_by('-num')[:10],
|
||||
},
|
||||
'Etablissement': {
|
||||
'Utilisateur' : School.objects.annotate(num=Count('user')).order_by('-num')[:10],
|
||||
'Utilisateur': School.objects.annotate(
|
||||
num=Count('user')
|
||||
).order_by('-num')[:10],
|
||||
},
|
||||
'Moyen de paiement': {
|
||||
'Utilisateur' : Paiement.objects.annotate(num=Count('facture')).order_by('-num')[:10],
|
||||
'Utilisateur': Paiement.objects.annotate(
|
||||
num=Count('facture')
|
||||
).order_by('-num')[:10],
|
||||
},
|
||||
'Banque': {
|
||||
'Utilisateur' : Banque.objects.annotate(num=Count('facture')).order_by('-num')[:10],
|
||||
'Utilisateur': Banque.objects.annotate(
|
||||
num=Count('facture')
|
||||
).order_by('-num')[:10],
|
||||
},
|
||||
}
|
||||
return render(request, 'logs/stats_users.html', {'stats_list': stats, 'stats_dict' : STATS_DICT, 'active_field': onglet})
|
||||
return render(request, 'logs/stats_users.html', {
|
||||
'stats_list': stats,
|
||||
'stats_dict': STATS_DICT,
|
||||
'active_field': onglet
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def stats_actions(request):
|
||||
onglet = request.GET.get('onglet')
|
||||
"""Vue qui affiche les statistiques de modifications d'objets par
|
||||
utilisateurs.
|
||||
Affiche le nombre de modifications aggrégées par utilisateurs"""
|
||||
stats = {
|
||||
'Utilisateur': {
|
||||
'Action' : User.objects.annotate(num=Count('revision')).order_by('-num')[:40],
|
||||
'Action': User.objects.annotate(
|
||||
num=Count('revision')
|
||||
).order_by('-num')[:40],
|
||||
},
|
||||
}
|
||||
return render(request, 'logs/stats_users.html', {'stats_list': stats})
|
||||
|
|
|
@ -40,7 +40,8 @@ class EditMachineForm(ModelForm):
|
|||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditMachineForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(EditMachineForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['name'].label = 'Nom de la machine'
|
||||
|
||||
class NewMachineForm(EditMachineForm):
|
||||
|
@ -57,7 +58,8 @@ class EditInterfaceForm(ModelForm):
|
|||
fields = ['machine', 'type', 'ipv4', 'mac_address', 'details']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditInterfaceForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(EditInterfaceForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['mac_address'].label = 'Adresse mac'
|
||||
self.fields['type'].label = 'Type de machine'
|
||||
self.fields['type'].empty_label = "Séléctionner un type de machine"
|
||||
|
@ -110,9 +112,10 @@ class AliasForm(ModelForm):
|
|||
fields = ['name','extension']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
if 'infra' in kwargs:
|
||||
infra = kwargs.pop('infra')
|
||||
super(AliasForm, self).__init__(*args, **kwargs)
|
||||
super(AliasForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
class DomainForm(AliasForm):
|
||||
class Meta(AliasForm.Meta):
|
||||
|
@ -125,7 +128,8 @@ class DomainForm(AliasForm):
|
|||
initial = kwargs.get('initial', {})
|
||||
initial['name'] = user.get_next_domain_name()
|
||||
kwargs['initial'] = initial
|
||||
super(DomainForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(DomainForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
class DelAliasForm(Form):
|
||||
alias = forms.ModelMultipleChoiceField(queryset=Domain.objects.all(), label="Alias actuels", widget=forms.CheckboxSelectMultiple)
|
||||
|
@ -141,7 +145,8 @@ class MachineTypeForm(ModelForm):
|
|||
fields = ['type','ip_type']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MachineTypeForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(MachineTypeForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['type'].label = 'Type de machine à ajouter'
|
||||
self.fields['ip_type'].label = "Type d'ip relié"
|
||||
|
||||
|
@ -153,9 +158,9 @@ class IpTypeForm(ModelForm):
|
|||
model = IpType
|
||||
fields = ['type','extension','need_infra','domaine_ip_start','domaine_ip_stop', 'prefix_v6', 'vlan', 'ouverture_ports']
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(IpTypeForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(IpTypeForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['type'].label = 'Type ip à ajouter'
|
||||
|
||||
class EditIpTypeForm(IpTypeForm):
|
||||
|
@ -171,7 +176,8 @@ class ExtensionForm(ModelForm):
|
|||
fields = ['name', 'need_infra', 'origin']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ExtensionForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(ExtensionForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['name'].label = 'Extension à ajouter'
|
||||
self.fields['origin'].label = 'Enregistrement A origin'
|
||||
|
||||
|
@ -184,7 +190,8 @@ class MxForm(ModelForm):
|
|||
fields = ['zone', 'priority', 'name']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MxForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(MxForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['name'].queryset = Domain.objects.exclude(interface_parent=None)
|
||||
|
||||
class DelMxForm(Form):
|
||||
|
@ -196,25 +203,34 @@ class NsForm(ModelForm):
|
|||
fields = ['zone', 'ns']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NsForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(NsForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['ns'].queryset = Domain.objects.exclude(interface_parent=None)
|
||||
|
||||
class DelNsForm(Form):
|
||||
ns = forms.ModelMultipleChoiceField(queryset=Ns.objects.all(), label="Enregistrements NS actuels", widget=forms.CheckboxSelectMultiple)
|
||||
|
||||
class TextForm(ModelForm):
|
||||
class TxtForm(ModelForm):
|
||||
class Meta:
|
||||
model = Text
|
||||
fields = '__all__'
|
||||
|
||||
class DelTextForm(Form):
|
||||
text = forms.ModelMultipleChoiceField(queryset=Text.objects.all(), label="Enregistrements Text actuels", widget=forms.CheckboxSelectMultiple)
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(TxtForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
class DelTxtForm(Form):
|
||||
txt = forms.ModelMultipleChoiceField(queryset=Text.objects.all(), label="Enregistrements Txt actuels", widget=forms.CheckboxSelectMultiple)
|
||||
|
||||
class NasForm(ModelForm):
|
||||
class Meta:
|
||||
model = Nas
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(NasForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
class DelNasForm(Form):
|
||||
nas = forms.ModelMultipleChoiceField(queryset=Nas.objects.all(), label="Enregistrements Nas actuels", widget=forms.CheckboxSelectMultiple)
|
||||
|
||||
|
@ -223,6 +239,10 @@ class ServiceForm(ModelForm):
|
|||
model = Service
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(ServiceForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super(ServiceForm, self).save(commit=False)
|
||||
if commit:
|
||||
|
@ -238,6 +258,10 @@ class VlanForm(ModelForm):
|
|||
model = Vlan
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(VlanForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
class DelVlanForm(Form):
|
||||
vlan = forms.ModelMultipleChoiceField(queryset=Vlan.objects.all(), label="Vlan actuels", widget=forms.CheckboxSelectMultiple)
|
||||
|
||||
|
@ -246,8 +270,16 @@ class EditOuverturePortConfigForm(ModelForm):
|
|||
model = Interface
|
||||
fields = ['port_lists']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(EditOuverturePortConfigForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
class EditOuverturePortListForm(ModelForm):
|
||||
class Meta:
|
||||
model = OuverturePortList
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(EditOuverturePortListForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
|
|
|
@ -31,15 +31,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for text in text_list %}
|
||||
{% for txt in txt_list %}
|
||||
<tr>
|
||||
<td>{{ text.zone }}</td>
|
||||
<td>{{ text.dns_entry }}</td>
|
||||
<td>{{ txt.zone }}</td>
|
||||
<td>{{ txt.dns_entry }}</td>
|
||||
<td class="text-right">
|
||||
{% if is_infra %}
|
||||
{% include 'buttons/edit.html' with href='machines:edit-text' id=text.id %}
|
||||
{% include 'buttons/edit.html' with href='machines:edit-txt' id=txt.id %}
|
||||
{% endif %}
|
||||
{% include 'buttons/history.html' with href='machines:history' name='text' id=text.id %}
|
||||
{% include 'buttons/history.html' with href='machines:history' name='txt' id=txt.id %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
|
@ -47,12 +47,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<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 Text</h2>
|
||||
<h2>Liste des enregistrements TXT</h2>
|
||||
{% if is_infra %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-text' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un enregistrement TXT</a>
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-text' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un enregistrement TXT</a>
|
||||
<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>
|
||||
<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_text.html" with text_list=text_list %}
|
||||
{% include "machines/aff_txt.html" with txt_list=txt_list %}
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
|
|
|
@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load bootstrap_form_typeahead %}
|
||||
{% load massive_bootstrap_form %}
|
||||
|
||||
{% block title %}Création et modification de machines{% endblock %}
|
||||
|
||||
|
@ -39,6 +39,36 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% if domainform %}
|
||||
{% bootstrap_form_errors domainform %}
|
||||
{% endif %}
|
||||
{% if iptypeform %}
|
||||
{% bootstrap_form_errors iptypeform %}
|
||||
{% endif %}
|
||||
{% if machinetypeform %}
|
||||
{% bootstrap_form_errors machinetypeform %}
|
||||
{% endif %}
|
||||
{% if extensionform %}
|
||||
{% bootstrap_form_errors extensionform %}
|
||||
{% endif %}
|
||||
{% if mxform %}
|
||||
{% bootstrap_form_errors mxform %}
|
||||
{% endif %}
|
||||
{% if nsform %}
|
||||
{% bootstrap_form_errors nsform %}
|
||||
{% endif %}
|
||||
{% if txtform %}
|
||||
{% bootstrap_form_errors txtform %}
|
||||
{% endif %}
|
||||
{% if aliasform %}
|
||||
{% bootstrap_form_errors aliasform %}
|
||||
{% endif %}
|
||||
{% if serviceform %}
|
||||
{% bootstrap_form_errors serviceform %}
|
||||
{% endif %}
|
||||
{% if vlanform %}
|
||||
{% bootstrap_form_errors vlanform %}
|
||||
{% endif %}
|
||||
{% if nasform %}
|
||||
{% bootstrap_form_errors nasform %}
|
||||
{% endif %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
@ -48,24 +78,56 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endif %}
|
||||
{% if interfaceform %}
|
||||
<h3>Interface</h3>
|
||||
{% if i_bft_param %}
|
||||
{% if 'machine' in interfaceform.fields %}
|
||||
{% bootstrap_form_typeahead interfaceform 'ipv4,machine' bft_param=i_bft_param %}
|
||||
{% if i_mbf_param %}
|
||||
{% massive_bootstrap_form interfaceform 'ipv4,machine' mbf_param=i_mbf_param %}
|
||||
{% else %}
|
||||
{% bootstrap_form_typeahead interfaceform 'ipv4' bft_param=i_bft_param %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if 'machine' in interfaceform.fields %}
|
||||
{% bootstrap_form_typeahead interfaceform 'ipv4,machine' %}
|
||||
{% else %}
|
||||
{% bootstrap_form_typeahead interfaceform 'ipv4' %}
|
||||
{% endif %}
|
||||
{% massive_bootstrap_form interfaceform 'ipv4,machine' %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if domainform %}
|
||||
<h3>Domaine</h3>
|
||||
{% bootstrap_form domainform %}
|
||||
{% endif %}
|
||||
{% if iptypeform %}
|
||||
<h3>Type d'IP</h3>
|
||||
{% bootstrap_form iptypeform %}
|
||||
{% endif %}
|
||||
{% if machinetypeform %}
|
||||
<h3>Type de machine</h3>
|
||||
{% bootstrap_form machinetypeform %}
|
||||
{% endif %}
|
||||
{% if extensionform %}
|
||||
<h3>Extension</h3>
|
||||
{% massive_bootstrap_form extensionform 'origin' %}
|
||||
{% endif %}
|
||||
{% if mxform %}
|
||||
<h3>Enregistrement MX</h3>
|
||||
{% massive_bootstrap_form mxform 'name' %}
|
||||
{% endif %}
|
||||
{% if nsform %}
|
||||
<h3>Enregistrement NS</h3>
|
||||
{% massive_bootstrap_form nsform 'ns' %}
|
||||
{% endif %}
|
||||
{% if txtform %}
|
||||
<h3>Enregistrement TXT</h3>
|
||||
{% bootstrap_form txtform %}
|
||||
{% endif %}
|
||||
{% if aliasform %}
|
||||
<h3>Alias</h3>
|
||||
{% bootstrap_form aliasform %}
|
||||
{% endif %}
|
||||
{% if serviceform %}
|
||||
<h3>Service</h3>
|
||||
{% massive_bootstrap_form serviceform 'servers' %}
|
||||
{% endif %}
|
||||
{% if vlanform %}
|
||||
<h3>Vlan</h3>
|
||||
{% bootstrap_form vlanform %}
|
||||
{% endif %}
|
||||
{% if nasform %}
|
||||
<h3>NAS</h3>
|
||||
{% bootstrap_form nasform %}
|
||||
{% endif %}
|
||||
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
|
||||
</form>
|
||||
<br />
|
||||
|
|
|
@ -1,386 +0,0 @@
|
|||
# -*- 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.
|
||||
|
||||
from django import template
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.forms import TextInput
|
||||
from bootstrap3.templatetags.bootstrap3 import bootstrap_form
|
||||
from bootstrap3.utils import render_tag
|
||||
from bootstrap3.forms import render_field
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs):
|
||||
"""
|
||||
Render a form where some specific fields are rendered using Typeahead.
|
||||
Using Typeahead really improves the performance, the speed and UX when
|
||||
dealing with very large datasets (select with 50k+ elts for instance).
|
||||
For convenience, it accepts the same parameters as a standard bootstrap
|
||||
can accept.
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_form_typeahead
|
||||
|
||||
**Parameters**:
|
||||
|
||||
form
|
||||
The form that is to be rendered
|
||||
|
||||
typeahead_fields
|
||||
A list of field names (comma separated) that should be rendered
|
||||
with typeahead instead of the default bootstrap renderer.
|
||||
|
||||
bft_param
|
||||
A dict of parameters for the bootstrap_form_typeahead tag. The
|
||||
possible parameters are the following.
|
||||
|
||||
choices
|
||||
A dict of strings representing the choices in JS. The keys of
|
||||
the dict are the names of the concerned fields. The choices
|
||||
must be an array of objects. Each of those objects must at
|
||||
least have the fields 'key' (value to send) and 'value' (value
|
||||
to display). Other fields can be added as desired.
|
||||
For a more complex structure you should also consider
|
||||
reimplementing the engine and the match_func.
|
||||
If not specified, the key is the id of the object and the value
|
||||
is its string representation as in a normal bootstrap form.
|
||||
Example :
|
||||
'choices' : {
|
||||
'field_A':'[{key:0,value:"choice0",extra:"data0"},{...},...]',
|
||||
'field_B':...,
|
||||
...
|
||||
}
|
||||
|
||||
engine
|
||||
A dict of strings representating the engine used for matching
|
||||
queries and possible values with typeahead. The keys of the
|
||||
dict are the names of the concerned fields. The string is valid
|
||||
JS code.
|
||||
If not specified, BloodHound with relevant basic properties is
|
||||
used.
|
||||
Example :
|
||||
'engine' : {'field_A': 'new Bloodhound()', 'field_B': ..., ...}
|
||||
|
||||
match_func
|
||||
A dict of strings representing a valid JS function used in the
|
||||
dataset to overload the matching engine. The keys of the dict
|
||||
are the names of the concerned fields. This function is used
|
||||
the source of the dataset. This function receives 2 parameters,
|
||||
the query and the synchronize function as specified in
|
||||
typeahead.js documentation. If needed, the local variables
|
||||
'choices_<fieldname>' and 'engine_<fieldname>' contains
|
||||
respectively the array of all possible values and the engine
|
||||
to match queries with possible values.
|
||||
If not specified, the function used display up to the 10 first
|
||||
elements if the query is empty and else the matching results.
|
||||
Example :
|
||||
'match_func' : {
|
||||
'field_A': 'function(q, sync) { engine.search(q, sync); }',
|
||||
'field_B': ...,
|
||||
...
|
||||
}
|
||||
|
||||
update_on
|
||||
A dict of list of ids that the values depends on. The engine
|
||||
and the typeahead properties are recalculated and reapplied.
|
||||
Example :
|
||||
'addition' : {
|
||||
'field_A' : [ 'id0', 'id1', ... ] ,
|
||||
'field_B' : ... ,
|
||||
...
|
||||
}
|
||||
|
||||
See boostrap_form_ for other arguments
|
||||
|
||||
**Usage**::
|
||||
|
||||
{% bootstrap_form_typeahead
|
||||
form
|
||||
[ '<field1>[,<field2>[,...]]' ]
|
||||
[ {
|
||||
[ 'choices': {
|
||||
[ '<field1>': '<choices1>'
|
||||
[, '<field2>': '<choices2>'
|
||||
[, ... ] ] ]
|
||||
} ]
|
||||
[, 'engine': {
|
||||
[ '<field1>': '<engine1>'
|
||||
[, '<field2>': '<engine2>'
|
||||
[, ... ] ] ]
|
||||
} ]
|
||||
[, 'match_func': {
|
||||
[ '<field1>': '<match_func1>'
|
||||
[, '<field2>': '<match_func2>'
|
||||
[, ... ] ] ]
|
||||
} ]
|
||||
[, 'update_on': {
|
||||
[ '<field1>': '<update_on1>'
|
||||
[, '<field2>': '<update_on2>'
|
||||
[, ... ] ] ]
|
||||
} ]
|
||||
} ]
|
||||
[ <standard boostrap_form parameters> ]
|
||||
%}
|
||||
|
||||
**Example**:
|
||||
|
||||
{% bootstrap_form_typeahead form 'ipv4' choices='[...]' %}
|
||||
"""
|
||||
|
||||
t_fields = typeahead_fields.split(',')
|
||||
params = kwargs.get('bft_param', {})
|
||||
exclude = params.get('exclude', None)
|
||||
exclude = exclude.split(',') if exclude else []
|
||||
t_choices = params.get('choices', {})
|
||||
t_engine = params.get('engine', {})
|
||||
t_match_func = params.get('match_func', {})
|
||||
t_update_on = params.get('update_on', {})
|
||||
hidden = [h.name for h in django_form.hidden_fields()]
|
||||
|
||||
form = ''
|
||||
for f_name, f_value in django_form.fields.items() :
|
||||
if not f_name in exclude :
|
||||
if f_name in t_fields and not f_name in hidden :
|
||||
f_bound = f_value.get_bound_field( django_form, f_name )
|
||||
f_value.widget = TextInput(
|
||||
attrs={
|
||||
'name': 'typeahead_'+f_name,
|
||||
'placeholder': f_value.empty_label
|
||||
}
|
||||
)
|
||||
form += render_field(
|
||||
f_value.get_bound_field( django_form, f_name ),
|
||||
*args,
|
||||
**kwargs
|
||||
)
|
||||
form += render_tag(
|
||||
'div',
|
||||
content = hidden_tag( f_bound, f_name ) +
|
||||
typeahead_js(
|
||||
f_name,
|
||||
f_value,
|
||||
f_bound,
|
||||
t_choices,
|
||||
t_engine,
|
||||
t_match_func,
|
||||
t_update_on
|
||||
)
|
||||
)
|
||||
else:
|
||||
form += render_field(
|
||||
f_value.get_bound_field(django_form, f_name),
|
||||
*args,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
return mark_safe( form )
|
||||
|
||||
def input_id( f_name ) :
|
||||
""" The id of the HTML input element """
|
||||
return 'id_'+f_name
|
||||
|
||||
def hidden_id( f_name ):
|
||||
""" The id of the HTML hidden input element """
|
||||
return 'typeahead_hidden_'+f_name
|
||||
|
||||
def hidden_tag( f_bound, f_name ):
|
||||
""" The HTML hidden input element """
|
||||
return render_tag(
|
||||
'input',
|
||||
attrs={
|
||||
'id': hidden_id(f_name),
|
||||
'name': f_name,
|
||||
'type': 'hidden',
|
||||
'value': f_bound.value() or ""
|
||||
}
|
||||
)
|
||||
|
||||
def typeahead_js( f_name, f_value, f_bound,
|
||||
t_choices, t_engine, t_match_func, t_update_on ) :
|
||||
""" The whole script to use """
|
||||
|
||||
choices = mark_safe( t_choices[f_name] ) if f_name in t_choices.keys() \
|
||||
else default_choices( f_value )
|
||||
|
||||
engine = mark_safe( t_engine[f_name] ) if f_name in t_engine.keys() \
|
||||
else default_engine ( f_name )
|
||||
|
||||
match_func = mark_safe(t_match_func[f_name]) \
|
||||
if f_name in t_match_func.keys() else default_match_func( f_name )
|
||||
|
||||
update_on = t_update_on[f_name] if f_name in t_update_on.keys() else []
|
||||
|
||||
js_content = (
|
||||
'var choices_{f_name} = {choices};'
|
||||
'var engine_{f_name};'
|
||||
'var setup_{f_name} = function() {{'
|
||||
'engine_{f_name} = {engine};'
|
||||
'$( "#{input_id}" ).typeahead( "destroy" );'
|
||||
'$( "#{input_id}" ).typeahead( {datasets} );'
|
||||
'}};'
|
||||
'$( "#{input_id}" ).bind( "typeahead:select", {updater} );'
|
||||
'$( "#{input_id}" ).bind( "typeahead:change", {change} );'
|
||||
'{updates}'
|
||||
'$( "#{input_id}" ).ready( function() {{'
|
||||
'setup_{f_name}();'
|
||||
'{init_input}'
|
||||
'}} );'
|
||||
).format(
|
||||
f_name = f_name,
|
||||
choices = choices,
|
||||
engine = engine,
|
||||
input_id = input_id( f_name ),
|
||||
datasets = default_datasets( f_name, match_func ),
|
||||
updater = typeahead_updater( f_name ),
|
||||
change = typeahead_change( f_name ),
|
||||
updates = ''.join( [ (
|
||||
'$( "#{u_id}" ).change( function() {{'
|
||||
'setup_{f_name}();'
|
||||
'{reset_input}'
|
||||
'}} );'
|
||||
).format(
|
||||
u_id = u_id,
|
||||
reset_input = reset_input( f_name ),
|
||||
f_name = f_name
|
||||
) for u_id in update_on ]
|
||||
),
|
||||
init_input = init_input( f_name, f_bound ),
|
||||
)
|
||||
|
||||
return render_tag( 'script', content=mark_safe( js_content ) )
|
||||
|
||||
def init_input( f_name, f_bound ) :
|
||||
""" The JS script to init the fields values """
|
||||
init_key = f_bound.value() or '""'
|
||||
return (
|
||||
'$( "#{input_id}" ).typeahead("val", {init_val});'
|
||||
'$( "#{hidden_id}" ).val( {init_key} );'
|
||||
).format(
|
||||
input_id = input_id( f_name ),
|
||||
init_val = '""' if init_key == '""' else
|
||||
'engine_{f_name}.get( {init_key} )[0].value'.format(
|
||||
f_name = f_name,
|
||||
init_key = init_key
|
||||
),
|
||||
init_key = init_key,
|
||||
hidden_id = hidden_id( f_name )
|
||||
)
|
||||
|
||||
def reset_input( f_name ) :
|
||||
""" The JS script to reset the fields values """
|
||||
return (
|
||||
'$( "#{input_id}" ).typeahead("val", "");'
|
||||
'$( "#{hidden_id}" ).val( "" );'
|
||||
).format(
|
||||
input_id = input_id( f_name ),
|
||||
hidden_id = hidden_id( f_name )
|
||||
)
|
||||
|
||||
def default_choices( f_value ) :
|
||||
""" The JS script creating the variable choices_<fieldname> """
|
||||
return '[{objects}]'.format(
|
||||
objects = ','.join(
|
||||
[ '{{key:{k},value:"{v}"}}'.format(
|
||||
k = choice[0] if choice[0] != '' else '""',
|
||||
v = choice[1]
|
||||
) for choice in f_value.choices ]
|
||||
)
|
||||
)
|
||||
|
||||
def default_engine ( f_name ) :
|
||||
""" The JS script creating the variable engine_<field_name> """
|
||||
return (
|
||||
'new Bloodhound({{'
|
||||
'datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"),'
|
||||
'queryTokenizer: Bloodhound.tokenizers.whitespace,'
|
||||
'local: choices_{f_name},'
|
||||
'identify: function(obj) {{ return obj.key; }}'
|
||||
'}})'
|
||||
).format(
|
||||
f_name = f_name
|
||||
)
|
||||
|
||||
def default_datasets( f_name, match_func ) :
|
||||
""" The JS script creating the datasets to use with typeahead """
|
||||
return (
|
||||
'{{'
|
||||
'hint: true,'
|
||||
'highlight: true,'
|
||||
'minLength: 0'
|
||||
'}},'
|
||||
'{{'
|
||||
'display: "value",'
|
||||
'name: "{f_name}",'
|
||||
'source: {match_func}'
|
||||
'}}'
|
||||
).format(
|
||||
f_name = f_name,
|
||||
match_func = match_func
|
||||
)
|
||||
|
||||
def default_match_func ( f_name ) :
|
||||
""" The JS script creating the matching function to use with typeahed """
|
||||
return (
|
||||
'function ( q, sync ) {{'
|
||||
'if ( q === "" ) {{'
|
||||
'var first = choices_{f_name}.slice( 0, 5 ).map('
|
||||
'function ( obj ) {{ return obj.key; }}'
|
||||
');'
|
||||
'sync( engine_{f_name}.get( first ) );'
|
||||
'}} else {{'
|
||||
'engine_{f_name}.search( q, sync );'
|
||||
'}}'
|
||||
'}}'
|
||||
).format(
|
||||
f_name = f_name
|
||||
)
|
||||
|
||||
def typeahead_updater( f_name ):
|
||||
""" The JS script creating the function triggered when an item is
|
||||
selected through typeahead """
|
||||
return (
|
||||
'function(evt, item) {{'
|
||||
'$( "#{hidden_id}" ).val( item.key );'
|
||||
'$( "#{hidden_id}" ).change();'
|
||||
'return item;'
|
||||
'}}'
|
||||
).format(
|
||||
hidden_id = hidden_id( f_name )
|
||||
)
|
||||
|
||||
def typeahead_change( f_name ):
|
||||
""" The JS script creating the function triggered when an item is changed
|
||||
(i.e. looses focus and value has changed since the moment it gained focus
|
||||
"""
|
||||
return (
|
||||
'function(evt) {{'
|
||||
'if ( $( "#{input_id}" ).typeahead( "val" ) === "" ) {{'
|
||||
'$( "#{hidden_id}" ).val( "" );'
|
||||
'$( "#{hidden_id}" ).change();'
|
||||
'}}'
|
||||
'}}'
|
||||
).format(
|
||||
input_id = input_id( f_name ),
|
||||
hidden_id = hidden_id( f_name )
|
||||
)
|
||||
|
|
@ -47,9 +47,9 @@ urlpatterns = [
|
|||
url(r'^add_mx/$', views.add_mx, name='add-mx'),
|
||||
url(r'^edit_mx/(?P<mxid>[0-9]+)$', views.edit_mx, name='edit-mx'),
|
||||
url(r'^del_mx/$', views.del_mx, name='del-mx'),
|
||||
url(r'^add_text/$', views.add_text, name='add-text'),
|
||||
url(r'^edit_text/(?P<textid>[0-9]+)$', views.edit_text, name='edit-text'),
|
||||
url(r'^del_text/$', views.del_text, name='del-text'),
|
||||
url(r'^add_txt/$', views.add_txt, name='add-txt'),
|
||||
url(r'^edit_txt/(?P<textid>[0-9]+)$', views.edit_txt, name='edit-txt'),
|
||||
url(r'^del_txt/$', views.del_txt, name='del-txt'),
|
||||
url(r'^add_ns/$', views.add_ns, name='add-ns'),
|
||||
url(r'^edit_ns/(?P<nsid>[0-9]+)$', views.edit_ns, name='edit-ns'),
|
||||
url(r'^del_ns/$', views.del_ns, name='del-ns'),
|
||||
|
@ -76,7 +76,7 @@ urlpatterns = [
|
|||
url(r'^history/(?P<object>extension)/(?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>text)/(?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>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'),
|
||||
|
|
|
@ -79,8 +79,8 @@ from .forms import (
|
|||
DelAliasForm,
|
||||
NsForm,
|
||||
DelNsForm,
|
||||
TextForm,
|
||||
DelTextForm,
|
||||
TxtForm,
|
||||
DelTxtForm,
|
||||
MxForm,
|
||||
DelMxForm,
|
||||
VlanForm,
|
||||
|
@ -110,58 +110,24 @@ from .models import (
|
|||
OuverturePort
|
||||
)
|
||||
from users.models import User
|
||||
from users.models import all_has_access
|
||||
from preferences.models import GeneralOption, OptionalMachine
|
||||
from .templatetags.bootstrap_form_typeahead import hidden_id, input_id
|
||||
|
||||
def filter_active_interfaces(q):
|
||||
"""Filtre les machines autorisées à sortir sur internet dans une requête"""
|
||||
return q.filter(
|
||||
machine__in=Machine.objects.filter(
|
||||
user__in=all_has_access()
|
||||
).filter(active=True)) \
|
||||
.select_related('domain') \
|
||||
.select_related('machine') \
|
||||
.select_related('type') \
|
||||
.select_related('ipv4') \
|
||||
.select_related('domain__extension') \
|
||||
.select_related('ipv4__ip_type').distinct()
|
||||
|
||||
def all_active_interfaces():
|
||||
"""Renvoie l'ensemble des machines autorisées à sortir sur internet """
|
||||
return filter_active_interfaces(Interface.objects)
|
||||
|
||||
def all_active_assigned_interfaces():
|
||||
"""
|
||||
Renvoie l'ensemble des machines qui ont une ipv4 assignées et disposant de
|
||||
l'accès internet
|
||||
"""
|
||||
return all_active_interfaces().filter(ipv4__isnull=False)
|
||||
|
||||
def all_active_interfaces_count():
|
||||
""" Version light seulement pour compter"""
|
||||
return Interface.objects.filter(
|
||||
machine__in=Machine.objects.filter(user__in=all_has_access())\
|
||||
.filter(active=True)
|
||||
from re2o.templatetags.massive_bootstrap_form import hidden_id, input_id
|
||||
from re2o.utils import (
|
||||
all_active_assigned_interfaces,
|
||||
all_has_access,
|
||||
filter_active_interfaces
|
||||
)
|
||||
|
||||
def all_active_assigned_interfaces_count():
|
||||
""" Version light seulement pour compter"""
|
||||
return all_active_interfaces_count().filter(ipv4__isnull=False)
|
||||
|
||||
def form(ctx, template, request):
|
||||
c = ctx
|
||||
c.update(csrf(request))
|
||||
return render(request, template, c)
|
||||
from re2o.views import form
|
||||
|
||||
def f_type_id( is_type_tt ):
|
||||
""" The id that will be used in HTML to store the value of the field
|
||||
type. Depends on the fact that type is generate using typeahead or not
|
||||
"""
|
||||
return hidden_id('type') if is_type_tt else input_id('type')
|
||||
return 'id_Interface-type_hidden' if is_type_tt else 'id_Interface-type'
|
||||
|
||||
def generate_ipv4_choices( form ) :
|
||||
""" Generate the parameter choices for the bootstrap_form_typeahead tag
|
||||
""" Generate the parameter choices for the massive_bootstrap_form tag
|
||||
"""
|
||||
f_ipv4 = form.fields['ipv4']
|
||||
used_mtype_id = []
|
||||
|
@ -189,7 +155,7 @@ def generate_ipv4_choices( form ) :
|
|||
return choices
|
||||
|
||||
def generate_ipv4_engine( is_type_tt ) :
|
||||
""" Generate the parameter engine for the bootstrap_form_typeahead tag
|
||||
""" Generate the parameter engine for the massive_bootstrap_form tag
|
||||
"""
|
||||
return (
|
||||
'new Bloodhound( {{'
|
||||
|
@ -203,7 +169,7 @@ def generate_ipv4_engine( is_type_tt ) :
|
|||
)
|
||||
|
||||
def generate_ipv4_match_func( is_type_tt ) :
|
||||
""" Generate the parameter match_func for the bootstrap_form_typeahead tag
|
||||
""" Generate the parameter match_func for the massive_bootstrap_form tag
|
||||
"""
|
||||
return (
|
||||
'function(q, sync) {{'
|
||||
|
@ -219,20 +185,20 @@ def generate_ipv4_match_func( is_type_tt ) :
|
|||
type_id = f_type_id( is_type_tt )
|
||||
)
|
||||
|
||||
def generate_ipv4_bft_param( form, is_type_tt ):
|
||||
""" Generate all the parameters to use with the bootstrap_form_typeahead
|
||||
def generate_ipv4_mbf_param( form, is_type_tt ):
|
||||
""" Generate all the parameters to use with the massive_bootstrap_form
|
||||
tag """
|
||||
i_choices = { 'ipv4': generate_ipv4_choices( form ) }
|
||||
i_engine = { 'ipv4': generate_ipv4_engine( is_type_tt ) }
|
||||
i_match_func = { 'ipv4': generate_ipv4_match_func( is_type_tt ) }
|
||||
i_update_on = { 'ipv4': [f_type_id( is_type_tt )] }
|
||||
i_bft_param = {
|
||||
i_mbf_param = {
|
||||
'choices': i_choices,
|
||||
'engine': i_engine,
|
||||
'match_func': i_match_func,
|
||||
'update_on': i_update_on
|
||||
}
|
||||
return i_bft_param
|
||||
return i_mbf_param
|
||||
|
||||
@login_required
|
||||
def new_machine(request, userid):
|
||||
|
@ -282,8 +248,8 @@ def new_machine(request, userid):
|
|||
reversion.set_comment("Création")
|
||||
messages.success(request, "La machine a été créée")
|
||||
return redirect("/users/profil/" + str(user.id))
|
||||
i_bft_param = generate_ipv4_bft_param( interface, False )
|
||||
return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain, 'i_bft_param': i_bft_param}, 'machines/machine.html', request)
|
||||
i_mbf_param = generate_ipv4_mbf_param( interface, False )
|
||||
return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain, 'i_mbf_param': i_mbf_param}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
def edit_interface(request, interfaceid):
|
||||
|
@ -322,8 +288,8 @@ def edit_interface(request, interfaceid):
|
|||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in domain_form.changed_data))
|
||||
messages.success(request, "La machine a été modifiée")
|
||||
return redirect("/users/profil/" + str(interface.machine.user.id))
|
||||
i_bft_param = generate_ipv4_bft_param( interface_form, False )
|
||||
return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form, 'i_bft_param': i_bft_param}, 'machines/machine.html', request)
|
||||
i_mbf_param = generate_ipv4_mbf_param( interface_form, False )
|
||||
return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form, 'i_mbf_param': i_mbf_param}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
def del_machine(request, machineid):
|
||||
|
@ -381,8 +347,8 @@ def new_interface(request, machineid):
|
|||
reversion.set_comment("Création")
|
||||
messages.success(request, "L'interface a été ajoutée")
|
||||
return redirect("/users/profil/" + str(machine.user.id))
|
||||
i_bft_param = generate_ipv4_bft_param( interface_form, False )
|
||||
return form({'interfaceform': interface_form, 'domainform': domain_form, 'i_bft_param': i_bft_param}, 'machines/machine.html', request)
|
||||
i_mbf_param = generate_ipv4_mbf_param( interface_form, False )
|
||||
return form({'interfaceform': interface_form, 'domainform': domain_form, 'i_mbf_param': i_mbf_param}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
def del_interface(request, interfaceid):
|
||||
|
@ -419,7 +385,7 @@ def add_iptype(request):
|
|||
reversion.set_comment("Création")
|
||||
messages.success(request, "Ce type d'ip a été ajouté")
|
||||
return redirect("/machines/index_iptype")
|
||||
return form({'machineform': iptype, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'iptypeform': iptype}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -438,7 +404,7 @@ def edit_iptype(request, iptypeid):
|
|||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in iptype.changed_data))
|
||||
messages.success(request, "Type d'ip modifié")
|
||||
return redirect("/machines/index_iptype/")
|
||||
return form({'machineform': iptype}, 'machines/machine.html', request)
|
||||
return form({'iptypeform': iptype}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -456,7 +422,7 @@ def del_iptype(request):
|
|||
except ProtectedError:
|
||||
messages.error(request, "Le type d'ip %s est affectée à au moins une machine, vous ne pouvez pas le supprimer" % iptype_del)
|
||||
return redirect("/machines/index_iptype")
|
||||
return form({'machineform': iptype, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'iptypeform': iptype}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -469,7 +435,7 @@ def add_machinetype(request):
|
|||
reversion.set_comment("Création")
|
||||
messages.success(request, "Ce type de machine a été ajouté")
|
||||
return redirect("/machines/index_machinetype")
|
||||
return form({'machineform': machinetype, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'machinetypeform': machinetype}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -487,7 +453,7 @@ def edit_machinetype(request, machinetypeid):
|
|||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in machinetype.changed_data))
|
||||
messages.success(request, "Type de machine modifié")
|
||||
return redirect("/machines/index_machinetype/")
|
||||
return form({'machineform': machinetype}, 'machines/machine.html', request)
|
||||
return form({'machinetypeform': machinetype}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -504,7 +470,7 @@ def del_machinetype(request):
|
|||
except ProtectedError:
|
||||
messages.error(request, "Le type de machine %s est affectée à au moins une machine, vous ne pouvez pas le supprimer" % machinetype_del)
|
||||
return redirect("/machines/index_machinetype")
|
||||
return form({'machineform': machinetype, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'machinetypeform': machinetype}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -517,7 +483,7 @@ def add_extension(request):
|
|||
reversion.set_comment("Création")
|
||||
messages.success(request, "Cette extension a été ajoutée")
|
||||
return redirect("/machines/index_extension")
|
||||
return form({'machineform': extension, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'extensionform': extension}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -535,7 +501,7 @@ def edit_extension(request, extensionid):
|
|||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in extension.changed_data))
|
||||
messages.success(request, "Extension modifiée")
|
||||
return redirect("/machines/index_extension/")
|
||||
return form({'machineform': extension}, 'machines/machine.html', request)
|
||||
return form({'extensionform': extension}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -552,7 +518,7 @@ def del_extension(request):
|
|||
except ProtectedError:
|
||||
messages.error(request, "L'extension %s est affectée à au moins un type de machine, vous ne pouvez pas la supprimer" % extension_del)
|
||||
return redirect("/machines/index_extension")
|
||||
return form({'machineform': extension, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'extensionform': extension}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -565,7 +531,7 @@ def add_mx(request):
|
|||
reversion.set_comment("Création")
|
||||
messages.success(request, "Cet enregistrement mx a été ajouté")
|
||||
return redirect("/machines/index_extension")
|
||||
return form({'machineform': mx, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'mxform': mx}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -583,7 +549,7 @@ def edit_mx(request, mxid):
|
|||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in mx.changed_data))
|
||||
messages.success(request, "Mx modifié")
|
||||
return redirect("/machines/index_extension/")
|
||||
return form({'machineform': mx}, 'machines/machine.html', request)
|
||||
return form({'mxform': mx}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -600,7 +566,7 @@ def del_mx(request):
|
|||
except ProtectedError:
|
||||
messages.error(request, "Erreur le Mx suivant %s ne peut être supprimé" % mx_del)
|
||||
return redirect("/machines/index_extension")
|
||||
return form({'machineform': mx, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'mxform': mx}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -613,7 +579,7 @@ def add_ns(request):
|
|||
reversion.set_comment("Création")
|
||||
messages.success(request, "Cet enregistrement ns a été ajouté")
|
||||
return redirect("/machines/index_extension")
|
||||
return form({'machineform': ns, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'nsform': ns}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -631,7 +597,7 @@ def edit_ns(request, nsid):
|
|||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in ns.changed_data))
|
||||
messages.success(request, "Ns modifié")
|
||||
return redirect("/machines/index_extension/")
|
||||
return form({'machineform': ns}, 'machines/machine.html', request)
|
||||
return form({'nsform': ns}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -648,55 +614,55 @@ def del_ns(request):
|
|||
except ProtectedError:
|
||||
messages.error(request, "Erreur le Ns suivant %s ne peut être supprimé" % ns_del)
|
||||
return redirect("/machines/index_extension")
|
||||
return form({'machineform': ns, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'nsform': ns}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
def add_text(request):
|
||||
text = TextForm(request.POST or None)
|
||||
if text.is_valid():
|
||||
def add_txt(request):
|
||||
txt = TxtForm(request.POST or None)
|
||||
if txt.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
text.save()
|
||||
txt.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Création")
|
||||
messages.success(request, "Cet enregistrement text a été ajouté")
|
||||
return redirect("/machines/index_extension")
|
||||
return form({'machineform': text, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'txtform': txt}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
def edit_text(request, textid):
|
||||
def edit_txt(request, txtid):
|
||||
try:
|
||||
text_instance = Text.objects.get(pk=textid)
|
||||
txt_instance = Text.objects.get(pk=txtid)
|
||||
except Text.DoesNotExist:
|
||||
messages.error(request, u"Entrée inexistante" )
|
||||
return redirect("/machines/index_extension/")
|
||||
text = TextForm(request.POST or None, instance=text_instance)
|
||||
if text.is_valid():
|
||||
txt = TxtForm(request.POST or None, instance=txt_instance)
|
||||
if txt.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
text.save()
|
||||
txt.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in text.changed_data))
|
||||
messages.success(request, "Text modifié")
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in txt.changed_data))
|
||||
messages.success(request, "Txt modifié")
|
||||
return redirect("/machines/index_extension/")
|
||||
return form({'machineform': text}, 'machines/machine.html', request)
|
||||
return form({'txtform': txt}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
def del_text(request):
|
||||
text = DelTextForm(request.POST or None)
|
||||
if text.is_valid():
|
||||
text_dels = text.cleaned_data['text']
|
||||
for text_del in text_dels:
|
||||
def del_txt(request):
|
||||
txt = DelTxtForm(request.POST or None)
|
||||
if txt.is_valid():
|
||||
txt_dels = txt.cleaned_data['txt']
|
||||
for txt_del in txt_dels:
|
||||
try:
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
text_del.delete()
|
||||
txt_del.delete()
|
||||
reversion.set_user(request.user)
|
||||
messages.success(request, "Le text a été supprimé")
|
||||
messages.success(request, "Le txt a été supprimé")
|
||||
except ProtectedError:
|
||||
messages.error(request, "Erreur le Text suivant %s ne peut être supprimé" % text_del)
|
||||
messages.error(request, "Erreur le Txt suivant %s ne peut être supprimé" % txt_del)
|
||||
return redirect("/machines/index_extension")
|
||||
return form({'machineform': text, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'txtform': txt}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
def add_alias(request, interfaceid):
|
||||
|
@ -724,7 +690,7 @@ def add_alias(request, interfaceid):
|
|||
reversion.set_comment("Création")
|
||||
messages.success(request, "Cet alias a été ajouté")
|
||||
return redirect("/machines/index_alias/" + str(interfaceid))
|
||||
return form({'machineform': alias, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'aliasform': alias}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
def edit_alias(request, aliasid):
|
||||
|
@ -744,7 +710,7 @@ def edit_alias(request, aliasid):
|
|||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in alias.changed_data))
|
||||
messages.success(request, "Alias modifié")
|
||||
return redirect("/machines/index_alias/" + str(alias_instance.cname.interface_parent.id))
|
||||
return form({'machineform': alias}, 'machines/machine.html', request)
|
||||
return form({'aliasform': alias}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
def del_alias(request, interfaceid):
|
||||
|
@ -768,7 +734,7 @@ def del_alias(request, interfaceid):
|
|||
except ProtectedError:
|
||||
messages.error(request, "Erreur l'alias suivant %s ne peut être supprimé" % alias_del)
|
||||
return redirect("/machines/index_alias/" + str(interfaceid))
|
||||
return form({'machineform': alias, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'aliasform': alias}, 'machines/machine.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -782,7 +748,7 @@ def add_service(request):
|
|||
reversion.set_comment("Création")
|
||||
messages.success(request, "Cet enregistrement service a été ajouté")
|
||||
return redirect("/machines/index_service")
|
||||
return form({'machineform': service}, 'machines/machine.html', request)
|
||||
return form({'serviceform': service}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -800,7 +766,7 @@ def edit_service(request, serviceid):
|
|||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in service.changed_data))
|
||||
messages.success(request, "Service modifié")
|
||||
return redirect("/machines/index_service/")
|
||||
return form({'machineform': service}, 'machines/machine.html', request)
|
||||
return form({'serviceform': service}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -817,7 +783,7 @@ def del_service(request):
|
|||
except ProtectedError:
|
||||
messages.error(request, "Erreur le service suivant %s ne peut être supprimé" % service_del)
|
||||
return redirect("/machines/index_service")
|
||||
return form({'machineform': service}, 'machines/machine.html', request)
|
||||
return form({'serviceform': service}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -830,7 +796,7 @@ def add_vlan(request):
|
|||
reversion.set_comment("Création")
|
||||
messages.success(request, "Cet enregistrement vlan a été ajouté")
|
||||
return redirect("/machines/index_vlan")
|
||||
return form({'machineform': vlan, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'vlanform': vlan}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -848,7 +814,7 @@ def edit_vlan(request, vlanid):
|
|||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in vlan.changed_data))
|
||||
messages.success(request, "Vlan modifié")
|
||||
return redirect("/machines/index_vlan/")
|
||||
return form({'machineform': vlan}, 'machines/machine.html', request)
|
||||
return form({'vlanform': vlan}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -865,7 +831,7 @@ def del_vlan(request):
|
|||
except ProtectedError:
|
||||
messages.error(request, "Erreur le Vlan suivant %s ne peut être supprimé" % vlan_del)
|
||||
return redirect("/machines/index_vlan")
|
||||
return form({'machineform': vlan, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'vlanform': vlan}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -878,7 +844,7 @@ def add_nas(request):
|
|||
reversion.set_comment("Création")
|
||||
messages.success(request, "Cet enregistrement nas a été ajouté")
|
||||
return redirect("/machines/index_nas")
|
||||
return form({'machineform': nas, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'nasform': nas}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -896,7 +862,7 @@ def edit_nas(request, nasid):
|
|||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in nas.changed_data))
|
||||
messages.success(request, "Nas modifié")
|
||||
return redirect("/machines/index_nas/")
|
||||
return form({'machineform': nas}, 'machines/machine.html', request)
|
||||
return form({'nasform': nas}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -913,7 +879,7 @@ def del_nas(request):
|
|||
except ProtectedError:
|
||||
messages.error(request, "Erreur le Nas suivant %s ne peut être supprimé" % nas_del)
|
||||
return redirect("/machines/index_nas")
|
||||
return form({'machineform': nas, 'interfaceform': None}, 'machines/machine.html', request)
|
||||
return form({'nasform': nas}, 'machines/machine.html', request)
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
|
@ -1039,11 +1005,11 @@ def history(request, object, id):
|
|||
except Mx.DoesNotExist:
|
||||
messages.error(request, "Mx inexistant")
|
||||
return redirect("/machines/")
|
||||
elif object == 'text' and request.user.has_perms(('cableur',)):
|
||||
elif object == 'txt' and request.user.has_perms(('cableur',)):
|
||||
try:
|
||||
object_instance = Text.objects.get(pk=id)
|
||||
except Text.DoesNotExist:
|
||||
messages.error(request, "Text inexistant")
|
||||
messages.error(request, "Txt inexistant")
|
||||
return redirect("/machines/")
|
||||
elif object == 'ns' and request.user.has_perms(('cableur',)):
|
||||
try:
|
||||
|
|
|
@ -20,35 +20,53 @@
|
|||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
"""
|
||||
Classes admin pour les models de preferences
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib import admin
|
||||
from reversion.admin import VersionAdmin
|
||||
|
||||
from .models import OptionalUser, OptionalMachine, OptionalTopologie, GeneralOption, Service, AssoOption, MailMessageOption
|
||||
from .models import OptionalUser, OptionalMachine, OptionalTopologie
|
||||
from .models import GeneralOption, Service, AssoOption, MailMessageOption
|
||||
|
||||
|
||||
class OptionalUserAdmin(VersionAdmin):
|
||||
"""Class admin options user"""
|
||||
pass
|
||||
|
||||
|
||||
class OptionalTopologieAdmin(VersionAdmin):
|
||||
"""Class admin options topologie"""
|
||||
pass
|
||||
|
||||
|
||||
class OptionalMachineAdmin(VersionAdmin):
|
||||
"""Class admin options machines"""
|
||||
pass
|
||||
|
||||
|
||||
class GeneralOptionAdmin(VersionAdmin):
|
||||
"""Class admin options générales"""
|
||||
pass
|
||||
|
||||
|
||||
class ServiceAdmin(VersionAdmin):
|
||||
"""Class admin gestion des services de la page d'accueil"""
|
||||
pass
|
||||
|
||||
|
||||
class AssoOptionAdmin(VersionAdmin):
|
||||
"""Class admin options de l'asso"""
|
||||
pass
|
||||
|
||||
|
||||
class MailMessageOptionAdmin(VersionAdmin):
|
||||
"""Class admin options mail"""
|
||||
pass
|
||||
|
||||
|
||||
admin.site.register(OptionalUser, OptionalUserAdmin)
|
||||
admin.site.register(OptionalMachine, OptionalMachineAdmin)
|
||||
admin.site.register(OptionalTopologie, OptionalTopologieAdmin)
|
||||
|
|
|
@ -19,66 +19,116 @@
|
|||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
"""
|
||||
Formulaire d'edition des réglages : user, machine, topologie, asso...
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.forms import ModelForm, Form, ValidationError
|
||||
from django.forms import ModelForm, Form
|
||||
from django import forms
|
||||
from .models import OptionalUser, OptionalMachine, OptionalTopologie, GeneralOption, AssoOption, MailMessageOption, Service
|
||||
from django.db.models import Q
|
||||
from .models import OptionalUser, OptionalMachine, OptionalTopologie
|
||||
from .models import GeneralOption, AssoOption, MailMessageOption, Service
|
||||
|
||||
|
||||
class EditOptionalUserForm(ModelForm):
|
||||
"""Formulaire d'édition des options de l'user. (solde, telephone..)"""
|
||||
class Meta:
|
||||
model = OptionalUser
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditOptionalUserForm, self).__init__(*args, **kwargs)
|
||||
self.fields['is_tel_mandatory'].label = 'Exiger un numéro de téléphone'
|
||||
self.fields['user_solde'].label = 'Activation du solde pour les utilisateurs'
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(EditOptionalUserForm, self).__init__(
|
||||
*args,
|
||||
prefix=prefix,
|
||||
**kwargs
|
||||
)
|
||||
self.fields['is_tel_mandatory'].label = 'Exiger un numéro de\
|
||||
téléphone'
|
||||
self.fields['user_solde'].label = 'Activation du solde pour\
|
||||
les utilisateurs'
|
||||
|
||||
|
||||
class EditOptionalMachineForm(ModelForm):
|
||||
"""Options machines (max de machines, etc)"""
|
||||
class Meta:
|
||||
model = OptionalMachine
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditOptionalMachineForm, self).__init__(*args, **kwargs)
|
||||
self.fields['password_machine'].label = "Possibilité d'attribuer un mot de passe par interface"
|
||||
self.fields['max_lambdauser_interfaces'].label = "Maximum d'interfaces autorisées pour un user normal"
|
||||
self.fields['max_lambdauser_aliases'].label = "Maximum d'alias dns autorisés pour un user normal"
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(EditOptionalMachineForm, self).__init__(
|
||||
*args,
|
||||
prefix=prefix,
|
||||
**kwargs
|
||||
)
|
||||
self.fields['password_machine'].label = "Possibilité d'attribuer\
|
||||
un mot de passe par interface"
|
||||
self.fields['max_lambdauser_interfaces'].label = "Maximum\
|
||||
d'interfaces autorisées pour un user normal"
|
||||
self.fields['max_lambdauser_aliases'].label = "Maximum d'alias\
|
||||
dns autorisés pour un user normal"
|
||||
|
||||
|
||||
class EditOptionalTopologieForm(ModelForm):
|
||||
"""Options de topologie, formulaire d'edition (vlan par default etc)"""
|
||||
class Meta:
|
||||
model = OptionalTopologie
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditOptionalTopologieForm, self).__init__(*args, **kwargs)
|
||||
self.fields['vlan_decision_ok'].label = "Vlan où placer les machines après acceptation RADIUS"
|
||||
self.fields['vlan_decision_nok'].label = "Vlan où placer les machines après rejet RADIUS"
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(EditOptionalTopologieForm, self).__init__(
|
||||
*args,
|
||||
prefix=prefix,
|
||||
**kwargs
|
||||
)
|
||||
self.fields['vlan_decision_ok'].label = "Vlan où placer les\
|
||||
machines après acceptation RADIUS"
|
||||
self.fields['vlan_decision_nok'].label = "Vlan où placer les\
|
||||
machines après rejet RADIUS"
|
||||
|
||||
|
||||
class EditGeneralOptionForm(ModelForm):
|
||||
"""Options générales (affichages de résultats de recherche, etc)"""
|
||||
class Meta:
|
||||
model = GeneralOption
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditGeneralOptionForm, self).__init__(*args, **kwargs)
|
||||
self.fields['search_display_page'].label = 'Resultats affichés dans une recherche'
|
||||
self.fields['pagination_number'].label = 'Items par page, taille normale (ex users)'
|
||||
self.fields['pagination_large_number'].label = 'Items par page, taille élevée (machines)'
|
||||
self.fields['req_expire_hrs'].label = 'Temps avant expiration du lien de reinitialisation de mot de passe (en heures)'
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(EditGeneralOptionForm, self).__init__(
|
||||
*args,
|
||||
prefix=prefix,
|
||||
**kwargs
|
||||
)
|
||||
self.fields['search_display_page'].label = 'Resultats\
|
||||
affichés dans une recherche'
|
||||
self.fields['pagination_number'].label = 'Items par page,\
|
||||
taille normale (ex users)'
|
||||
self.fields['pagination_large_number'].label = 'Items par page,\
|
||||
taille élevée (machines)'
|
||||
self.fields['req_expire_hrs'].label = 'Temps avant expiration du lien\
|
||||
de reinitialisation de mot de passe (en heures)'
|
||||
self.fields['site_name'].label = 'Nom du site web'
|
||||
self.fields['email_from'].label = 'Adresse mail d\'expedition automatique'
|
||||
self.fields['email_from'].label = "Adresse mail d\
|
||||
'expedition automatique"
|
||||
|
||||
|
||||
class EditAssoOptionForm(ModelForm):
|
||||
"""Options de l'asso (addresse, telephone, etc)"""
|
||||
class Meta:
|
||||
model = AssoOption
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditAssoOptionForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(EditAssoOptionForm, self).__init__(
|
||||
*args,
|
||||
prefix=prefix,
|
||||
**kwargs
|
||||
)
|
||||
self.fields['name'].label = 'Nom de l\'asso'
|
||||
self.fields['siret'].label = 'SIRET'
|
||||
self.fields['adresse1'].label = 'Adresse (ligne 1)'
|
||||
|
@ -86,22 +136,44 @@ class EditAssoOptionForm(ModelForm):
|
|||
self.fields['contact'].label = 'Email de contact'
|
||||
self.fields['telephone'].label = 'Numéro de téléphone'
|
||||
self.fields['pseudo'].label = 'Pseudo d\'usage'
|
||||
self.fields['utilisateur_asso'].label = 'Compte utilisé pour faire les modifications depuis /admin'
|
||||
self.fields['utilisateur_asso'].label = 'Compte utilisé pour\
|
||||
faire les modifications depuis /admin'
|
||||
|
||||
|
||||
class EditMailMessageOptionForm(ModelForm):
|
||||
"""Formulaire d'edition des messages de bienvenue personnalisés"""
|
||||
class Meta:
|
||||
model = MailMessageOption
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditMailMessageOptionForm, self).__init__(*args, **kwargs)
|
||||
self.fields['welcome_mail_fr'].label = 'Message dans le mail de bienvenue en français'
|
||||
self.fields['welcome_mail_en'].label = 'Message dans le mail de bienvenue en anglais'
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(EditMailMessageOptionForm, self).__init__(
|
||||
*args,
|
||||
prefix=prefix,
|
||||
**kwargs
|
||||
)
|
||||
self.fields['welcome_mail_fr'].label = 'Message dans le\
|
||||
mail de bienvenue en français'
|
||||
self.fields['welcome_mail_en'].label = 'Message dans le\
|
||||
mail de bienvenue en anglais'
|
||||
|
||||
|
||||
class ServiceForm(ModelForm):
|
||||
"""Edition, ajout de services sur la page d'accueil"""
|
||||
class Meta:
|
||||
model = Service
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(ServiceForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
|
||||
class DelServiceForm(Form):
|
||||
services = forms.ModelMultipleChoiceField(queryset=Service.objects.all(), label="Enregistrements service actuels", widget=forms.CheckboxSelectMultiple)
|
||||
"""Suppression de services sur la page d'accueil"""
|
||||
services = forms.ModelMultipleChoiceField(
|
||||
queryset=Service.objects.all(),
|
||||
label="Enregistrements service actuels",
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
|
|
@ -20,26 +20,38 @@
|
|||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
"""
|
||||
Reglages généraux, machines, utilisateurs, mail, general pour l'application.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
from cotisations.models import Paiement
|
||||
from machines.models import Vlan
|
||||
|
||||
|
||||
class OptionalUser(models.Model):
|
||||
"""Options pour l'user : obligation ou nom du telephone,
|
||||
activation ou non du solde, autorisation du negatif, fingerprint etc"""
|
||||
PRETTY_NAME = "Options utilisateur"
|
||||
|
||||
is_tel_mandatory = models.BooleanField(default=True)
|
||||
user_solde = models.BooleanField(default=False)
|
||||
solde_negatif = models.DecimalField(max_digits=5, decimal_places=2, default=0)
|
||||
solde_negatif = models.DecimalField(
|
||||
max_digits=5,
|
||||
decimal_places=2,
|
||||
default=0
|
||||
)
|
||||
gpg_fingerprint = models.BooleanField(default=True)
|
||||
|
||||
def clean(self):
|
||||
"""Creation du mode de paiement par solde"""
|
||||
if self.user_solde:
|
||||
Paiement.objects.get_or_create(moyen="Solde")
|
||||
|
||||
|
||||
class OptionalMachine(models.Model):
|
||||
"""Options pour les machines : maximum de machines ou d'alias par user
|
||||
sans droit, activation de l'ipv6"""
|
||||
PRETTY_NAME = "Options machines"
|
||||
|
||||
password_machine = models.BooleanField(default=False)
|
||||
|
@ -47,21 +59,43 @@ class OptionalMachine(models.Model):
|
|||
max_lambdauser_aliases = models.IntegerField(default=10)
|
||||
ipv6 = models.BooleanField(default=False)
|
||||
|
||||
|
||||
class OptionalTopologie(models.Model):
|
||||
"""Reglages pour la topologie : mode d'accès radius, vlan où placer
|
||||
les machines en accept ou reject"""
|
||||
PRETTY_NAME = "Options topologie"
|
||||
MACHINE = 'MACHINE'
|
||||
DEFINED = 'DEFINED'
|
||||
CHOICE_RADIUS = (
|
||||
(MACHINE, 'Sur le vlan de la plage ip machine'),
|
||||
(DEFINED, 'Prédéfini dans "Vlan où placer les machines après acceptation RADIUS"'),
|
||||
(DEFINED, 'Prédéfini dans "Vlan où placer les machines\
|
||||
après acceptation RADIUS"'),
|
||||
)
|
||||
|
||||
radius_general_policy = models.CharField(
|
||||
max_length=32,
|
||||
choices=CHOICE_RADIUS,
|
||||
default='DEFINED'
|
||||
)
|
||||
vlan_decision_ok = models.OneToOneField(
|
||||
'machines.Vlan',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='decision_ok',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
vlan_decision_nok = models.OneToOneField(
|
||||
'machines.Vlan',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='decision_nok',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
|
||||
|
||||
radius_general_policy = models.CharField(max_length=32, choices=CHOICE_RADIUS, default='DEFINED')
|
||||
vlan_decision_ok = models.OneToOneField('machines.Vlan', on_delete=models.PROTECT, related_name='decision_ok', blank=True, null=True)
|
||||
vlan_decision_nok = models.OneToOneField('machines.Vlan', on_delete=models.PROTECT, related_name='decision_nok', blank=True, null=True)
|
||||
|
||||
class GeneralOption(models.Model):
|
||||
"""Options générales : nombre de resultats par page, nom du site,
|
||||
temps où les liens sont valides"""
|
||||
PRETTY_NAME = "Options générales"
|
||||
|
||||
search_display_page = models.IntegerField(default=15)
|
||||
|
@ -71,7 +105,10 @@ class GeneralOption(models.Model):
|
|||
site_name = models.CharField(max_length=32, default="Re2o")
|
||||
email_from = models.EmailField(default="www-data@serveur.net")
|
||||
|
||||
|
||||
class Service(models.Model):
|
||||
"""Liste des services affichés sur la page d'accueil : url, description,
|
||||
image et nom"""
|
||||
name = models.CharField(max_length=32)
|
||||
url = models.URLField()
|
||||
description = models.TextField()
|
||||
|
@ -80,21 +117,32 @@ class Service(models.Model):
|
|||
def __str__(self):
|
||||
return str(self.name)
|
||||
|
||||
|
||||
class AssoOption(models.Model):
|
||||
"""Options générales de l'asso : siret, addresse, nom, etc"""
|
||||
PRETTY_NAME = "Options de l'association"
|
||||
|
||||
name = models.CharField(default="Association réseau école machin", max_length=256)
|
||||
name = models.CharField(
|
||||
default="Association réseau école machin",
|
||||
max_length=256
|
||||
)
|
||||
siret = models.CharField(default="00000000000000", max_length=32)
|
||||
adresse1 = models.CharField(default="1 Rue de exemple", max_length=128)
|
||||
adresse2 = models.CharField(default="94230 Cachan", max_length=128)
|
||||
contact = models.EmailField(default="contact@example.org")
|
||||
telephone = models.CharField(max_length=15, default="0000000000")
|
||||
pseudo = models.CharField(default="Asso", max_length=32)
|
||||
utilisateur_asso = models.OneToOneField('users.User', on_delete=models.PROTECT, blank=True, null=True)
|
||||
utilisateur_asso = models.OneToOneField(
|
||||
'users.User',
|
||||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
|
||||
|
||||
class MailMessageOption(models.Model):
|
||||
"""Reglages, mail de bienvenue et autre"""
|
||||
PRETTY_NAME = "Options de corps de mail"
|
||||
|
||||
welcome_mail_fr = models.TextField(default="")
|
||||
welcome_mail_en = models.TextField(default="")
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load massive_bootstrap_form %}
|
||||
|
||||
{% block title %}Création et modification des préférences{% endblock %}
|
||||
|
||||
|
@ -34,7 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form options %}
|
||||
{% massive_bootstrap_form options 'utilisateur_asso' %}
|
||||
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
|
||||
</form>
|
||||
<br />
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
"""
|
||||
Urls de l'application preferences, pointant vers les fonctions de views
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
@ -28,15 +31,47 @@ from . import views
|
|||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^edit_options/(?P<section>OptionalUser)$', views.edit_options, name='edit-options'),
|
||||
url(r'^edit_options/(?P<section>OptionalMachine)$', views.edit_options, name='edit-options'),
|
||||
url(r'^edit_options/(?P<section>OptionalTopologie)$', views.edit_options, name='edit-options'),
|
||||
url(r'^edit_options/(?P<section>GeneralOption)$', views.edit_options, name='edit-options'),
|
||||
url(r'^edit_options/(?P<section>AssoOption)$', views.edit_options, name='edit-options'),
|
||||
url(r'^edit_options/(?P<section>MailMessageOption)$', views.edit_options, name='edit-options'),
|
||||
url(
|
||||
r'^edit_options/(?P<section>OptionalUser)$',
|
||||
views.edit_options,
|
||||
name='edit-options'
|
||||
),
|
||||
url(
|
||||
r'^edit_options/(?P<section>OptionalMachine)$',
|
||||
views.edit_options,
|
||||
name='edit-options'
|
||||
),
|
||||
url(
|
||||
r'^edit_options/(?P<section>OptionalTopologie)$',
|
||||
views.edit_options,
|
||||
name='edit-options'
|
||||
),
|
||||
url(
|
||||
r'^edit_options/(?P<section>GeneralOption)$',
|
||||
views.edit_options,
|
||||
name='edit-options'
|
||||
),
|
||||
url(
|
||||
r'^edit_options/(?P<section>AssoOption)$',
|
||||
views.edit_options,
|
||||
name='edit-options'
|
||||
),
|
||||
url(
|
||||
r'^edit_options/(?P<section>MailMessageOption)$',
|
||||
views.edit_options,
|
||||
name='edit-options'
|
||||
),
|
||||
url(r'^add_services/$', views.add_services, name='add-services'),
|
||||
url(r'^edit_services/(?P<servicesid>[0-9]+)$', views.edit_services, name='edit-services'),
|
||||
url(
|
||||
r'^edit_services/(?P<servicesid>[0-9]+)$',
|
||||
views.edit_services,
|
||||
name='edit-services'
|
||||
),
|
||||
url(r'^del_services/$', views.del_services, name='del-services'),
|
||||
url(r'^history/(?P<object>service)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||
url(
|
||||
r'^history/(?P<object>service)/(?P<id>[0-9]+)$',
|
||||
views.history,
|
||||
name='history'
|
||||
),
|
||||
url(r'^$', views.display_options, name='display-options'),
|
||||
]
|
||||
|
|
|
@ -23,48 +23,53 @@
|
|||
# App de gestion des machines pour re2o
|
||||
# Gabriel Détraz, Augustin Lemesle
|
||||
# Gplv2
|
||||
"""
|
||||
Vue d'affichage, et de modification des réglages (réglages machine,
|
||||
topologie, users, service...)
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.shortcuts import get_object_or_404, render, redirect
|
||||
from django.template.context_processors import csrf
|
||||
from django.shortcuts import render, redirect
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.template import Context, RequestContext, loader
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.db.models import Max, ProtectedError
|
||||
from django.db import IntegrityError
|
||||
from django.core.mail import send_mail
|
||||
from django.utils import timezone
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db.models import ProtectedError
|
||||
from django.db import transaction
|
||||
|
||||
from reversion.models import Version
|
||||
from reversion import revisions as reversion
|
||||
|
||||
from re2o.views import form
|
||||
from .forms import ServiceForm, DelServiceForm
|
||||
from .models import Service, OptionalUser, OptionalMachine, AssoOption, MailMessageOption, GeneralOption, OptionalTopologie
|
||||
from .models import Service, OptionalUser, OptionalMachine, AssoOption
|
||||
from .models import MailMessageOption, GeneralOption, OptionalTopologie
|
||||
from . import models
|
||||
from . import forms
|
||||
|
||||
def form(ctx, template, request):
|
||||
c = ctx
|
||||
c.update(csrf(request))
|
||||
return render(request, template, c)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def display_options(request):
|
||||
useroptions, created = OptionalUser.objects.get_or_create()
|
||||
machineoptions, created = OptionalMachine.objects.get_or_create()
|
||||
topologieoptions, created = OptionalTopologie.objects.get_or_create()
|
||||
generaloptions, created = GeneralOption.objects.get_or_create()
|
||||
assooptions, created = AssoOption.objects.get_or_create()
|
||||
mailmessageoptions, created = MailMessageOption.objects.get_or_create()
|
||||
"""Vue pour affichage des options (en vrac) classé selon les models
|
||||
correspondants dans un tableau"""
|
||||
useroptions, _created = OptionalUser.objects.get_or_create()
|
||||
machineoptions, _created = OptionalMachine.objects.get_or_create()
|
||||
topologieoptions, _created = OptionalTopologie.objects.get_or_create()
|
||||
generaloptions, _created = GeneralOption.objects.get_or_create()
|
||||
assooptions, _created = AssoOption.objects.get_or_create()
|
||||
mailmessageoptions, _created = MailMessageOption.objects.get_or_create()
|
||||
service_list = Service.objects.all()
|
||||
return form({'useroptions': useroptions, 'machineoptions': machineoptions, 'topologieoptions': topologieoptions, 'generaloptions': generaloptions, 'assooptions' : assooptions, 'mailmessageoptions' : mailmessageoptions, 'service_list':service_list}, 'preferences/display_preferences.html', request)
|
||||
return form({
|
||||
'useroptions': useroptions,
|
||||
'machineoptions': machineoptions,
|
||||
'topologieoptions': topologieoptions,
|
||||
'generaloptions': generaloptions,
|
||||
'assooptions': assooptions,
|
||||
'mailmessageoptions': mailmessageoptions,
|
||||
'service_list': service_list
|
||||
}, 'preferences/display_preferences.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('admin')
|
||||
|
@ -73,23 +78,36 @@ def edit_options(request, section):
|
|||
model = getattr(models, section, None)
|
||||
form_instance = getattr(forms, 'Edit' + section + 'Form', None)
|
||||
if model and form:
|
||||
options_instance, created = model.objects.get_or_create()
|
||||
options = form_instance(request.POST or None, instance=options_instance)
|
||||
options_instance, _created = model.objects.get_or_create()
|
||||
options = form_instance(
|
||||
request.POST or None,
|
||||
instance=options_instance
|
||||
)
|
||||
if options.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
options.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in options.changed_data))
|
||||
reversion.set_comment(
|
||||
"Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in options.changed_data
|
||||
)
|
||||
)
|
||||
messages.success(request, "Préférences modifiées")
|
||||
return redirect("/preferences/")
|
||||
return form({'options': options}, 'preferences/edit_preferences.html', request)
|
||||
return form(
|
||||
{'options': options},
|
||||
'preferences/edit_preferences.html',
|
||||
request
|
||||
)
|
||||
else:
|
||||
messages.error(request, "Objet inconnu")
|
||||
return redirect("/preferences/")
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('admin')
|
||||
def add_services(request):
|
||||
"""Ajout d'un service de la page d'accueil"""
|
||||
services = ServiceForm(request.POST or None)
|
||||
if services.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
|
@ -98,11 +116,17 @@ def add_services(request):
|
|||
reversion.set_comment("Création")
|
||||
messages.success(request, "Cet enregistrement ns a été ajouté")
|
||||
return redirect("/preferences/")
|
||||
return form({'preferenceform': services}, 'preferences/preferences.html', request)
|
||||
return form(
|
||||
{'preferenceform': services},
|
||||
'preferences/preferences.html',
|
||||
request
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('admin')
|
||||
def edit_services(request, servicesid):
|
||||
"""Edition des services affichés sur la page d'accueil"""
|
||||
try:
|
||||
services_instance = Service.objects.get(pk=servicesid)
|
||||
except Service.DoesNotExist:
|
||||
|
@ -113,14 +137,24 @@ def edit_services(request, servicesid):
|
|||
with transaction.atomic(), reversion.create_revision():
|
||||
services.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in services.changed_data))
|
||||
reversion.set_comment(
|
||||
"Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in services.changed_data
|
||||
)
|
||||
)
|
||||
messages.success(request, "Service modifié")
|
||||
return redirect("/preferences/")
|
||||
return form({'preferenceform': services}, 'preferences/preferences.html', request)
|
||||
return form(
|
||||
{'preferenceform': services},
|
||||
'preferences/preferences.html',
|
||||
request
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('admin')
|
||||
def del_services(request):
|
||||
"""Suppression d'un service de la page d'accueil"""
|
||||
services = DelServiceForm(request.POST or None)
|
||||
if services.is_valid():
|
||||
services_dels = services.cleaned_data['services']
|
||||
|
@ -131,20 +165,28 @@ def del_services(request):
|
|||
reversion.set_user(request.user)
|
||||
messages.success(request, "Le services a été supprimée")
|
||||
except ProtectedError:
|
||||
messages.error(request, "Erreur le service suivant %s ne peut être supprimé" % services_del)
|
||||
messages.error(request, "Erreur le service\
|
||||
suivant %s ne peut être supprimé" % services_del)
|
||||
return redirect("/preferences/")
|
||||
return form({'preferenceform': services}, 'preferences/preferences.html', request)
|
||||
return form(
|
||||
{'preferenceform': services},
|
||||
'preferences/preferences.html',
|
||||
request
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def history(request, object, id):
|
||||
if object == 'service':
|
||||
def history(request, object_name, object_id):
|
||||
"""Historique de creation et de modification d'un service affiché sur
|
||||
la page d'accueil"""
|
||||
if object_name == 'service':
|
||||
try:
|
||||
object_instance = Service.objects.get(pk=id)
|
||||
object_instance = Service.objects.get(pk=object_id)
|
||||
except Service.DoesNotExist:
|
||||
messages.error(request, "Service inexistant")
|
||||
return redirect("/preferences/")
|
||||
options, created = GeneralOption.objects.get_or_create()
|
||||
options, _created = GeneralOption.objects.get_or_create()
|
||||
pagination_number = options.pagination_number
|
||||
reversions = Version.objects.get_for_object(object_instance)
|
||||
paginator = Paginator(reversions, pagination_number)
|
||||
|
@ -157,4 +199,7 @@ def history(request, object, id):
|
|||
except EmptyPage:
|
||||
# If page is out of range (e.g. 9999), deliver last page of results.
|
||||
reversions = paginator.page(paginator.num_pages)
|
||||
return render(request, 're2o/history.html', {'reversions': reversions, 'object': object_instance})
|
||||
return render(request, 're2o/history.html', {
|
||||
'reversions': reversions,
|
||||
'object': object_instance
|
||||
})
|
||||
|
|
|
@ -19,15 +19,19 @@
|
|||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
"""Fonction de context, variables renvoyées à toutes les vues"""
|
||||
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from machines.models import Interface, Machine
|
||||
from preferences.models import GeneralOption, OptionalMachine
|
||||
|
||||
|
||||
def context_user(request):
|
||||
general_options, created = GeneralOption.objects.get_or_create()
|
||||
machine_options, created = OptionalMachine.objects.get_or_create()
|
||||
"""Fonction de context lorsqu'un user est logué (ou non),
|
||||
renvoie les infos sur l'user, la liste de ses droits, ses machines"""
|
||||
general_options, _created = GeneralOption.objects.get_or_create()
|
||||
machine_options, _created = OptionalMachine.objects.get_or_create()
|
||||
user = request.user
|
||||
if user.is_authenticated():
|
||||
interfaces = user.user_interfaces()
|
||||
|
|
572
re2o/templatetags/massive_bootstrap_form.py
Normal file
572
re2o/templatetags/massive_bootstrap_form.py
Normal file
|
@ -0,0 +1,572 @@
|
|||
# -*- 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.
|
||||
|
||||
from django import template
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.forms import TextInput
|
||||
from django.forms.widgets import Select
|
||||
from bootstrap3.templatetags.bootstrap3 import bootstrap_form
|
||||
from bootstrap3.utils import render_tag
|
||||
from bootstrap3.forms import render_field
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.simple_tag
|
||||
def massive_bootstrap_form(form, mbf_fields, *args, **kwargs):
|
||||
"""
|
||||
Render a form where some specific fields are rendered using Twitter
|
||||
Typeahead and/or splitree's Bootstrap Tokenfield to improve the performance, the
|
||||
speed and UX when dealing with very large datasets (select with 50k+ elts
|
||||
for instance).
|
||||
When the fields specified should normally be rendered as a select with
|
||||
single selectable option, Twitter Typeahead is used for a better display
|
||||
and the matching query engine. When dealing with multiple selectable
|
||||
options, sliptree's Bootstrap Tokenfield in addition with Typeahead.
|
||||
For convenience, it accepts the same parameters as a standard bootstrap
|
||||
can accept.
|
||||
|
||||
**Tag name**::
|
||||
|
||||
massive_bootstrap_form
|
||||
|
||||
**Parameters**:
|
||||
|
||||
form (required)
|
||||
The form that is to be rendered
|
||||
|
||||
mbf_fields (optional)
|
||||
A list of field names (comma separated) that should be rendered
|
||||
with Typeahead/Tokenfield instead of the default bootstrap
|
||||
renderer.
|
||||
If not specified, all fields will be rendered as a normal bootstrap
|
||||
field.
|
||||
|
||||
mbf_param (optional)
|
||||
A dict of parameters for the massive_bootstrap_form tag. The
|
||||
possible parameters are the following.
|
||||
|
||||
choices (optional)
|
||||
A dict of strings representing the choices in JS. The keys of
|
||||
the dict are the names of the concerned fields. The choices
|
||||
must be an array of objects. Each of those objects must at
|
||||
least have the fields 'key' (value to send) and 'value' (value
|
||||
to display). Other fields can be added as desired.
|
||||
For a more complex structure you should also consider
|
||||
reimplementing the engine and the match_func.
|
||||
If not specified, the key is the id of the object and the value
|
||||
is its string representation as in a normal bootstrap form.
|
||||
Example :
|
||||
'choices' : {
|
||||
'field_A':'[{key:0,value:"choice0",extra:"data0"},{...},...]',
|
||||
'field_B':...,
|
||||
...
|
||||
}
|
||||
|
||||
engine (optional)
|
||||
A dict of strings representating the engine used for matching
|
||||
queries and possible values with typeahead. The keys of the
|
||||
dict are the names of the concerned fields. The string is valid
|
||||
JS code.
|
||||
If not specified, BloodHound with relevant basic properties is
|
||||
used.
|
||||
Example :
|
||||
'engine' : {'field_A': 'new Bloodhound()', 'field_B': ..., ...}
|
||||
|
||||
match_func (optional)
|
||||
A dict of strings representing a valid JS function used in the
|
||||
dataset to overload the matching engine. The keys of the dict
|
||||
are the names of the concerned fields. This function is used
|
||||
the source of the dataset. This function receives 2 parameters,
|
||||
the query and the synchronize function as specified in
|
||||
typeahead.js documentation. If needed, the local variables
|
||||
'choices_<fieldname>' and 'engine_<fieldname>' contains
|
||||
respectively the array of all possible values and the engine
|
||||
to match queries with possible values.
|
||||
If not specified, the function used display up to the 10 first
|
||||
elements if the query is empty and else the matching results.
|
||||
Example :
|
||||
'match_func' : {
|
||||
'field_A': 'function(q, sync) { engine.search(q, sync); }',
|
||||
'field_B': ...,
|
||||
...
|
||||
}
|
||||
|
||||
update_on (optional)
|
||||
A dict of list of ids that the values depends on. The engine
|
||||
and the typeahead properties are recalculated and reapplied.
|
||||
Example :
|
||||
'addition' : {
|
||||
'field_A' : [ 'id0', 'id1', ... ] ,
|
||||
'field_B' : ... ,
|
||||
...
|
||||
}
|
||||
|
||||
See boostrap_form_ for other arguments
|
||||
|
||||
**Usage**::
|
||||
|
||||
{% massive_bootstrap_form
|
||||
form
|
||||
[ '<field1>[,<field2>[,...]]' ]
|
||||
[ mbf_param = {
|
||||
[ 'choices': {
|
||||
[ '<field1>': '<choices1>'
|
||||
[, '<field2>': '<choices2>'
|
||||
[, ... ] ] ]
|
||||
} ]
|
||||
[, 'engine': {
|
||||
[ '<field1>': '<engine1>'
|
||||
[, '<field2>': '<engine2>'
|
||||
[, ... ] ] ]
|
||||
} ]
|
||||
[, 'match_func': {
|
||||
[ '<field1>': '<match_func1>'
|
||||
[, '<field2>': '<match_func2>'
|
||||
[, ... ] ] ]
|
||||
} ]
|
||||
[, 'update_on': {
|
||||
[ '<field1>': '<update_on1>'
|
||||
[, '<field2>': '<update_on2>'
|
||||
[, ... ] ] ]
|
||||
} ]
|
||||
} ]
|
||||
[ <standard boostrap_form parameters> ]
|
||||
%}
|
||||
|
||||
**Example**:
|
||||
|
||||
{% massive_bootstrap_form form 'ipv4' choices='[...]' %}
|
||||
"""
|
||||
|
||||
fields = mbf_fields.split(',')
|
||||
param = kwargs.pop('mbf_param', {})
|
||||
exclude = param.get('exclude', '').split(',')
|
||||
choices = param.get('choices', {})
|
||||
engine = param.get('engine', {})
|
||||
match_func = param.get('match_func', {})
|
||||
update_on = param.get('update_on', {})
|
||||
hidden_fields = [h.name for h in form.hidden_fields()]
|
||||
|
||||
html = ''
|
||||
|
||||
for f_name, f_value in form.fields.items() :
|
||||
if not f_name in exclude :
|
||||
if f_name in fields and not f_name in hidden_fields :
|
||||
|
||||
if not isinstance(f_value.widget, Select) :
|
||||
raise ValueError(
|
||||
('Field named {f_name} from {form} is not a Select and'
|
||||
'can\'t be rendered with massive_bootstrap_form.'
|
||||
).format(
|
||||
f_name=f_name,
|
||||
form=form
|
||||
)
|
||||
)
|
||||
|
||||
multiple = f_value.widget.allow_multiple_selected
|
||||
f_bound = f_value.get_bound_field( form, f_name )
|
||||
|
||||
f_value.widget = TextInput(
|
||||
attrs = {
|
||||
'name': 'mbf_'+f_name,
|
||||
'placeholder': f_value.empty_label
|
||||
}
|
||||
)
|
||||
html += render_field(
|
||||
f_value.get_bound_field( form, f_name ),
|
||||
*args,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
if multiple :
|
||||
content = mbf_js(
|
||||
f_name,
|
||||
f_value,
|
||||
f_bound,
|
||||
multiple,
|
||||
choices,
|
||||
engine,
|
||||
match_func,
|
||||
update_on
|
||||
)
|
||||
else :
|
||||
content = hidden_tag( f_bound, f_name ) + mbf_js(
|
||||
f_name,
|
||||
f_value,
|
||||
f_bound,
|
||||
multiple,
|
||||
choices,
|
||||
engine,
|
||||
match_func,
|
||||
update_on
|
||||
)
|
||||
html += render_tag(
|
||||
'div',
|
||||
content = content,
|
||||
attrs = { 'id': custom_div_id( f_bound ) }
|
||||
)
|
||||
|
||||
else:
|
||||
html += render_field(
|
||||
f_value.get_bound_field( form, f_name ),
|
||||
*args,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
return mark_safe( html )
|
||||
|
||||
def input_id( f_bound ) :
|
||||
""" The id of the HTML input element """
|
||||
return f_bound.auto_id
|
||||
|
||||
def hidden_id( f_bound ):
|
||||
""" The id of the HTML hidden input element """
|
||||
return input_id( f_bound ) + '_hidden'
|
||||
|
||||
def custom_div_id( f_bound ):
|
||||
""" The id of the HTML div element containing values and script """
|
||||
return input_id( f_bound ) + '_div'
|
||||
|
||||
def hidden_tag( f_bound, f_name ):
|
||||
""" The HTML hidden input element """
|
||||
return render_tag(
|
||||
'input',
|
||||
attrs={
|
||||
'id': hidden_id( f_bound ),
|
||||
'name': f_bound.html_name,
|
||||
'type': 'hidden',
|
||||
'value': f_bound.value() or ""
|
||||
}
|
||||
)
|
||||
|
||||
def mbf_js( f_name, f_value, f_bound, multiple,
|
||||
choices_, engine_, match_func_, update_on_ ) :
|
||||
""" The whole script to use """
|
||||
|
||||
choices = ( mark_safe( choices_[f_name] ) if f_name in choices_.keys()
|
||||
else default_choices( f_value ) )
|
||||
|
||||
engine = ( mark_safe( engine_[f_name] ) if f_name in engine_.keys()
|
||||
else default_engine ( f_name ) )
|
||||
|
||||
match_func = ( mark_safe( match_func_[f_name] )
|
||||
if f_name in match_func_.keys() else default_match_func( f_name ) )
|
||||
|
||||
update_on = update_on_[f_name] if f_name in update_on_.keys() else []
|
||||
|
||||
if multiple :
|
||||
js_content = (
|
||||
'var choices_{f_name} = {choices};'
|
||||
'var engine_{f_name};'
|
||||
'var setup_{f_name} = function() {{'
|
||||
'engine_{f_name} = {engine};'
|
||||
'$( "#{input_id}" ).tokenfield( "destroy" );'
|
||||
'$( "#{input_id}" ).tokenfield({{typeahead: [ {datasets} ] }});'
|
||||
'}};'
|
||||
'$( "#{input_id}" ).bind( "tokenfield:createtoken", {create} );'
|
||||
'$( "#{input_id}" ).bind( "tokenfield:edittoken", {edit} );'
|
||||
'$( "#{input_id}" ).bind( "tokenfield:removetoken", {remove} );'
|
||||
'{updates}'
|
||||
'$( "#{input_id}" ).ready( function() {{'
|
||||
'setup_{f_name}();'
|
||||
'{init_input}'
|
||||
'}} );'
|
||||
).format(
|
||||
f_name = f_name,
|
||||
choices = choices,
|
||||
engine = engine,
|
||||
input_id = input_id( f_bound ),
|
||||
datasets = default_datasets( f_name, match_func ),
|
||||
create = tokenfield_create( f_name, f_bound ),
|
||||
edit = tokenfield_edit( f_name, f_bound ),
|
||||
remove = tokenfield_remove( f_name, f_bound ),
|
||||
updates = ''.join( [ (
|
||||
'$( "#{u_id}" ).change( function() {{'
|
||||
'setup_{f_name}();'
|
||||
'{reset_input}'
|
||||
'}} );'
|
||||
).format(
|
||||
u_id = u_id,
|
||||
reset_input = tokenfield_reset_input( f_bound ),
|
||||
f_name = f_name
|
||||
) for u_id in update_on ]
|
||||
),
|
||||
init_input = tokenfield_init_input( f_name, f_bound ),
|
||||
)
|
||||
else :
|
||||
js_content = (
|
||||
'var choices_{f_name} = {choices};'
|
||||
'var engine_{f_name};'
|
||||
'var setup_{f_name} = function() {{'
|
||||
'engine_{f_name} = {engine};'
|
||||
'$( "#{input_id}" ).typeahead( "destroy" );'
|
||||
'$( "#{input_id}" ).typeahead( {datasets} );'
|
||||
'}};'
|
||||
'$( "#{input_id}" ).bind( "typeahead:select", {select} );'
|
||||
'$( "#{input_id}" ).bind( "typeahead:change", {change} );'
|
||||
'{updates}'
|
||||
'$( "#{input_id}" ).ready( function() {{'
|
||||
'setup_{f_name}();'
|
||||
'{init_input}'
|
||||
'}} );'
|
||||
).format(
|
||||
f_name = f_name,
|
||||
choices = choices,
|
||||
engine = engine,
|
||||
input_id = input_id( f_bound ),
|
||||
datasets = default_datasets( f_name, match_func ),
|
||||
select = typeahead_select( f_bound ),
|
||||
change = typeahead_change( f_bound ),
|
||||
updates = ''.join( [ (
|
||||
'$( "#{u_id}" ).change( function() {{'
|
||||
'setup_{f_name}();'
|
||||
'{reset_input}'
|
||||
'}} );'
|
||||
).format(
|
||||
u_id = u_id,
|
||||
reset_input = typeahead_reset_input( f_bound ),
|
||||
f_name = f_name
|
||||
) for u_id in update_on ]
|
||||
),
|
||||
init_input = typeahead_init_input( f_name, f_bound ),
|
||||
)
|
||||
|
||||
return render_tag( 'script', content=mark_safe( js_content ) )
|
||||
|
||||
def typeahead_init_input( f_name, f_bound ) :
|
||||
""" The JS script to init the fields values """
|
||||
init_key = f_bound.value() or '""'
|
||||
return (
|
||||
'$( "#{input_id}" ).typeahead("val", {init_val});'
|
||||
'$( "#{hidden_id}" ).val( {init_key} );'
|
||||
).format(
|
||||
input_id = input_id( f_bound ),
|
||||
init_val = '""' if init_key == '""' else
|
||||
'engine_{f_name}.get( {init_key} )[0].value'.format(
|
||||
f_name = f_name,
|
||||
init_key = init_key
|
||||
),
|
||||
init_key = init_key,
|
||||
hidden_id = hidden_id( f_bound )
|
||||
)
|
||||
|
||||
def typeahead_reset_input( f_bound ) :
|
||||
""" The JS script to reset the fields values """
|
||||
return (
|
||||
'$( "#{input_id}" ).typeahead("val", "");'
|
||||
'$( "#{hidden_id}" ).val( "" );'
|
||||
).format(
|
||||
input_id = input_id( f_bound ),
|
||||
hidden_id = hidden_id( f_bound )
|
||||
)
|
||||
|
||||
def tokenfield_init_input( f_name, f_bound ) :
|
||||
""" The JS script to init the fields values """
|
||||
init_key = f_bound.value() or '""'
|
||||
return (
|
||||
'$( "#{input_id}" ).tokenfield("setTokens", {init_val});'
|
||||
).format(
|
||||
input_id = input_id( f_bound ),
|
||||
init_val = '""' if init_key == '""' else (
|
||||
'engine_{f_name}.get( {init_key} ).map('
|
||||
'function(o) {{ return o.value; }}'
|
||||
')').format(
|
||||
f_name = f_name,
|
||||
init_key = init_key
|
||||
),
|
||||
init_key = init_key,
|
||||
)
|
||||
|
||||
def tokenfield_reset_input( f_bound ) :
|
||||
""" The JS script to reset the fields values """
|
||||
return (
|
||||
'$( "#{input_id}" ).tokenfield("setTokens", "");'
|
||||
).format(
|
||||
input_id = input_id( f_bound ),
|
||||
)
|
||||
|
||||
def default_choices( f_value ) :
|
||||
""" The JS script creating the variable choices_<fieldname> """
|
||||
return '[{objects}]'.format(
|
||||
objects = ','.join(
|
||||
[ '{{key:{k},value:"{v}"}}'.format(
|
||||
k = choice[0] if choice[0] != '' else '""',
|
||||
v = choice[1]
|
||||
) for choice in f_value.choices ]
|
||||
)
|
||||
)
|
||||
|
||||
def default_engine ( f_name ) :
|
||||
""" The JS script creating the variable engine_<field_name> """
|
||||
return (
|
||||
'new Bloodhound({{'
|
||||
'datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"),'
|
||||
'queryTokenizer: Bloodhound.tokenizers.whitespace,'
|
||||
'local: choices_{f_name},'
|
||||
'identify: function(obj) {{ return obj.key; }}'
|
||||
'}})'
|
||||
).format(
|
||||
f_name = f_name
|
||||
)
|
||||
|
||||
def default_datasets( f_name, match_func ) :
|
||||
""" The JS script creating the datasets to use with typeahead """
|
||||
return (
|
||||
'{{'
|
||||
'hint: true,'
|
||||
'highlight: true,'
|
||||
'minLength: 0'
|
||||
'}},'
|
||||
'{{'
|
||||
'display: "value",'
|
||||
'name: "{f_name}",'
|
||||
'source: {match_func}'
|
||||
'}}'
|
||||
).format(
|
||||
f_name = f_name,
|
||||
match_func = match_func
|
||||
)
|
||||
|
||||
def default_match_func ( f_name ) :
|
||||
""" The JS script creating the matching function to use with typeahed """
|
||||
return (
|
||||
'function ( q, sync ) {{'
|
||||
'if ( q === "" ) {{'
|
||||
'var first = choices_{f_name}.slice( 0, 5 ).map('
|
||||
'function ( obj ) {{ return obj.key; }}'
|
||||
');'
|
||||
'sync( engine_{f_name}.get( first ) );'
|
||||
'}} else {{'
|
||||
'engine_{f_name}.search( q, sync );'
|
||||
'}}'
|
||||
'}}'
|
||||
).format(
|
||||
f_name = f_name
|
||||
)
|
||||
|
||||
def typeahead_select( f_bound ):
|
||||
""" The JS script creating the function triggered when an item is
|
||||
selected through typeahead """
|
||||
return (
|
||||
'function(evt, item) {{'
|
||||
'$( "#{hidden_id}" ).val( item.key );'
|
||||
'$( "#{hidden_id}" ).change();'
|
||||
'return item;'
|
||||
'}}'
|
||||
).format(
|
||||
hidden_id = hidden_id( f_bound )
|
||||
)
|
||||
|
||||
def typeahead_change( f_bound ):
|
||||
""" The JS script creating the function triggered when an item is changed
|
||||
(i.e. looses focus and value has changed since the moment it gained focus
|
||||
"""
|
||||
return (
|
||||
'function(evt) {{'
|
||||
'if ( $( "#{input_id}" ).typeahead( "val" ) === "" ) {{'
|
||||
'$( "#{hidden_id}" ).val( "" );'
|
||||
'$( "#{hidden_id}" ).change();'
|
||||
'}}'
|
||||
'}}'
|
||||
).format(
|
||||
input_id = input_id( f_bound ),
|
||||
hidden_id = hidden_id( f_bound )
|
||||
)
|
||||
|
||||
def tokenfield_create( f_name, f_bound ):
|
||||
""" The JS script triggered when a new token is created in tokenfield. """
|
||||
return (
|
||||
'function(evt) {{'
|
||||
'var k = evt.attrs.key;'
|
||||
'if (!k) {{'
|
||||
'var data = evt.attrs.value;'
|
||||
'var i = 0;'
|
||||
'while ( i<choices_{f_name}.length &&'
|
||||
'choices_{f_name}[i].value !== data ) {{'
|
||||
'i++;'
|
||||
'}}'
|
||||
'if ( i === choices_{f_name}.length ) {{ return false; }}'
|
||||
'k = choices_{f_name}[i].key;'
|
||||
'}}'
|
||||
'var new_input = document.createElement("input");'
|
||||
'new_input.type = "hidden";'
|
||||
'new_input.id = "{hidden_id}_"+k.toString();'
|
||||
'new_input.value = k.toString();'
|
||||
'new_input.name = "{name}";'
|
||||
'$( "#{div_id}" ).append(new_input);'
|
||||
'}}'
|
||||
).format(
|
||||
f_name = f_name,
|
||||
hidden_id = hidden_id( f_bound ),
|
||||
name = f_bound.html_name,
|
||||
div_id = custom_div_id( f_bound )
|
||||
)
|
||||
|
||||
def tokenfield_edit( f_name, f_bound ):
|
||||
""" The JS script triggered when a token is edited in tokenfield. """
|
||||
return (
|
||||
'function(evt) {{'
|
||||
'var k = evt.attrs.key;'
|
||||
'if (!k) {{'
|
||||
'var data = evt.attrs.value;'
|
||||
'var i = 0;'
|
||||
'while ( i<choices_{f_name}.length &&'
|
||||
'choices_{f_name}[i].value !== data ) {{'
|
||||
'i++;'
|
||||
'}}'
|
||||
'if ( i === choices_{f_name}.length ) {{ return true; }}'
|
||||
'k = choices_{f_name}[i].key;'
|
||||
'}}'
|
||||
'var old_input = document.getElementById('
|
||||
'"{hidden_id}_"+k.toString()'
|
||||
');'
|
||||
'old_input.parentNode.removeChild(old_input);'
|
||||
'}}'
|
||||
).format(
|
||||
f_name = f_name,
|
||||
hidden_id = hidden_id( f_bound )
|
||||
)
|
||||
|
||||
def tokenfield_remove( f_name, f_bound ):
|
||||
""" The JS script trigggered when a token is removed from tokenfield. """
|
||||
return (
|
||||
'function(evt) {{'
|
||||
'var k = evt.attrs.key;'
|
||||
'if (!k) {{'
|
||||
'var data = evt.attrs.value;'
|
||||
'var i = 0;'
|
||||
'while ( i<choices_{f_name}.length &&'
|
||||
'choices_{f_name}[i].value !== data ) {{'
|
||||
'i++;'
|
||||
'}}'
|
||||
'if ( i === choices_{f_name}.length ) {{ return true; }}'
|
||||
'k = choices_{f_name}[i].key;'
|
||||
'}}'
|
||||
'var old_input = document.getElementById('
|
||||
'"{hidden_id}_"+k.toString()'
|
||||
');'
|
||||
'old_input.parentNode.removeChild(old_input);'
|
||||
'}}'
|
||||
).format(
|
||||
f_name = f_name,
|
||||
hidden_id = hidden_id( f_bound )
|
||||
)
|
||||
|
10
re2o/urls.py
10
re2o/urls.py
|
@ -49,10 +49,16 @@ urlpatterns = [
|
|||
url(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^users/', include('users.urls', namespace='users')),
|
||||
url(r'^search/', include('search.urls', namespace='search')),
|
||||
url(r'^cotisations/', include('cotisations.urls', namespace='cotisations')),
|
||||
url(
|
||||
r'^cotisations/',
|
||||
include('cotisations.urls', namespace='cotisations')
|
||||
),
|
||||
url(r'^machines/', include('machines.urls', namespace='machines')),
|
||||
url(r'^topologie/', include('topologie.urls', namespace='topologie')),
|
||||
url(r'^logs/', include('logs.urls', namespace='logs')),
|
||||
url(r'^preferences/', include('preferences.urls', namespace='preferences')),
|
||||
url(
|
||||
r'^preferences/',
|
||||
include('preferences.urls', namespace='preferences')
|
||||
),
|
||||
|
||||
]
|
||||
|
|
141
re2o/utils.py
Normal file
141
re2o/utils.py
Normal file
|
@ -0,0 +1,141 @@
|
|||
# -*- mode: python; coding: utf-8 -*-
|
||||
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
||||
# se veut agnostique au réseau considéré, de manière à être installable en
|
||||
# quelques clics.
|
||||
#
|
||||
# Copyright © 2017 Gabriel Détraz
|
||||
# Copyright © 2017 Goulven Kermarec
|
||||
# Copyright © 2017 Augustin Lemesle
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
# David Sinquin, Gabriel Détraz, Goulven Kermarec
|
||||
"""
|
||||
Regroupe les fonctions transversales utiles
|
||||
|
||||
Fonction :
|
||||
- récupérer tous les utilisateurs actifs
|
||||
- récupérer toutes les machines
|
||||
- récupérer tous les bans
|
||||
etc
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.utils import timezone
|
||||
from django.db.models import Q
|
||||
|
||||
from cotisations.models import Cotisation, Facture, Paiement, Vente
|
||||
from machines.models import Domain, Interface, Machine
|
||||
from users.models import User, Ban, Whitelist
|
||||
from preferences.models import Service
|
||||
|
||||
DT_NOW = timezone.now()
|
||||
|
||||
|
||||
def all_adherent(search_time=DT_NOW):
|
||||
""" Fonction renvoyant tous les users adherents. Optimisee pour n'est
|
||||
qu'une seule requete sql
|
||||
Inspecte les factures de l'user et ses cotisation, regarde si elles
|
||||
sont posterieur à now (end_time)"""
|
||||
return User.objects.filter(
|
||||
facture__in=Facture.objects.filter(
|
||||
vente__in=Vente.objects.filter(
|
||||
cotisation__in=Cotisation.objects.filter(
|
||||
vente__in=Vente.objects.filter(
|
||||
facture__in=Facture.objects.all().exclude(valid=False)
|
||||
)
|
||||
).filter(date_end__gt=search_time)
|
||||
)
|
||||
)
|
||||
).distinct()
|
||||
|
||||
|
||||
def all_baned(search_time=DT_NOW):
|
||||
""" Fonction renvoyant tous les users bannis """
|
||||
return User.objects.filter(
|
||||
ban__in=Ban.objects.filter(
|
||||
date_end__gt=search_time
|
||||
)
|
||||
).distinct()
|
||||
|
||||
|
||||
def all_whitelisted(search_time=DT_NOW):
|
||||
""" Fonction renvoyant tous les users whitelistes """
|
||||
return User.objects.filter(
|
||||
whitelist__in=Whitelist.objects.filter(
|
||||
date_end__gt=search_time
|
||||
)
|
||||
).distinct()
|
||||
|
||||
|
||||
def all_has_access(search_time=DT_NOW):
|
||||
""" Renvoie tous les users beneficiant d'une connexion
|
||||
: user adherent ou whiteliste et non banni """
|
||||
return User.objects.filter(
|
||||
Q(state=User.STATE_ACTIVE) &
|
||||
~Q(ban__in=Ban.objects.filter(date_end__gt=search_time)) &
|
||||
(Q(whitelist__in=Whitelist.objects.filter(date_end__gt=search_time)) |
|
||||
Q(facture__in=Facture.objects.filter(
|
||||
vente__in=Vente.objects.filter(
|
||||
cotisation__in=Cotisation.objects.filter(
|
||||
vente__in=Vente.objects.filter(
|
||||
facture__in=Facture.objects.all()
|
||||
.exclude(valid=False)
|
||||
)
|
||||
).filter(date_end__gt=search_time)
|
||||
)
|
||||
)))
|
||||
).distinct()
|
||||
|
||||
|
||||
def filter_active_interfaces(interface_set):
|
||||
"""Filtre les machines autorisées à sortir sur internet dans une requête"""
|
||||
return interface_set.filter(
|
||||
machine__in=Machine.objects.filter(
|
||||
user__in=all_has_access()
|
||||
).filter(active=True)
|
||||
).select_related('domain').select_related('machine')\
|
||||
.select_related('type').select_related('ipv4')\
|
||||
.select_related('domain__extension').select_related('ipv4__ip_type')\
|
||||
.distinct()
|
||||
|
||||
|
||||
def all_active_interfaces():
|
||||
"""Renvoie l'ensemble des machines autorisées à sortir sur internet """
|
||||
return filter_active_interfaces(Interface.objects)
|
||||
|
||||
|
||||
def all_active_assigned_interfaces():
|
||||
""" Renvoie l'ensemble des machines qui ont une ipv4 assignées et
|
||||
disposant de l'accès internet"""
|
||||
return all_active_interfaces().filter(ipv4__isnull=False)
|
||||
|
||||
|
||||
def all_active_interfaces_count():
|
||||
""" Version light seulement pour compter"""
|
||||
return Interface.objects.filter(
|
||||
machine__in=Machine.objects.filter(
|
||||
user__in=all_has_access()
|
||||
).filter(active=True)
|
||||
)
|
||||
|
||||
|
||||
def all_active_assigned_interfaces_count():
|
||||
""" Version light seulement pour compter"""
|
||||
return all_active_interfaces_count().filter(ipv4__isnull=False)
|
|
@ -19,25 +19,28 @@
|
|||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
"""
|
||||
Fonctions de la page d'accueil et diverses fonctions utiles pour tous
|
||||
les views
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template.context_processors import csrf
|
||||
from django.template import Context, RequestContext, loader
|
||||
from preferences.models import Service
|
||||
|
||||
|
||||
def form(ctx, template, request):
|
||||
c = ctx
|
||||
c.update(csrf(request))
|
||||
return render(request, template, c)
|
||||
"""Form générique, raccourci importé par les fonctions views du site"""
|
||||
context = ctx
|
||||
context.update(csrf(request))
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
def index(request):
|
||||
i = 0
|
||||
"""Affiche la liste des services sur la page d'accueil de re2o"""
|
||||
services = [[], [], []]
|
||||
for indice, serv in enumerate(Service.objects.all()):
|
||||
services[indice % 3].append(serv)
|
||||
|
||||
return form({'services_urls': services}, 're2o/index.html', request)
|
||||
|
|
|
@ -32,9 +32,10 @@ https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
from os.path import dirname
|
||||
import sys
|
||||
from os.path import dirname
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
|
||||
sys.path.append(dirname(dirname(__file__)))
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "re2o.settings")
|
||||
|
|
210
static/css/bootstrap-tokenfield.css
vendored
Normal file
210
static/css/bootstrap-tokenfield.css
vendored
Normal file
|
@ -0,0 +1,210 @@
|
|||
/*!
|
||||
* bootstrap-tokenfield
|
||||
* https://github.com/sliptree/bootstrap-tokenfield
|
||||
* Copyright 2013-2014 Sliptree and other contributors; Licensed MIT
|
||||
*/
|
||||
@-webkit-keyframes blink {
|
||||
0% {
|
||||
border-color: #ededed;
|
||||
}
|
||||
100% {
|
||||
border-color: #b94a48;
|
||||
}
|
||||
}
|
||||
@-moz-keyframes blink {
|
||||
0% {
|
||||
border-color: #ededed;
|
||||
}
|
||||
100% {
|
||||
border-color: #b94a48;
|
||||
}
|
||||
}
|
||||
@keyframes blink {
|
||||
0% {
|
||||
border-color: #ededed;
|
||||
}
|
||||
100% {
|
||||
border-color: #b94a48;
|
||||
}
|
||||
}
|
||||
.tokenfield {
|
||||
height: auto;
|
||||
min-height: 34px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
.tokenfield.focus {
|
||||
border-color: #66afe9;
|
||||
outline: 0;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);
|
||||
}
|
||||
.tokenfield .token {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
display: inline-block;
|
||||
border: 1px solid #d9d9d9;
|
||||
background-color: #ededed;
|
||||
white-space: nowrap;
|
||||
margin: -1px 5px 5px 0;
|
||||
height: 22px;
|
||||
vertical-align: top;
|
||||
cursor: default;
|
||||
}
|
||||
.tokenfield .token:hover {
|
||||
border-color: #b9b9b9;
|
||||
}
|
||||
.tokenfield .token.active {
|
||||
border-color: #52a8ec;
|
||||
border-color: rgba(82, 168, 236, 0.8);
|
||||
}
|
||||
.tokenfield .token.duplicate {
|
||||
border-color: #ebccd1;
|
||||
-webkit-animation-name: blink;
|
||||
animation-name: blink;
|
||||
-webkit-animation-duration: 0.1s;
|
||||
animation-duration: 0.1s;
|
||||
-webkit-animation-direction: normal;
|
||||
animation-direction: normal;
|
||||
-webkit-animation-timing-function: ease;
|
||||
animation-timing-function: ease;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
.tokenfield .token.invalid {
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
-webkit-border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
border-radius: 0;
|
||||
border-bottom: 1px dotted #d9534f;
|
||||
}
|
||||
.tokenfield .token.invalid.active {
|
||||
background: #ededed;
|
||||
border: 1px solid #ededed;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.tokenfield .token .token-label {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-left: 4px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.tokenfield .token .close {
|
||||
font-family: Arial;
|
||||
display: inline-block;
|
||||
line-height: 100%;
|
||||
font-size: 1.1em;
|
||||
line-height: 1.49em;
|
||||
margin-left: 5px;
|
||||
float: none;
|
||||
height: 100%;
|
||||
vertical-align: top;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.tokenfield .token-input {
|
||||
background: none;
|
||||
width: 60px;
|
||||
min-width: 60px;
|
||||
border: 0;
|
||||
height: 20px;
|
||||
padding: 0;
|
||||
margin-bottom: 6px;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.tokenfield .token-input:focus {
|
||||
border-color: transparent;
|
||||
outline: 0;
|
||||
/* IE6-9 */
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.tokenfield.disabled {
|
||||
cursor: not-allowed;
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
.tokenfield.disabled .token-input {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.tokenfield.disabled .token:hover {
|
||||
cursor: not-allowed;
|
||||
border-color: #d9d9d9;
|
||||
}
|
||||
.tokenfield.disabled .token:hover .close {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.2;
|
||||
filter: alpha(opacity=20);
|
||||
}
|
||||
.has-warning .tokenfield.focus {
|
||||
border-color: #66512c;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;
|
||||
}
|
||||
.has-error .tokenfield.focus {
|
||||
border-color: #843534;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
|
||||
}
|
||||
.has-success .tokenfield.focus {
|
||||
border-color: #2b542c;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;
|
||||
}
|
||||
.tokenfield.input-sm,
|
||||
.input-group-sm .tokenfield {
|
||||
min-height: 30px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
.input-group-sm .token,
|
||||
.tokenfield.input-sm .token {
|
||||
height: 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.input-group-sm .token-input,
|
||||
.tokenfield.input-sm .token-input {
|
||||
height: 18px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.tokenfield.input-lg,
|
||||
.input-group-lg .tokenfield {
|
||||
height: auto;
|
||||
min-height: 45px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.input-group-lg .token,
|
||||
.tokenfield.input-lg .token {
|
||||
height: 25px;
|
||||
}
|
||||
.input-group-lg .token-label,
|
||||
.tokenfield.input-lg .token-label {
|
||||
line-height: 23px;
|
||||
}
|
||||
.input-group-lg .token .close,
|
||||
.tokenfield.input-lg .token .close {
|
||||
line-height: 1.3em;
|
||||
}
|
||||
.input-group-lg .token-input,
|
||||
.tokenfield.input-lg .token-input {
|
||||
height: 23px;
|
||||
line-height: 23px;
|
||||
margin-bottom: 6px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.tokenfield.rtl {
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
.tokenfield.rtl .token {
|
||||
margin: -1px 0 5px 5px;
|
||||
}
|
||||
.tokenfield.rtl .token .token-label {
|
||||
padding-left: 0px;
|
||||
padding-right: 4px;
|
||||
}
|
23
static/js/bootstrap-tokenfield/LICENSE.md
Normal file
23
static/js/bootstrap-tokenfield/LICENSE.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
#### Sliptree
|
||||
- by Illimar Tambek for [Sliptree](http://sliptree.com)
|
||||
- Copyright (c) 2013 by Sliptree
|
||||
|
||||
Available for use under the [MIT License](http://en.wikipedia.org/wiki/MIT_License)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
1042
static/js/bootstrap-tokenfield/bootstrap-tokenfield.js
vendored
Normal file
1042
static/js/bootstrap-tokenfield/bootstrap-tokenfield.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
19
static/js/handlebars/LICENSE
Normal file
19
static/js/handlebars/LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
|||
Copyright (C) 2011-2017 by Yehuda Katz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
21
static/js/konami/LICENSE.md
Normal file
21
static/js/konami/LICENSE.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 Snaptortoise
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
139
static/js/konami/konami.js
Normal file
139
static/js/konami/konami.js
Normal file
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Konami-JS ~
|
||||
* :: Now with support for touch events and multiple instances for
|
||||
* :: those situations that call for multiple easter eggs!
|
||||
* Code: https://github.com/snaptortoise/konami-js
|
||||
* Examples: http://www.snaptortoise.com/konami-js
|
||||
* Copyright (c) 2009 George Mandis (georgemandis.com, snaptortoise.com)
|
||||
* Version: 1.5.1 (9/4/2017)
|
||||
* Licensed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
* Tested in: Safari 4+, Google Chrome 4+, Firefox 3+, IE7+, Mobile Safari 2.2.1+ and Android
|
||||
*/
|
||||
|
||||
var Konami = function (callback) {
|
||||
var konami = {
|
||||
addEvent: function (obj, type, fn, ref_obj) {
|
||||
if (obj.addEventListener)
|
||||
obj.addEventListener(type, fn, false);
|
||||
else if (obj.attachEvent) {
|
||||
// IE
|
||||
obj["e" + type + fn] = fn;
|
||||
obj[type + fn] = function () {
|
||||
obj["e" + type + fn](window.event, ref_obj);
|
||||
}
|
||||
obj.attachEvent("on" + type, obj[type + fn]);
|
||||
}
|
||||
},
|
||||
removeEvent: function (obj, eventName, eventCallback) {
|
||||
if (obj.removeEventListener) {
|
||||
obj.removeEventListener(eventName, eventCallback);
|
||||
} else if (obj.attachEvent) {
|
||||
obj.detachEvent(eventName);
|
||||
}
|
||||
},
|
||||
input: "",
|
||||
pattern: "38384040373937396665",
|
||||
keydownHandler: function (e, ref_obj) {
|
||||
if (ref_obj) {
|
||||
konami = ref_obj;
|
||||
} // IE
|
||||
konami.input += e ? e.keyCode : event.keyCode;
|
||||
if (konami.input.length > konami.pattern.length) {
|
||||
konami.input = konami.input.substr((konami.input.length - konami.pattern.length));
|
||||
}
|
||||
if (konami.input === konami.pattern) {
|
||||
konami.code(this._currentlink);
|
||||
konami.input = '';
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
load: function (link) {
|
||||
this.addEvent(document, "keydown", this.keydownHandler, this);
|
||||
this.iphone.load(link);
|
||||
},
|
||||
unload: function () {
|
||||
this.removeEvent(document, 'keydown', this.keydownHandler);
|
||||
this.iphone.unload();
|
||||
},
|
||||
code: function (link) {
|
||||
window.location = link
|
||||
},
|
||||
iphone: {
|
||||
start_x: 0,
|
||||
start_y: 0,
|
||||
stop_x: 0,
|
||||
stop_y: 0,
|
||||
tap: false,
|
||||
capture: false,
|
||||
orig_keys: "",
|
||||
keys: ["UP", "UP", "DOWN", "DOWN", "LEFT", "RIGHT", "LEFT", "RIGHT", "TAP", "TAP"],
|
||||
input: [],
|
||||
code: function (link) {
|
||||
konami.code(link);
|
||||
},
|
||||
touchmoveHandler: function (e) {
|
||||
if (e.touches.length === 1 && konami.iphone.capture === true) {
|
||||
var touch = e.touches[0];
|
||||
konami.iphone.stop_x = touch.pageX;
|
||||
konami.iphone.stop_y = touch.pageY;
|
||||
konami.iphone.tap = false;
|
||||
konami.iphone.capture = false;
|
||||
konami.iphone.check_direction();
|
||||
}
|
||||
},
|
||||
toucheendHandler: function () {
|
||||
if (konami.iphone.tap === true) {
|
||||
konami.iphone.check_direction(this._currentLink);
|
||||
}
|
||||
},
|
||||
touchstartHandler: function (e) {
|
||||
konami.iphone.start_x = e.changedTouches[0].pageX;
|
||||
konami.iphone.start_y = e.changedTouches[0].pageY;
|
||||
konami.iphone.tap = true;
|
||||
konami.iphone.capture = true;
|
||||
},
|
||||
load: function (link) {
|
||||
this.orig_keys = this.keys;
|
||||
konami.addEvent(document, "touchmove", this.touchmoveHandler);
|
||||
konami.addEvent(document, "touchend", this.toucheendHandler, false);
|
||||
konami.addEvent(document, "touchstart", this.touchstartHandler);
|
||||
},
|
||||
unload: function () {
|
||||
konami.removeEvent(document, 'touchmove', this.touchmoveHandler);
|
||||
konami.removeEvent(document, 'touchend', this.toucheendHandler);
|
||||
konami.removeEvent(document, 'touchstart', this.touchstartHandler);
|
||||
},
|
||||
check_direction: function () {
|
||||
x_magnitude = Math.abs(this.start_x - this.stop_x);
|
||||
y_magnitude = Math.abs(this.start_y - this.stop_y);
|
||||
x = ((this.start_x - this.stop_x) < 0) ? "RIGHT" : "LEFT";
|
||||
y = ((this.start_y - this.stop_y) < 0) ? "DOWN" : "UP";
|
||||
result = (x_magnitude > y_magnitude) ? x : y;
|
||||
result = (this.tap === true) ? "TAP" : result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typeof callback === "string" && konami.load(callback);
|
||||
if (typeof callback === "function") {
|
||||
konami.code = callback;
|
||||
konami.load();
|
||||
}
|
||||
|
||||
return konami;
|
||||
};
|
||||
|
||||
|
||||
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
||||
module.exports = Konami;
|
||||
} else {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define([], function() {
|
||||
return Konami;
|
||||
});
|
||||
} else {
|
||||
window.Konami = Konami;
|
||||
}
|
||||
}
|
316
static/js/sapphire.js
Normal file
316
static/js/sapphire.js
Normal file
|
@ -0,0 +1,316 @@
|
|||
// 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.
|
||||
|
||||
// General options
|
||||
//=====================================
|
||||
// Times the canvas is refreshed a second
|
||||
var FPS = 30;
|
||||
// Determine the length of the trail (0=instant disappear, maximum=window.innerHeight=no disappear)
|
||||
var TRAIL_TIME = 5;
|
||||
// The color of the characters
|
||||
var RAIN_COLOR = "#00F";
|
||||
// The characters displayed
|
||||
var CHARACTERS = "田由甲申甴电甶男甸甹町画甼甽甾甿畀畁畂畃畄畅畆畇畈畉畊畋界畍畎畏畐畑".split("");
|
||||
// The font size used to display the characters
|
||||
var FONT_SIZE = 10;
|
||||
// The maximum number of characters displayed by column
|
||||
var MAX_CHAR = 7;
|
||||
|
||||
var Sapphire = function () {
|
||||
var sapphire = {
|
||||
triggerHandle: undefined,
|
||||
activated: false,
|
||||
runOnce: false,
|
||||
|
||||
getClass: function(elt, main, name) { elt.obj = main.getElementsByClassName(name); },
|
||||
getTag: function(elt, main, name) { elt.obj = main.getElementsByTagName(name); },
|
||||
|
||||
getProp: function(elt) {
|
||||
for (var i=0 ; i<elt.obj.length ; i++) {
|
||||
for (var p in elt.prop) {
|
||||
if ( p === "color" ) { elt.prop[p][i] = elt.obj[i].style.color; }
|
||||
else if ( p === "bgColor" ) { elt.prop[p][i] = elt.obj[i].style.backgroundColor; }
|
||||
else if ( p === "display" ) { elt.prop[p][i] = elt.obj[i].style.display; }
|
||||
}
|
||||
}
|
||||
},
|
||||
alterProp: function(elt) {
|
||||
for (var i=0 ; i<elt.obj.length ; i++) {
|
||||
for (var p in elt.prop) {
|
||||
if ( p === "color" ) { elt.obj[i].style.color = "white"; }
|
||||
else if ( p === "bgColor" ) { elt.obj[i].style.backgroundColor = "transparent"; }
|
||||
else if ( p === "display" ) { elt.obj[i].style.display = "none"; }
|
||||
}
|
||||
}
|
||||
},
|
||||
revertProp: function(elt) {
|
||||
for (var i=0 ; i<elt.obj.length ; i++) {
|
||||
for (var p in elt.prop) {
|
||||
if ( p === "color" ) { elt.obj[i].style.color = elt.prop[p][i]; }
|
||||
else if ( p === "bgColor" ) { elt.obj[i].style.backgroundColor = elt.prop[p][i]; }
|
||||
else if ( p === "display" ) { elt.obj[i].style.display = elt.prop[p][i]; }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
elts: {
|
||||
alerts: {
|
||||
obj: undefined,
|
||||
prop: {bgColor: []},
|
||||
get: function(main) { sapphire.getClass(this, main, "alert"); sapphire.getProp(this); },
|
||||
alter: function() { sapphire.alterProp(this); },
|
||||
revert: function() { sapphire.revertProp(this); }
|
||||
},
|
||||
btns: {
|
||||
obj: undefined,
|
||||
prop: {color: [], bgColor: []},
|
||||
get: function(main) { sapphire.getClass(this, main, "btn"); sapphire.getProp(this); },
|
||||
alter: function() { sapphire.alterProp(this); },
|
||||
revert: function() { sapphire.revertProp(this); }
|
||||
},
|
||||
body: {
|
||||
obj: undefined,
|
||||
prop: {color: []},
|
||||
get: function(main) {
|
||||
this.obj = document.body;
|
||||
for (var p in this.prop) { if ( p === "color" ) { this.prop[p] = this.obj.style.color; } }
|
||||
},
|
||||
alter: function() { for (var p in this.prop) { if ( p === "color" ) { this.obj.style.color = "white"; } } },
|
||||
revert: function() { for (var p in this.prop) { if ( p === "color" ) { this.obj.style.color = this.prop[p]; } } }
|
||||
},
|
||||
captions: {
|
||||
obj: undefined,
|
||||
prop: {color: []},
|
||||
get: function(main) { sapphire.getClass(this, main, "caption"); sapphire.getProp(this); },
|
||||
alter: function() { sapphire.alterProp(this); },
|
||||
revert: function() { sapphire.revertProp(this); }
|
||||
},
|
||||
helps: {
|
||||
obj: undefined,
|
||||
prop: {color: []},
|
||||
get: function(main) { sapphire.getClass(this, main, "help-block"); sapphire.getProp(this); },
|
||||
alter: function() { sapphire.alterProp(this); },
|
||||
revert: function() { sapphire.revertProp(this); }
|
||||
},
|
||||
hrs: {
|
||||
obj: undefined,
|
||||
prop: {display: []},
|
||||
get: function(main) { sapphire.getTag(this, main, "hr"); sapphire.getProp(this); },
|
||||
alter: function() { sapphire.alterProp(this); },
|
||||
revert: function() { sapphire.revertProp(this); }
|
||||
},
|
||||
inputs: {
|
||||
obj: undefined,
|
||||
prop: {color: [], bgColor: []},
|
||||
get: function(main) { sapphire.getTag(this, main, "input"); sapphire.getProp(this); },
|
||||
alter: function() { sapphire.alterProp(this); },
|
||||
revert: function() { sapphire.revertProp(this); }
|
||||
},
|
||||
listGroups: {
|
||||
obj: undefined,
|
||||
prop: {color: [], bgColor: []},
|
||||
get: function(main) { sapphire.getClass(this, main, "list-group-item"); sapphire.getProp(this); },
|
||||
alter: function() { sapphire.alterProp(this); },
|
||||
revert: function() { sapphire.revertProp(this); }
|
||||
},
|
||||
paginations: {
|
||||
obj: [],
|
||||
prop: {bgColor: []},
|
||||
get: function(main) {
|
||||
var a = main.getElementsByClassName("pagination");
|
||||
for (var i=0 ; i<a.length ; i++) {
|
||||
this.obj[i] = []; this.prop.bgColor[i] = [];
|
||||
for (var j=0 ; j<a[i].children.length ; j++) {
|
||||
this.obj[i][j] = a[i].children[j].children[0];
|
||||
this.prop.bgColor[i][j] = this.obj[i][j].style.backgroundColor;
|
||||
}
|
||||
}
|
||||
},
|
||||
alter: function () {
|
||||
for (var i=0 ; i<this.obj.length ; i++)
|
||||
for (var j=0 ; j<this.obj[i].length ; j++)
|
||||
for (var p in this.prop)
|
||||
if ( p === "bgColor" ) { this.obj[i][j].style.backgroundColor = "transparent"; }
|
||||
},
|
||||
revert: function() {
|
||||
for (var i=0 ; i<this.obj.length ; i++)
|
||||
for (var j=0 ; j<this.obj[i].length ; j++)
|
||||
for (var p in this.prop)
|
||||
if ( p === "bgColor" ) { this.obj[i][j].style.backgroundColor = this.prop[p][i][j]; }
|
||||
}
|
||||
},
|
||||
panelHeadings: {
|
||||
obj: undefined,
|
||||
prop: {bgColor: [], color: []},
|
||||
get: function(main) { sapphire.getClass(this, main, "panel-heading"); sapphire.getProp(this); },
|
||||
alter: function() { sapphire.alterProp(this); },
|
||||
revert: function() { sapphire.revertProp(this); }
|
||||
},
|
||||
panels: {
|
||||
obj: undefined,
|
||||
prop: {bgColor: []},
|
||||
get: function(main) { sapphire.getClass(this, main, "panel"); sapphire.getProp(this); },
|
||||
alter: function() { sapphire.alterProp(this); },
|
||||
revert: function() { sapphire.revertProp(this); }
|
||||
},
|
||||
selects: {
|
||||
obj: undefined,
|
||||
prop: {color: [], bgColor: []},
|
||||
get: function(main) { sapphire.getTag(this, main, "select"); sapphire.getProp(this); },
|
||||
alter: function() { sapphire.alterProp(this); },
|
||||
revert: function() { sapphire.revertProp(this); }
|
||||
},
|
||||
sidenavs: {
|
||||
obj: undefined,
|
||||
prop: {bgColor: []},
|
||||
get: function(main) { sapphire.getClass(this, main, "sidenav"); sapphire.getProp(this); },
|
||||
alter: function() { sapphire.alterProp(this); },
|
||||
revert: function() { sapphire.revertProp(this); }
|
||||
},
|
||||
tds: {
|
||||
obj: undefined,
|
||||
prop: {bgColor: []},
|
||||
get: function(main) { sapphire.getTag(this, main, "td"); sapphire.getProp(this); },
|
||||
alter: function() { sapphire.alterProp(this); },
|
||||
revert: function() { sapphire.revertProp(this); }
|
||||
},
|
||||
thumbnails: {
|
||||
obj: undefined,
|
||||
prop: {bgColor: []},
|
||||
get: function(main) { sapphire.getClass(this, main, "thumbnail"); sapphire.getProp(this); },
|
||||
alter: function() { sapphire.alterProp(this); },
|
||||
revert: function() { sapphire.revertProp(this); }
|
||||
},
|
||||
trs: {
|
||||
obj: undefined,
|
||||
prop: {bgColor: []},
|
||||
get: function(main) { sapphire.getTag(this, main, "tr"); sapphire.getProp(this); },
|
||||
alter: function() { sapphire.alterProp(this); },
|
||||
revert: function() { sapphire.revertProp(this); }
|
||||
}
|
||||
},
|
||||
|
||||
columns: undefined,
|
||||
alpha: undefined,
|
||||
drops: undefined,
|
||||
canvas: undefined,
|
||||
|
||||
init: function() {
|
||||
var main = document.getElementById("main");
|
||||
for (var e in sapphire.elts) { sapphire.elts[e].get(main); }
|
||||
},
|
||||
|
||||
resize: function() {
|
||||
var ctx = sapphire.canvas.getContext("2d");
|
||||
var img = ctx.getImageData( 0, 0, sapphire.canvas.width, sapphire.canvas.height );
|
||||
sapphire.canvas.width = window.innerWidth;
|
||||
sapphire.canvas.height = window.innerHeight;
|
||||
ctx.fillStyle = "rgba(0, 0, 0, 1)";
|
||||
ctx.fillRect(0, 0, sapphire.canvas.width, sapphire.canvas.height);
|
||||
ctx.putImageData( img, 0, 0 );
|
||||
sapphire.columns = sapphire.canvas.width/FONT_SIZE;
|
||||
sapphire.alpha = Math.max( 0, Math.min( 1, TRAIL_TIME / ( sapphire.canvas.height/FONT_SIZE ) ) );
|
||||
var newDrops = [];
|
||||
for(var x = 0; x < sapphire.columns; x++) {
|
||||
if ( sapphire.drops && sapphire.drops[x] ) { newDrops[x] = sapphire.drops[x] }
|
||||
else {
|
||||
newDrops[x] = [];
|
||||
var nb = Math.floor(Math.random()*MAX_CHAR);
|
||||
for (var y = 0; y < nb; y++)
|
||||
newDrops[x][y] = 0;
|
||||
}
|
||||
}
|
||||
sapphire.drops = newDrops;
|
||||
},
|
||||
|
||||
run: function() {
|
||||
sapphire.canvas = document.createElement("canvas");
|
||||
document.body.appendChild(sapphire.canvas);
|
||||
sapphire.canvas.style.position = "fixed";
|
||||
sapphire.canvas.style.zIndex = -1;
|
||||
sapphire.canvas.style.left = 0;
|
||||
sapphire.canvas.style.top = 0;
|
||||
|
||||
var ctx = sapphire.canvas.getContext("2d");
|
||||
ctx.fillStyle = "rgba(0, 0, 0, 1)";
|
||||
ctx.fillRect(0, 0, sapphire.canvas.width, sapphire.canvas.height);
|
||||
|
||||
function attenuateBackground() {
|
||||
ctx.fillStyle = "rgba(0, 0, 0, "+sapphire.alpha+")";
|
||||
ctx.fillRect(0, 0, sapphire.canvas.width, sapphire.canvas.height);
|
||||
}
|
||||
|
||||
function drawMatrixRainDrop() {
|
||||
ctx.fillStyle = RAIN_COLOR;
|
||||
ctx.font = FONT_SIZE + "px arial";
|
||||
for(var i = 0; i < sapphire.drops.length; i++) {
|
||||
for (var j = 0; j < sapphire.drops[i].length; j++) {
|
||||
var text = CHARACTERS[Math.floor(Math.random()*CHARACTERS.length)];
|
||||
ctx.fillText(text, i*FONT_SIZE, sapphire.drops[i][j]*FONT_SIZE);
|
||||
if(sapphire.drops[i][j]*FONT_SIZE > sapphire.canvas.height && Math.random() > 0.975)
|
||||
sapphire.drops[i][j] = 0;
|
||||
sapphire.drops[i][j]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawEverything() {
|
||||
attenuateBackground();
|
||||
drawMatrixRainDrop();
|
||||
}
|
||||
|
||||
sapphire.resize();
|
||||
window.addEventListener('resize', sapphire.resize);
|
||||
sapphire.triggerHandle = setInterval(drawEverything, 1000/FPS);
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
window.removeEventListener('resize', sapphire.resize);
|
||||
clearInterval(sapphire.triggerHandle);
|
||||
sapphire.canvas.parentNode.removeChild(sapphire.canvas);
|
||||
},
|
||||
|
||||
alterElts: function() { for (var e in sapphire.elts) { sapphire.elts[e].alter(main); } },
|
||||
revertElts: function() { for (var e in sapphire.elts) { sapphire.elts[e].revert(main); } },
|
||||
|
||||
activate: function() {
|
||||
if (!sapphire.runOnce) {
|
||||
sapphire.runOnce = true;
|
||||
sapphire.init();
|
||||
}
|
||||
if (!sapphire.activated) {
|
||||
sapphire.activated = true;
|
||||
sapphire.alterElts();
|
||||
sapphire.run()
|
||||
}
|
||||
else {
|
||||
sapphire.activated = false;
|
||||
sapphire.stop();
|
||||
sapphire.revertElts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sapphire;
|
||||
}
|
||||
|
||||
var s = Sapphire();
|
||||
Konami(s.activate);
|
||||
|
19
static/js/typeahead/LICENSE
Normal file
19
static/js/typeahead/LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2013-2014 Twitter, Inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
Binary file not shown.
Before Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
Before Width: | Height: | Size: 17 KiB |
Binary file not shown.
Before Width: | Height: | Size: 16 KiB |
Binary file not shown.
Before Width: | Height: | Size: 16 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.3 KiB |
|
@ -33,10 +33,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{# Load CSS and JavaScript #}
|
||||
{% bootstrap_css %}
|
||||
<link href="/static/css/typeaheadjs.css" rel="stylesheet">
|
||||
<link href="/static/css/bootstrap-tokenfield.css" rel="stylesheet">
|
||||
{% comment %}<link href="/static/css/jquery-ui.css" rel="stylesheet">{% endcomment %}
|
||||
|
||||
{% bootstrap_javascript %}
|
||||
<script src="/static/js/typeahead.js"></script>
|
||||
<script src="/static/js/handlebars.js"></script>
|
||||
<script src="/static/js/typeahead/typeahead.js"></script>
|
||||
<script src="/static/js/handlebars/handlebars.js"></script>
|
||||
<script src="/static/js/konami/konami.js"></script>
|
||||
<script src="/static/js/sapphire.js"> var s=Sapphire(); Konami(s.activate); </script>
|
||||
<script src="/static/js/bootstrap-tokenfield/bootstrap-tokenfield.js"></script>
|
||||
{% comment %}<script src="/static/js/jquery-ui.js"></script>{% endcomment %}
|
||||
<link rel="stylesheet" href="{% static "/css/base.css" %}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ site_name }} : {% block title %}Accueil{% endblock %}</title>
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
"""
|
||||
Fichier définissant les administration des models dans l'interface admin
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
@ -28,18 +31,27 @@ from reversion.admin import VersionAdmin
|
|||
|
||||
from .models import Port, Room, Switch, Stack
|
||||
|
||||
|
||||
class StackAdmin(VersionAdmin):
|
||||
"""Administration d'une stack de switches (inclus des switches)"""
|
||||
pass
|
||||
|
||||
|
||||
class SwitchAdmin(VersionAdmin):
|
||||
"""Administration d'un switch"""
|
||||
pass
|
||||
|
||||
|
||||
class PortAdmin(VersionAdmin):
|
||||
"""Administration d'un port de switches"""
|
||||
pass
|
||||
|
||||
|
||||
class RoomAdmin(VersionAdmin):
|
||||
"""Administration d'un chambre"""
|
||||
pass
|
||||
|
||||
|
||||
admin.site.register(Port, PortAdmin)
|
||||
admin.site.register(Room, RoomAdmin)
|
||||
admin.site.register(Switch, SwitchAdmin)
|
||||
|
|
|
@ -19,52 +19,113 @@
|
|||
# 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.
|
||||
"""
|
||||
Un forms le plus simple possible pour les objets topologie de re2o.
|
||||
|
||||
Permet de créer et supprimer : un Port de switch, relié à un switch.
|
||||
|
||||
Permet de créer des stacks et d'y ajouter des switchs (StackForm)
|
||||
|
||||
Permet de créer, supprimer et editer un switch (EditSwitchForm,
|
||||
NewSwitchForm)
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .models import Port, Switch, Room, Stack
|
||||
from django.forms import ModelForm, Form
|
||||
from machines.models import Interface
|
||||
from django.forms import ModelForm
|
||||
from .models import Port, Switch, Room, Stack
|
||||
|
||||
|
||||
class PortForm(ModelForm):
|
||||
"""Formulaire pour la création d'un port d'un switch
|
||||
Relié directement au modèle port"""
|
||||
class Meta:
|
||||
model = Port
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(PortForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
|
||||
class EditPortForm(ModelForm):
|
||||
"""Form pour l'édition d'un port de switche : changement des reglages
|
||||
radius ou vlan, ou attribution d'une chambre, autre port ou machine
|
||||
|
||||
Un port est relié à une chambre, un autre port (uplink) ou une machine
|
||||
(serveur ou borne), mutuellement exclusif
|
||||
Optimisation sur les queryset pour machines et port_related pour
|
||||
optimiser le temps de chargement avec select_related (vraiment
|
||||
lent sans)"""
|
||||
class Meta(PortForm.Meta):
|
||||
fields = ['room', 'related', 'machine_interface', 'radius', 'vlan_force', 'details']
|
||||
fields = ['room', 'related', 'machine_interface', 'radius',
|
||||
'vlan_force', 'details']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditPortForm, self).__init__(*args, **kwargs)
|
||||
self.fields['machine_interface'].queryset = Interface.objects.all().select_related('domain__extension')
|
||||
self.fields['related'].queryset = Port.objects.all().select_related('switch__switch_interface__domain__extension').order_by('switch', 'port')
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(EditPortForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['machine_interface'].queryset = Interface.objects.all()\
|
||||
.select_related('domain__extension')
|
||||
self.fields['related'].queryset = Port.objects.all()\
|
||||
.select_related('switch__switch_interface__domain__extension')\
|
||||
.order_by('switch', 'port')
|
||||
|
||||
|
||||
class AddPortForm(ModelForm):
|
||||
"""Permet d'ajouter un port de switch. Voir EditPortForm pour plus
|
||||
d'informations"""
|
||||
class Meta(PortForm.Meta):
|
||||
fields = ['port', 'room', 'machine_interface', 'related', 'radius', 'vlan_force', 'details']
|
||||
fields = ['port', 'room', 'machine_interface', 'related',
|
||||
'radius', 'vlan_force', 'details']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(AddPortForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
|
||||
class StackForm(ModelForm):
|
||||
"""Permet d'edition d'une stack : stack_id, et switches membres
|
||||
de la stack"""
|
||||
class Meta:
|
||||
model = Stack
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(StackForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
|
||||
class EditSwitchForm(ModelForm):
|
||||
"""Permet d'éditer un switch : nom et nombre de ports"""
|
||||
class Meta:
|
||||
model = Switch
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditSwitchForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(EditSwitchForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['location'].label = 'Localisation'
|
||||
self.fields['number'].label = 'Nombre de ports'
|
||||
|
||||
|
||||
class NewSwitchForm(ModelForm):
|
||||
"""Permet de créer un switch : emplacement, paramètres machine,
|
||||
membre d'un stack (option), nombre de ports (number)"""
|
||||
class Meta(EditSwitchForm.Meta):
|
||||
fields = ['location', 'number', 'details', 'stack', 'stack_member_id']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(NewSwitchForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
|
||||
class EditRoomForm(ModelForm):
|
||||
"""Permet d'éediter le nom et commentaire d'une prise murale"""
|
||||
class Meta:
|
||||
model = Room
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(EditRoomForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
|
|
@ -20,19 +20,27 @@
|
|||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
"""
|
||||
Definition des modèles de l'application topologie.
|
||||
|
||||
On défini les models suivants :
|
||||
|
||||
- stack (id, id_min, id_max et nom) regrouppant les switches
|
||||
- switch : nom, nombre de port, et interface
|
||||
machine correspondante (mac, ip, etc) (voir machines.models.interface)
|
||||
- Port: relié à un switch parent par foreign_key, numero du port,
|
||||
relié de façon exclusive à un autre port, une machine
|
||||
(serveur ou borne) ou une prise murale
|
||||
- room : liste des prises murales, nom et commentaire de l'état de
|
||||
la prise
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_delete
|
||||
from django.dispatch import receiver
|
||||
from django.forms import ModelForm, Form
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.core.exceptions import ValidationError
|
||||
import reversion
|
||||
|
||||
from machines.models import Vlan
|
||||
|
||||
|
||||
class Stack(models.Model):
|
||||
|
@ -59,7 +67,9 @@ class Stack(models.Model):
|
|||
def clean(self):
|
||||
""" Verification que l'id_max < id_min"""
|
||||
if self.member_id_max < self.member_id_min:
|
||||
raise ValidationError({'member_id_max':"L'id maximale est inférieure à l'id minimale"})
|
||||
raise ValidationError({'member_id_max': "L'id maximale est\
|
||||
inférieure à l'id minimale"})
|
||||
|
||||
|
||||
class Switch(models.Model):
|
||||
""" Definition d'un switch. Contient un nombre de ports (number),
|
||||
|
@ -69,14 +79,25 @@ class Switch(models.Model):
|
|||
Pourquoi ne pas avoir fait hériter switch de interface ?
|
||||
Principalement par méconnaissance de la puissance de cette façon de faire.
|
||||
Ceci étant entendu, django crée en interne un onetoone, ce qui a un
|
||||
effet identique avec ce que l'on fait ici"""
|
||||
effet identique avec ce que l'on fait ici
|
||||
|
||||
Validation au save que l'id du stack est bien dans le range id_min
|
||||
id_max de la stack parente"""
|
||||
PRETTY_NAME = "Switch / Commutateur"
|
||||
|
||||
switch_interface = models.OneToOneField('machines.Interface', on_delete=models.CASCADE)
|
||||
switch_interface = models.OneToOneField(
|
||||
'machines.Interface',
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
location = models.CharField(max_length=255)
|
||||
number = models.IntegerField()
|
||||
details = models.CharField(max_length=255, blank=True)
|
||||
stack = models.ForeignKey(Stack, blank=True, null=True, on_delete=models.SET_NULL)
|
||||
stack = models.ForeignKey(
|
||||
Stack,
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.SET_NULL
|
||||
)
|
||||
stack_member_id = models.IntegerField(blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
|
@ -89,10 +110,16 @@ class Switch(models.Model):
|
|||
""" Verifie que l'id stack est dans le bon range"""
|
||||
if self.stack is not None:
|
||||
if self.stack_member_id is not None:
|
||||
if (self.stack_member_id > self.stack.member_id_max) or (self.stack_member_id < self.stack.member_id_min):
|
||||
raise ValidationError({'stack_member_id': "L'id de ce switch est en dehors des bornes permises pas la stack"})
|
||||
if (self.stack_member_id > self.stack.member_id_max) or\
|
||||
(self.stack_member_id < self.stack.member_id_min):
|
||||
raise ValidationError(
|
||||
{'stack_member_id': "L'id de ce switch est en\
|
||||
dehors des bornes permises pas la stack"}
|
||||
)
|
||||
else:
|
||||
raise ValidationError({'stack_member_id': "L'id dans la stack ne peut être nul"})
|
||||
raise ValidationError({'stack_member_id': "L'id dans la stack\
|
||||
ne peut être nul"})
|
||||
|
||||
|
||||
class Port(models.Model):
|
||||
""" Definition d'un port. Relié à un switch(foreign_key),
|
||||
|
@ -102,7 +129,8 @@ class Port(models.Model):
|
|||
- un autre port (uplink) (related)
|
||||
Champs supplémentaires :
|
||||
- RADIUS (mode STRICT : connexion sur port uniquement si machine
|
||||
d'un adhérent à jour de cotisation et que la chambre est également à jour de cotisation
|
||||
d'un adhérent à jour de cotisation et que la chambre est également à
|
||||
jour de cotisation
|
||||
mode COMMON : vérification uniquement du statut de la machine
|
||||
mode NO : accepte toute demande venant du port et place sur le vlan normal
|
||||
mode BLOQ : rejet de toute authentification
|
||||
|
@ -119,11 +147,31 @@ class Port(models.Model):
|
|||
|
||||
switch = models.ForeignKey('Switch', related_name="ports")
|
||||
port = models.IntegerField()
|
||||
room = models.ForeignKey('Room', on_delete=models.PROTECT, blank=True, null=True)
|
||||
machine_interface = models.ForeignKey('machines.Interface', on_delete=models.SET_NULL, blank=True, null=True)
|
||||
related = models.OneToOneField('self', null=True, blank=True, related_name='related_port')
|
||||
room = models.ForeignKey(
|
||||
'Room',
|
||||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
machine_interface = models.ForeignKey(
|
||||
'machines.Interface',
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
related = models.OneToOneField(
|
||||
'self',
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='related_port'
|
||||
)
|
||||
radius = models.CharField(max_length=32, choices=STATES, default='NO')
|
||||
vlan_force = models.ForeignKey('machines.Vlan', on_delete=models.SET_NULL, blank=True, null=True)
|
||||
vlan_force = models.ForeignKey(
|
||||
'machines.Vlan',
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
details = models.CharField(max_length=255, blank=True)
|
||||
|
||||
class Meta:
|
||||
|
@ -142,23 +190,28 @@ class Port(models.Model):
|
|||
related_port.save()
|
||||
|
||||
def clean(self):
|
||||
""" Verifie que un seul de chambre, interface_parent et related_port est rempli.
|
||||
Verifie que le related n'est pas le port lui-même....
|
||||
Verifie que le related n'est pas déjà occupé par une machine ou une chambre. Si
|
||||
ce n'est pas le cas, applique la relation related
|
||||
""" Verifie que un seul de chambre, interface_parent et related_port
|
||||
est rempli. Verifie que le related n'est pas le port lui-même....
|
||||
Verifie que le related n'est pas déjà occupé par une machine ou une
|
||||
chambre. Si ce n'est pas le cas, applique la relation related
|
||||
Si un port related point vers self, on nettoie la relation
|
||||
A priori pas d'autre solution que de faire ça à la main. A priori tout cela est dans
|
||||
un bloc transaction, donc pas de problème de cohérence"""
|
||||
A priori pas d'autre solution que de faire ça à la main. A priori
|
||||
tout cela est dans un bloc transaction, donc pas de problème de
|
||||
cohérence"""
|
||||
if hasattr(self, 'switch'):
|
||||
if self.port > self.switch.number:
|
||||
raise ValidationError("Ce port ne peut exister, numero trop élevé")
|
||||
if self.room and self.machine_interface or self.room and self.related or self.machine_interface and self.related:
|
||||
raise ValidationError("Chambre, interface et related_port sont mutuellement exclusifs")
|
||||
raise ValidationError("Ce port ne peut exister,\
|
||||
numero trop élevé")
|
||||
if self.room and self.machine_interface or self.room and\
|
||||
self.related or self.machine_interface and self.related:
|
||||
raise ValidationError("Chambre, interface et related_port sont\
|
||||
mutuellement exclusifs")
|
||||
if self.related == self:
|
||||
raise ValidationError("On ne peut relier un port à lui même")
|
||||
if self.related and not self.related.related:
|
||||
if self.related.machine_interface or self.related.room:
|
||||
raise ValidationError("Le port relié est déjà occupé, veuillez le libérer avant de créer une relation")
|
||||
raise ValidationError("Le port relié est déjà occupé, veuillez\
|
||||
le libérer avant de créer une relation")
|
||||
else:
|
||||
self.make_port_related()
|
||||
elif hasattr(self, 'related_port'):
|
||||
|
@ -167,6 +220,7 @@ class Port(models.Model):
|
|||
def __str__(self):
|
||||
return str(self.switch) + " - " + str(self.port)
|
||||
|
||||
|
||||
class Room(models.Model):
|
||||
"""Une chambre/local contenant une prise murale"""
|
||||
PRETTY_NAME = "Chambre/ Prise murale"
|
||||
|
@ -180,6 +234,8 @@ class Room(models.Model):
|
|||
def __str__(self):
|
||||
return str(self.name)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=Stack)
|
||||
def stack_post_delete(sender, **kwargs):
|
||||
"""Vide les id des switches membres d'une stack supprimée"""
|
||||
Switch.objects.filter(stack=None).update(stack_member_id=None)
|
||||
|
|
|
@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</thead>
|
||||
{% for stack in stack_list %}
|
||||
{% for switch in stack.switch_set.all %}
|
||||
<tbody>
|
||||
<tr class="active">
|
||||
{% if forloop.first %}
|
||||
<td rowspan="{{ stack.switch_set.all|length }}">{{stack.name}}</td>
|
||||
|
@ -56,6 +57,26 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr class="active">
|
||||
<td>{{stack.name}}</td>
|
||||
<td>{{stack.stack_id}}</td>
|
||||
<td>{{stack.details}}</td>
|
||||
<td>Aucun</td>
|
||||
<td>
|
||||
<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 %}
|
||||
<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>
|
||||
<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 %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
|
|
@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load massive_bootstrap_form %}
|
||||
|
||||
{% block title %}Création et modification d'un switch{% endblock %}
|
||||
|
||||
|
@ -46,13 +47,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
{% if topoform %}
|
||||
{% bootstrap_form topoform %}
|
||||
{% massive_bootstrap_form topoform 'switch_interface' %}
|
||||
{% endif %}
|
||||
{% if machineform %}
|
||||
{% bootstrap_form machineform %}
|
||||
{% massive_bootstrap_form machineform 'user' %}
|
||||
{% endif %}
|
||||
{% if interfaceform %}
|
||||
{% bootstrap_form interfaceform %}
|
||||
{% if i_bft_param %}
|
||||
{% massive_bootstrap_form interfaceform 'ipv4,machine' mbf_param=i_mbf_param %}
|
||||
{% else %}
|
||||
{% massive_bootstrap_form interfaceform 'ipv4,machine' %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if domainform %}
|
||||
{% bootstrap_form domainform %}
|
||||
|
|
|
@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load massive_bootstrap_form %}
|
||||
|
||||
{% block title %}Création et modificationd 'utilisateur{% endblock %}
|
||||
|
||||
|
@ -32,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form topoform %}
|
||||
{% massive_bootstrap_form topoform 'room,related,machine_interface' %}
|
||||
{%bootstrap_button "Créer ou modifier" button_type="submit" icon="ok" %}
|
||||
</form>
|
||||
<br />
|
||||
|
|
|
@ -19,6 +19,12 @@
|
|||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
"""
|
||||
Definition des urls de l'application topologie.
|
||||
Inclu dans urls de re2o.
|
||||
|
||||
Fait référence aux fonctions du views
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
@ -33,18 +39,33 @@ urlpatterns = [
|
|||
url(r'^new_room/$', views.new_room, name='new-room'),
|
||||
url(r'^edit_room/(?P<room_id>[0-9]+)$', views.edit_room, name='edit-room'),
|
||||
url(r'^del_room/(?P<room_id>[0-9]+)$', views.del_room, name='del-room'),
|
||||
url(r'^switch/(?P<switch_id>[0-9]+)$', views.index_port, name='index-port'),
|
||||
url(r'^history/(?P<object>switch)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||
url(r'^history/(?P<object>port)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||
url(r'^history/(?P<object>room)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||
url(r'^history/(?P<object>stack)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||
url(r'^switch/(?P<switch_id>[0-9]+)$',
|
||||
views.index_port,
|
||||
name='index-port'),
|
||||
url(r'^history/(?P<object>switch)/(?P<id>[0-9]+)$',
|
||||
views.history,
|
||||
name='history'),
|
||||
url(r'^history/(?P<object>port)/(?P<id>[0-9]+)$',
|
||||
views.history,
|
||||
name='history'),
|
||||
url(r'^history/(?P<object>room)/(?P<id>[0-9]+)$',
|
||||
views.history,
|
||||
name='history'),
|
||||
url(r'^history/(?P<object>stack)/(?P<id>[0-9]+)$',
|
||||
views.history,
|
||||
name='history'),
|
||||
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'),
|
||||
url(r'^edit_switch/(?P<switch_id>[0-9]+)$', views.edit_switch, name='edit-switch'),
|
||||
url(r'^edit_switch/(?P<switch_id>[0-9]+)$',
|
||||
views.edit_switch,
|
||||
name='edit-switch'),
|
||||
url(r'^new_stack/$', views.new_stack, name='new-stack'),
|
||||
url(r'^index_stack/$', views.index_stack, name='index-stack'),
|
||||
url(r'^edit_stack/(?P<stack_id>[0-9]+)$', views.edit_stack, name='edit-stack'),
|
||||
url(r'^del_stack/(?P<stack_id>[0-9]+)$', views.del_stack, name='del-stack'),
|
||||
url(r'^edit_stack/(?P<stack_id>[0-9]+)$',
|
||||
views.edit_stack,
|
||||
name='edit-stack'),
|
||||
url(r'^del_stack/(?P<stack_id>[0-9]+)$',
|
||||
views.del_stack,
|
||||
name='del-stack'),
|
||||
]
|
||||
|
||||
|
|
|
@ -19,7 +19,20 @@
|
|||
# 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.
|
||||
"""
|
||||
Page des vues de l'application topologie
|
||||
|
||||
Permet de créer, modifier et supprimer :
|
||||
- un port (add_port, edit_port, del_port)
|
||||
- un switch : les vues d'ajout et d'édition font appel aux forms de creation
|
||||
de switch, mais aussi aux forms de machines.forms (domain, interface et
|
||||
machine). Le views les envoie et les save en même temps. TODO : rationaliser
|
||||
et faire que la creation de machines (interfaces, domain etc) soit gérée
|
||||
coté models et forms de topologie
|
||||
- une chambre (new_room, edit_room, del_room)
|
||||
- une stack
|
||||
- l'historique de tous les objets cités
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.shortcuts import render, redirect
|
||||
|
@ -33,11 +46,12 @@ from reversion import revisions as reversion
|
|||
from reversion.models import Version
|
||||
|
||||
from topologie.models import Switch, Port, Room, Stack
|
||||
from topologie.forms import EditPortForm, NewSwitchForm, EditSwitchForm, AddPortForm, EditRoomForm, StackForm
|
||||
from topologie.forms import EditPortForm, NewSwitchForm, EditSwitchForm
|
||||
from topologie.forms import AddPortForm, EditRoomForm, StackForm
|
||||
from users.views import form
|
||||
from users.models import User
|
||||
|
||||
from machines.forms import AliasForm, NewMachineForm, EditMachineForm, EditInterfaceForm, AddInterfaceForm
|
||||
from machines.views import generate_ipv4_mbf_param
|
||||
from preferences.models import AssoOption, GeneralOption
|
||||
|
||||
|
||||
|
@ -45,41 +59,52 @@ from preferences.models import AssoOption, GeneralOption
|
|||
@permission_required('cableur')
|
||||
def index(request):
|
||||
""" Vue d'affichage de tous les swicthes"""
|
||||
switch_list = Switch.objects.order_by('stack','stack_member_id','location').select_related('switch_interface__domain__extension').select_related('switch_interface__ipv4').select_related('switch_interface__domain').select_related('stack')
|
||||
return render(request, 'topologie/index.html', {'switch_list': switch_list})
|
||||
switch_list = Switch.objects.order_by(
|
||||
'stack',
|
||||
'stack_member_id',
|
||||
'location'
|
||||
)\
|
||||
.select_related('switch_interface__domain__extension')\
|
||||
.select_related('switch_interface__ipv4')\
|
||||
.select_related('switch_interface__domain')\
|
||||
.select_related('stack')
|
||||
return render(request, 'topologie/index.html', {
|
||||
'switch_list': switch_list
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def history(request, object, id):
|
||||
def history(request, object_name, object_id):
|
||||
""" Vue générique pour afficher l'historique complet d'un objet"""
|
||||
if object == 'switch':
|
||||
if object_name == 'switch':
|
||||
try:
|
||||
object_instance = Switch.objects.get(pk=id)
|
||||
object_instance = Switch.objects.get(pk=object_id)
|
||||
except Switch.DoesNotExist:
|
||||
messages.error(request, "Switch inexistant")
|
||||
return redirect("/topologie/")
|
||||
elif object == 'port':
|
||||
elif object_name == 'port':
|
||||
try:
|
||||
object_instance = Port.objects.get(pk=id)
|
||||
object_instance = Port.objects.get(pk=object_id)
|
||||
except Port.DoesNotExist:
|
||||
messages.error(request, "Port inexistant")
|
||||
return redirect("/topologie/")
|
||||
elif object == 'room':
|
||||
elif object_name == 'room':
|
||||
try:
|
||||
object_instance = Room.objects.get(pk=id)
|
||||
object_instance = Room.objects.get(pk=object_id)
|
||||
except Room.DoesNotExist:
|
||||
messages.error(request, "Chambre inexistante")
|
||||
return redirect("/topologie/")
|
||||
elif object == 'stack':
|
||||
elif object_name == 'stack':
|
||||
try:
|
||||
object_instance = Stack.objects.get(pk=id)
|
||||
object_instance = Stack.objects.get(pk=object_id)
|
||||
except Room.DoesNotExist:
|
||||
messages.error(request, "Stack inexistante")
|
||||
return redirect("/topologie/")
|
||||
else:
|
||||
messages.error(request, "Objet inconnu")
|
||||
return redirect("/topologie/")
|
||||
options, created = GeneralOption.objects.get_or_create()
|
||||
options, _created = GeneralOption.objects.get_or_create()
|
||||
pagination_number = options.pagination_number
|
||||
reversions = Version.objects.get_for_object(object_instance)
|
||||
paginator = Paginator(reversions, pagination_number)
|
||||
|
@ -92,7 +117,11 @@ def history(request, object, id):
|
|||
except EmptyPage:
|
||||
# If page is out of range (e.g. 9999), deliver last page of results.
|
||||
reversions = paginator.page(paginator.num_pages)
|
||||
return render(request, 're2o/history.html', {'reversions': reversions, 'object': object_instance})
|
||||
return render(request, 're2o/history.html', {
|
||||
'reversions': reversions,
|
||||
'object': object_instance
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
|
@ -103,15 +132,25 @@ def index_port(request, switch_id):
|
|||
except Switch.DoesNotExist:
|
||||
messages.error(request, u"Switch inexistant")
|
||||
return redirect("/topologie/")
|
||||
port_list = Port.objects.filter(switch = switch).select_related('room').select_related('machine_interface__domain__extension').select_related('related').select_related('switch').order_by('port')
|
||||
return render(request, 'topologie/index_p.html', {'port_list':port_list, 'id_switch':switch_id, 'nom_switch':switch})
|
||||
port_list = Port.objects.filter(switch=switch)\
|
||||
.select_related('room')\
|
||||
.select_related('machine_interface__domain__extension')\
|
||||
.select_related('related')\
|
||||
.select_related('switch')\
|
||||
.order_by('port')
|
||||
return render(request, 'topologie/index_p.html', {
|
||||
'port_list': port_list,
|
||||
'id_switch': switch_id,
|
||||
'nom_switch': switch
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def index_room(request):
|
||||
""" Affichage de l'ensemble des chambres"""
|
||||
room_list = Room.objects.order_by('name')
|
||||
options, created = GeneralOption.objects.get_or_create()
|
||||
options, _created = GeneralOption.objects.get_or_create()
|
||||
pagination_number = options.pagination_number
|
||||
paginator = Paginator(room_list, pagination_number)
|
||||
page = request.GET.get('page')
|
||||
|
@ -123,13 +162,20 @@ def index_room(request):
|
|||
except EmptyPage:
|
||||
# If page is out of range (e.g. 9999), deliver last page of results.
|
||||
room_list = paginator.page(paginator.num_pages)
|
||||
return render(request, 'topologie/index_room.html', {'room_list': room_list})
|
||||
return render(request, 'topologie/index_room.html', {
|
||||
'room_list': room_list
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
def index_stack(request):
|
||||
stack_list = Stack.objects.order_by('name').prefetch_related('switch_set__switch_interface__domain__extension')
|
||||
return render(request, 'topologie/index_stack.html', {'stack_list': stack_list})
|
||||
"""Affichage de la liste des stacks (affiche l'ensemble des switches)"""
|
||||
stack_list = Stack.objects.order_by('name')\
|
||||
.prefetch_related('switch_set__switch_interface__domain__extension')
|
||||
return render(request, 'topologie/index_stack.html', {
|
||||
'stack_list': stack_list
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -156,12 +202,20 @@ def new_port(request, switch_id):
|
|||
return redirect("/topologie/switch/" + switch_id)
|
||||
return form({'topoform': port}, 'topologie/topo.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
def edit_port(request, port_id):
|
||||
""" Edition d'un port. Permet de changer le switch parent et l'affectation du port"""
|
||||
""" 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)
|
||||
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("/topologie/")
|
||||
|
@ -170,11 +224,14 @@ def edit_port(request, port_id):
|
|||
with transaction.atomic(), reversion.create_revision():
|
||||
port.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in port.changed_data))
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in port.changed_data
|
||||
))
|
||||
messages.success(request, "Le port a bien été modifié")
|
||||
return redirect("/topologie/switch/" + str(port_object.switch.id))
|
||||
return form({'topoform': port}, 'topologie/topo.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
def del_port(request, port_id):
|
||||
|
@ -192,30 +249,30 @@ def del_port(request,port_id):
|
|||
reversion.set_comment("Destruction")
|
||||
messages.success(request, "Le port a eté détruit")
|
||||
except ProtectedError:
|
||||
messages.error(request, "Le port %s est affecté à un autre objet, impossible de le supprimer" % port)
|
||||
messages.error(request, "Le port %s est affecté à un autre objet,\
|
||||
impossible de le supprimer" % port)
|
||||
return redirect('/topologie/switch/' + str(port.switch.id))
|
||||
return form({'objet': port}, 'topologie/delete.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
def new_stack(request):
|
||||
"""Ajoute un nouveau stack : stack_id_min, max, et nombre de switches"""
|
||||
stack = StackForm(request.POST or None)
|
||||
#if stack.is_valid():
|
||||
if request.POST:
|
||||
try:
|
||||
if stack.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
stack.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Création")
|
||||
messages.success(request, "Stack crée")
|
||||
except:
|
||||
messages.error(request, "Cette stack existe déjà")
|
||||
return form({'topoform': stack}, 'topologie/topo.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
def edit_stack(request, stack_id):
|
||||
"""Edition d'un stack (nombre de switches, nom...)"""
|
||||
try:
|
||||
stack = Stack.objects.get(pk=stack_id)
|
||||
except Stack.DoesNotExist:
|
||||
|
@ -226,13 +283,19 @@ def edit_stack(request,stack_id):
|
|||
with transaction.atomic(), reversion.create_revision():
|
||||
stack.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in stack.changed_data))
|
||||
reversion.set_comment(
|
||||
"Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in stack.changed_data
|
||||
)
|
||||
)
|
||||
return redirect('/topologie/index_stack')
|
||||
return form({'topoform': stack}, 'topologie/topo.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
def del_stack(request, stack_id):
|
||||
"""Supprime un stack"""
|
||||
try:
|
||||
stack = Stack.objects.get(pk=stack_id)
|
||||
except Stack.DoesNotExist:
|
||||
|
@ -246,13 +309,16 @@ def del_stack(request,stack_id):
|
|||
reversion.set_comment("Destruction")
|
||||
messages.success(request, "La stack a eté détruite")
|
||||
except ProtectedError:
|
||||
messages.error(request, "La stack %s est affectée à un autre objet, impossible de la supprimer" % stack)
|
||||
messages.error(request, "La stack %s est affectée à un autre\
|
||||
objet, impossible de la supprimer" % stack)
|
||||
return redirect('/topologie/index_stack')
|
||||
return form({'objet': stack}, 'topologie/delete.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
def edit_switchs_stack(request, 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:
|
||||
|
@ -264,30 +330,36 @@ def edit_switchs_stack(request,stack_id):
|
|||
context = {'stack': stack}
|
||||
context['switchs_stack'] = stack.switchs_set.all()
|
||||
context['switchs_autres'] = Switch.object.filter(stack=None)
|
||||
pass
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
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"""
|
||||
""" 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)
|
||||
interface = AddInterfaceForm(request.POST or None, infra=request.user.has_perms(('infra',)))
|
||||
domain = AliasForm(request.POST or None, infra=request.user.has_perms(('infra',)))
|
||||
interface = AddInterfaceForm(
|
||||
request.POST or None,
|
||||
infra=request.user.has_perms(('infra',))
|
||||
)
|
||||
domain = AliasForm(
|
||||
request.POST or None,
|
||||
infra=request.user.has_perms(('infra',))
|
||||
)
|
||||
if switch.is_valid() and machine.is_valid() and interface.is_valid():
|
||||
options, created = AssoOption.objects.get_or_create()
|
||||
options, _created = AssoOption.objects.get_or_create()
|
||||
user = options.utilisateur_asso
|
||||
if not user:
|
||||
messages.error(request, "L'user association n'existe pas encore, veuillez le créer ou le linker dans preferences")
|
||||
messages.error(request, "L'user association n'existe pas encore,\
|
||||
veuillez le créer ou le linker dans preferences")
|
||||
return redirect("/topologie/")
|
||||
new_machine = machine.save(commit=False)
|
||||
new_machine.user = user
|
||||
new_interface = interface.save(commit=False)
|
||||
new_switch = switch.save(commit=False)
|
||||
new_domain = domain.save(commit=False)
|
||||
new_switch_instance = switch.save(commit=False)
|
||||
new_domain_instance = domain.save(commit=False)
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
new_machine.save()
|
||||
reversion.set_user(request.user)
|
||||
|
@ -297,58 +369,95 @@ def new_switch(request):
|
|||
new_interface.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Création")
|
||||
new_domain.interface_parent = new_interface
|
||||
new_domain_instance.interface_parent = new_interface
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
new_domain.save()
|
||||
new_domain_instance.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Création")
|
||||
new_switch.switch_interface = new_interface
|
||||
new_switch_instance.switch_interface = new_interface
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
new_switch.save()
|
||||
new_switch_instance.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Création")
|
||||
messages.success(request, "Le switch a été crée")
|
||||
messages.success(request, "Le switch a été créé")
|
||||
return redirect("/topologie/")
|
||||
return form({'topoform':switch, 'machineform': machine, 'interfaceform': interface, 'domainform': domain}, 'topologie/switch.html', request)
|
||||
i_mbf_param = generate_ipv4_mbf_param( interface, False )
|
||||
return form({
|
||||
'topoform': switch,
|
||||
'machineform': machine,
|
||||
'interfaceform': interface,
|
||||
'domainform': domain,
|
||||
'i_mbf_param': i_mbf_param
|
||||
}, 'topologie/switch.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
def edit_switch(request, switch_id):
|
||||
""" Edition d'un switch. Permet de chambre nombre de ports, place dans le stack,
|
||||
interface et machine associée"""
|
||||
""" 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("/topologie/")
|
||||
switch_form = EditSwitchForm(request.POST or None, instance=switch)
|
||||
machine_form = EditMachineForm(request.POST or None, instance=switch.switch_interface.machine)
|
||||
interface_form = EditInterfaceForm(request.POST or None, instance=switch.switch_interface)
|
||||
domain_form = AliasForm(request.POST or None, infra=request.user.has_perms(('infra',)), instance=switch.switch_interface.domain)
|
||||
if switch_form.is_valid() and machine_form.is_valid() and interface_form.is_valid():
|
||||
machine_form = EditMachineForm(
|
||||
request.POST or None,
|
||||
instance=switch.switch_interface.machine
|
||||
)
|
||||
interface_form = EditInterfaceForm(
|
||||
request.POST or None,
|
||||
instance=switch.switch_interface
|
||||
)
|
||||
domain_form = AliasForm(
|
||||
request.POST or None,
|
||||
infra=request.user.has_perms(('infra',)),
|
||||
instance=switch.switch_interface.domain
|
||||
)
|
||||
if switch_form.is_valid() and machine_form.is_valid()\
|
||||
and interface_form.is_valid():
|
||||
new_interface = interface_form.save(commit=False)
|
||||
new_machine = machine_form.save(commit=False)
|
||||
new_switch = switch_form.save(commit=False)
|
||||
new_switch_instance = switch_form.save(commit=False)
|
||||
new_domain = domain_form.save(commit=False)
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
new_machine.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in machine_form.changed_data))
|
||||
reversion.set_comment(
|
||||
"Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in machine_form.changed_data
|
||||
)
|
||||
)
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
new_interface.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in interface_form.changed_data))
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in interface_form.changed_data)
|
||||
)
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
new_domain.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in domain_form.changed_data))
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in domain_form.changed_data)
|
||||
)
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
new_switch.save()
|
||||
new_switch_instance.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in switch_form.changed_data))
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in switch_form.changed_data)
|
||||
)
|
||||
messages.success(request, "Le switch a bien été modifié")
|
||||
return redirect("/topologie/")
|
||||
return form({'topoform':switch_form, 'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form}, 'topologie/switch.html', request)
|
||||
i_mbf_param = generate_ipv4_mbf_param( interface_form, False )
|
||||
return form({
|
||||
'topoform': switch_form,
|
||||
'machineform': machine_form,
|
||||
'interfaceform': interface_form,
|
||||
'domainform': domain_form,
|
||||
'i_mbf_param': i_mbf_param
|
||||
}, 'topologie/switch.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
|
@ -364,6 +473,7 @@ def new_room(request):
|
|||
return redirect("/topologie/index_room/")
|
||||
return form({'topoform': room}, 'topologie/topo.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
def edit_room(request, room_id):
|
||||
|
@ -378,11 +488,14 @@ def edit_room(request, room_id):
|
|||
with transaction.atomic(), reversion.create_revision():
|
||||
room.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in room.changed_data))
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in room.changed_data)
|
||||
)
|
||||
messages.success(request, "La chambre a bien été modifiée")
|
||||
return redirect("/topologie/index_room/")
|
||||
return form({'topoform': room}, 'topologie/topo.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
def del_room(request, room_id):
|
||||
|
@ -400,6 +513,10 @@ def del_room(request, room_id):
|
|||
reversion.set_comment("Destruction")
|
||||
messages.success(request, "La chambre/prise a été détruite")
|
||||
except ProtectedError:
|
||||
messages.error(request, "La chambre %s est affectée à un autre objet, impossible de la supprimer (switch ou user)" % room)
|
||||
messages.error(request, "La chambre %s est affectée à un autre objet,\
|
||||
impossible de la supprimer (switch ou user)" % room)
|
||||
return redirect("/topologie/index_room/")
|
||||
return form({'objet': room, 'objet_name': 'Chambre'}, 'topologie/delete.html', request)
|
||||
return form({
|
||||
'objet': room,
|
||||
'objet_name': 'Chambre'
|
||||
}, 'topologie/delete.html', request)
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
"""
|
||||
Definition des vues pour les admin. Classique, sauf pour users,
|
||||
où on fait appel à UserChange et ServiceUserChange, forms custom
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
@ -28,11 +32,15 @@ from django.contrib.auth.models import Group
|
|||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from reversion.admin import VersionAdmin
|
||||
|
||||
from .models import User, ServiceUser, School, Right, ListRight, ListShell, Ban, Whitelist, Request, LdapUser, LdapServiceUser, LdapServiceUserGroup, LdapUserGroup
|
||||
from .forms import UserChangeForm, UserCreationForm, ServiceUserChangeForm, ServiceUserCreationForm
|
||||
from .models import User, ServiceUser, School, Right, ListRight, ListShell
|
||||
from .models import Ban, Whitelist, Request, LdapUser, LdapServiceUser
|
||||
from .models import LdapServiceUserGroup, LdapUserGroup
|
||||
from .forms import UserChangeForm, UserCreationForm
|
||||
from .forms import ServiceUserChangeForm, ServiceUserCreationForm
|
||||
|
||||
|
||||
class UserAdmin(admin.ModelAdmin):
|
||||
"""Administration d'un user"""
|
||||
list_display = (
|
||||
'name',
|
||||
'surname',
|
||||
|
@ -47,47 +55,69 @@ class UserAdmin(admin.ModelAdmin):
|
|||
|
||||
|
||||
class LdapUserAdmin(admin.ModelAdmin):
|
||||
"""Administration du ldapuser"""
|
||||
list_display = ('name', 'uidNumber', 'login_shell')
|
||||
exclude = ('user_password', 'sambat_nt_password')
|
||||
search_fields = ('name',)
|
||||
|
||||
|
||||
class LdapServiceUserAdmin(admin.ModelAdmin):
|
||||
"""Administration du ldapserviceuser"""
|
||||
list_display = ('name',)
|
||||
exclude = ('user_password',)
|
||||
search_fields = ('name',)
|
||||
|
||||
|
||||
class LdapUserGroupAdmin(admin.ModelAdmin):
|
||||
"""Administration du ldapusergroupe"""
|
||||
list_display = ('name', 'members', 'gid')
|
||||
search_fields = ('name',)
|
||||
|
||||
|
||||
class LdapServiceUserGroupAdmin(admin.ModelAdmin):
|
||||
"""Administration du ldap serviceusergroup"""
|
||||
list_display = ('name',)
|
||||
search_fields = ('name',)
|
||||
|
||||
|
||||
class SchoolAdmin(VersionAdmin):
|
||||
list_display = ('name',)
|
||||
"""Administration, gestion des écoles"""
|
||||
pass
|
||||
|
||||
|
||||
class ListRightAdmin(VersionAdmin):
|
||||
"""Gestion de la liste des droits existants
|
||||
Ne permet pas l'edition du gid (primarykey pour ldap)"""
|
||||
list_display = ('listright',)
|
||||
|
||||
|
||||
class ListShellAdmin(VersionAdmin):
|
||||
list_display = ('shell',)
|
||||
"""Gestion de la liste des shells coté admin"""
|
||||
pass
|
||||
|
||||
|
||||
class RightAdmin(VersionAdmin):
|
||||
list_display = ('user', 'right')
|
||||
"""Gestion de la liste des droits affectés"""
|
||||
pass
|
||||
|
||||
|
||||
class RequestAdmin(admin.ModelAdmin):
|
||||
"""Gestion des request objet, ticket pour lien de reinit mot de passe"""
|
||||
list_display = ('user', 'type', 'created_at', 'expires_at')
|
||||
|
||||
|
||||
class BanAdmin(VersionAdmin):
|
||||
list_display = ('user', 'raison', 'date_start', 'date_end')
|
||||
"""Gestion des bannissements"""
|
||||
pass
|
||||
|
||||
|
||||
class WhitelistAdmin(VersionAdmin):
|
||||
list_display = ('user', 'raison', 'date_start', 'date_end')
|
||||
"""Gestion des whitelist"""
|
||||
pass
|
||||
|
||||
|
||||
class UserAdmin(VersionAdmin, BaseUserAdmin):
|
||||
"""Gestion d'un user : modification des champs perso, mot de passe, etc"""
|
||||
# The forms to add and change user instances
|
||||
form = UserChangeForm
|
||||
add_form = UserCreationForm
|
||||
|
@ -95,27 +125,56 @@ class UserAdmin(VersionAdmin, BaseUserAdmin):
|
|||
# The fields to be used in displaying the User model.
|
||||
# These override the definitions on the base UserAdmin
|
||||
# that reference specific fields on auth.User.
|
||||
list_display = ('pseudo', 'name', 'surname', 'email', 'school', 'is_admin', 'shell')
|
||||
list_display = (
|
||||
'pseudo',
|
||||
'name',
|
||||
'surname',
|
||||
'email',
|
||||
'school',
|
||||
'is_admin',
|
||||
'shell'
|
||||
)
|
||||
list_display = ('pseudo',)
|
||||
list_filter = ()
|
||||
fieldsets = (
|
||||
(None, {'fields': ('pseudo', 'password')}),
|
||||
('Personal info', {'fields': ('name', 'surname', 'email', 'school','shell', 'uid_number')}),
|
||||
(
|
||||
'Personal info',
|
||||
{
|
||||
'fields':
|
||||
('name', 'surname', 'email', 'school', 'shell', 'uid_number')
|
||||
}
|
||||
),
|
||||
('Permissions', {'fields': ('is_admin', )}),
|
||||
)
|
||||
# add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
|
||||
# overrides get_fieldsets to use this attribute when creating a user.
|
||||
add_fieldsets = (
|
||||
(None, {
|
||||
(
|
||||
None,
|
||||
{
|
||||
'classes': ('wide',),
|
||||
'fields': ('pseudo', 'name', 'surname', 'email', 'school', 'is_admin', 'password1', 'password2')}
|
||||
'fields': (
|
||||
'pseudo',
|
||||
'name',
|
||||
'surname',
|
||||
'email',
|
||||
'school',
|
||||
'is_admin',
|
||||
'password1',
|
||||
'password2'
|
||||
)
|
||||
}
|
||||
),
|
||||
)
|
||||
search_fields = ('pseudo',)
|
||||
ordering = ('pseudo',)
|
||||
filter_horizontal = ()
|
||||
|
||||
|
||||
class ServiceUserAdmin(VersionAdmin, BaseUserAdmin):
|
||||
"""Gestion d'un service user admin : champs personnels,
|
||||
mot de passe; etc"""
|
||||
# The forms to add and change user instances
|
||||
form = ServiceUserChangeForm
|
||||
add_form = ServiceUserCreationForm
|
||||
|
@ -131,15 +190,19 @@ class ServiceUserAdmin(VersionAdmin, BaseUserAdmin):
|
|||
# add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
|
||||
# overrides get_fieldsets to use this attribute when creating a user.
|
||||
add_fieldsets = (
|
||||
(None, {
|
||||
(
|
||||
None,
|
||||
{
|
||||
'classes': ('wide',),
|
||||
'fields': ('pseudo', 'password1', 'password2')}
|
||||
'fields': ('pseudo', 'password1', 'password2')
|
||||
}
|
||||
),
|
||||
)
|
||||
search_fields = ('pseudo',)
|
||||
ordering = ('pseudo',)
|
||||
filter_horizontal = ()
|
||||
|
||||
|
||||
admin.site.register(User, UserAdmin)
|
||||
admin.site.register(ServiceUser, ServiceUserAdmin)
|
||||
admin.site.register(LdapUser, LdapUserAdmin)
|
||||
|
|
250
users/forms.py
250
users/forms.py
|
@ -20,8 +20,16 @@
|
|||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
"""
|
||||
Definition des forms pour l'application users.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
Modification, creation de :
|
||||
- un user (informations personnelles)
|
||||
- un bannissement
|
||||
- le mot de passe d'un user
|
||||
- une whiteliste
|
||||
- un user de service
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
@ -29,17 +37,34 @@ from django import forms
|
|||
from django.forms import ModelForm, Form
|
||||
from django.contrib.auth.forms import ReadOnlyPasswordHashField
|
||||
from django.core.validators import MinLengthValidator
|
||||
from preferences.models import OptionalUser
|
||||
from django.utils import timezone
|
||||
from .models import User, ServiceUser, Right, School, ListRight, Whitelist, Ban, Request, remove_user_room
|
||||
|
||||
from .models import get_admin_right
|
||||
from preferences.models import OptionalUser
|
||||
from .models import User, ServiceUser, Right, School, ListRight, Whitelist
|
||||
from .models import Ban, remove_user_room
|
||||
|
||||
NOW = timezone.now()
|
||||
|
||||
|
||||
class PassForm(forms.Form):
|
||||
passwd1 = forms.CharField(label=u'Nouveau mot de passe', max_length=255, validators=[MinLengthValidator(8)], widget=forms.PasswordInput)
|
||||
passwd2 = forms.CharField(label=u'Saisir à nouveau le mot de passe', max_length=255, validators=[MinLengthValidator(8)], widget=forms.PasswordInput)
|
||||
"""Formulaire de changement de mot de passe. Verifie que les 2
|
||||
nouveaux mots de passe renseignés sont identiques et respectent
|
||||
une norme"""
|
||||
passwd1 = forms.CharField(
|
||||
label=u'Nouveau mot de passe',
|
||||
max_length=255,
|
||||
validators=[MinLengthValidator(8)],
|
||||
widget=forms.PasswordInput
|
||||
)
|
||||
passwd2 = forms.CharField(
|
||||
label=u'Saisir à nouveau le mot de passe',
|
||||
max_length=255,
|
||||
validators=[MinLengthValidator(8)],
|
||||
widget=forms.PasswordInput
|
||||
)
|
||||
|
||||
def clean_passwd2(self):
|
||||
"""Verifie que passwd1 et 2 sont identiques"""
|
||||
# Check that the two password entries match
|
||||
password1 = self.cleaned_data.get("passwd1")
|
||||
password2 = self.cleaned_data.get("passwd2")
|
||||
|
@ -47,18 +72,38 @@ class PassForm(forms.Form):
|
|||
raise forms.ValidationError("Passwords don't match")
|
||||
return password2
|
||||
|
||||
|
||||
class UserCreationForm(forms.ModelForm):
|
||||
"""A form for creating new users. Includes all the required
|
||||
fields, plus a repeated password."""
|
||||
password1 = forms.CharField(label='Password', widget=forms.PasswordInput, validators=[MinLengthValidator(8)], max_length=255)
|
||||
password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput, validators=[MinLengthValidator(8)], max_length=255)
|
||||
fields, plus a repeated password.
|
||||
|
||||
Formulaire pour la création d'un user. N'est utilisé que pour
|
||||
l'admin, lors de la creation d'un user par admin. Inclu tous les
|
||||
champs obligatoires"""
|
||||
password1 = forms.CharField(
|
||||
label='Password',
|
||||
widget=forms.PasswordInput,
|
||||
validators=[MinLengthValidator(8)],
|
||||
max_length=255
|
||||
)
|
||||
password2 = forms.CharField(
|
||||
label='Password confirmation',
|
||||
widget=forms.PasswordInput,
|
||||
validators=[MinLengthValidator(8)],
|
||||
max_length=255
|
||||
)
|
||||
is_admin = forms.BooleanField(label='is admin')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(UserCreationForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('pseudo', 'name', 'surname', 'email')
|
||||
|
||||
def clean_password2(self):
|
||||
"""Verifie que password1 et 2 sont identiques"""
|
||||
# Check that the two password entries match
|
||||
password1 = self.cleaned_data.get("password1")
|
||||
password2 = self.cleaned_data.get("password2")
|
||||
|
@ -74,17 +119,40 @@ class UserCreationForm(forms.ModelForm):
|
|||
user.is_admin = self.cleaned_data.get("is_admin")
|
||||
return user
|
||||
|
||||
|
||||
class ServiceUserCreationForm(forms.ModelForm):
|
||||
"""A form for creating new users. Includes all the required
|
||||
fields, plus a repeated password."""
|
||||
password1 = forms.CharField(label='Password', widget=forms.PasswordInput, min_length=8, max_length=255)
|
||||
password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput, min_length=8, max_length=255)
|
||||
fields, plus a repeated password.
|
||||
|
||||
Formulaire pour la creation de nouveaux serviceusers.
|
||||
Requiert seulement un mot de passe; et un pseudo"""
|
||||
password1 = forms.CharField(
|
||||
label='Password',
|
||||
widget=forms.PasswordInput,
|
||||
min_length=8,
|
||||
max_length=255
|
||||
)
|
||||
password2 = forms.CharField(
|
||||
label='Password confirmation',
|
||||
widget=forms.PasswordInput,
|
||||
min_length=8,
|
||||
max_length=255
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(ServiceUserCreationForm, self).__init__(
|
||||
*args,
|
||||
prefix=prefix,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ServiceUser
|
||||
fields = ('pseudo',)
|
||||
|
||||
def clean_password2(self):
|
||||
"""Verifie que password1 et 2 sont indentiques"""
|
||||
# Check that the two password entries match
|
||||
password1 = self.cleaned_data.get("password1")
|
||||
password2 = self.cleaned_data.get("password2")
|
||||
|
@ -99,10 +167,13 @@ class ServiceUserCreationForm(forms.ModelForm):
|
|||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
class UserChangeForm(forms.ModelForm):
|
||||
"""A form for updating users. Includes all the fields on
|
||||
the user, but replaces the password field with admin's
|
||||
password hash display field.
|
||||
|
||||
Formulaire pour la modification d'un user coté admin
|
||||
"""
|
||||
password = ReadOnlyPasswordHashField()
|
||||
is_admin = forms.BooleanField(label='is admin', required=False)
|
||||
|
@ -112,11 +183,13 @@ class UserChangeForm(forms.ModelForm):
|
|||
fields = ('pseudo', 'password', 'name', 'surname', 'email')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UserChangeForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(UserChangeForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
print("User is admin : %s" % kwargs['instance'].is_admin)
|
||||
self.initial['is_admin'] = kwargs['instance'].is_admin
|
||||
|
||||
def clean_password(self):
|
||||
"""Dummy fun"""
|
||||
# Regardless of what the user provides, return the initial value.
|
||||
# This is done here, rather than on the field, because the
|
||||
# field does not have access to the initial value
|
||||
|
@ -130,40 +203,62 @@ class UserChangeForm(forms.ModelForm):
|
|||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
class ServiceUserChangeForm(forms.ModelForm):
|
||||
"""A form for updating users. Includes all the fields on
|
||||
the user, but replaces the password field with admin's
|
||||
password hash display field.
|
||||
|
||||
Formulaire pour l'edition des service users coté admin
|
||||
"""
|
||||
password = ReadOnlyPasswordHashField()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(ServiceUserChangeForm, self).__init__(
|
||||
*args,
|
||||
prefix=prefix,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ServiceUser
|
||||
fields = ('pseudo',)
|
||||
|
||||
def clean_password(self):
|
||||
# Regardless of what the user provides, return the initial value.
|
||||
# This is done here, rather than on the field, because the
|
||||
# field does not have access to the initial value
|
||||
"""Dummy fun"""
|
||||
return self.initial["password"]
|
||||
|
||||
|
||||
class ResetPasswordForm(forms.Form):
|
||||
"""Formulaire de demande de reinitialisation de mot de passe,
|
||||
mdp oublié"""
|
||||
pseudo = forms.CharField(label=u'Pseudo', max_length=255)
|
||||
email = forms.EmailField(max_length=255)
|
||||
|
||||
|
||||
class MassArchiveForm(forms.Form):
|
||||
"""Formulaire d'archivage des users inactif. Prend en argument
|
||||
du formulaire la date de depart avant laquelle archiver les
|
||||
users"""
|
||||
date = forms.DateTimeField(help_text='%d/%m/%y')
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(MassArchiveForm, self).clean()
|
||||
date = cleaned_data.get("date")
|
||||
if date:
|
||||
if date>timezone.now():
|
||||
raise forms.ValidationError("Impossible d'archiver des utilisateurs dont la fin d'accès se situe dans le futur !")
|
||||
if date > NOW:
|
||||
raise forms.ValidationError("Impossible d'archiver des\
|
||||
utilisateurs dont la fin d'accès se situe dans le futur !")
|
||||
|
||||
|
||||
class BaseInfoForm(ModelForm):
|
||||
"""Formulaire de base d'edition d'un user. Formulaire de base, utilisé
|
||||
pour l'edition de self par self ou un cableur. On formate les champs
|
||||
avec des label plus jolis"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(BaseInfoForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(BaseInfoForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['name'].label = 'Prénom'
|
||||
self.fields['surname'].label = 'Nom'
|
||||
self.fields['school'].label = 'Établissement'
|
||||
|
@ -186,13 +281,21 @@ class BaseInfoForm(ModelForm):
|
|||
]
|
||||
|
||||
def clean_telephone(self):
|
||||
"""Verifie que le tel est présent si 'option est validée
|
||||
dans preferences"""
|
||||
telephone = self.cleaned_data['telephone']
|
||||
preferences, created = OptionalUser.objects.get_or_create()
|
||||
preferences, _created = OptionalUser.objects.get_or_create()
|
||||
if not telephone and preferences.is_tel_mandatory:
|
||||
raise forms.ValidationError("Un numéro de téléphone valide est requis")
|
||||
raise forms.ValidationError(
|
||||
"Un numéro de téléphone valide est requis"
|
||||
)
|
||||
return telephone
|
||||
|
||||
|
||||
class EditInfoForm(BaseInfoForm):
|
||||
"""Edition complète d'un user. Utilisé par admin,
|
||||
permet d'editer normalement la chambre, ou le shell
|
||||
Herite de la base"""
|
||||
class Meta(BaseInfoForm.Meta):
|
||||
fields = [
|
||||
'name',
|
||||
|
@ -206,80 +309,138 @@ class EditInfoForm(BaseInfoForm):
|
|||
'telephone',
|
||||
]
|
||||
|
||||
|
||||
class InfoForm(EditInfoForm):
|
||||
""" Utile pour forcer un déménagement quand il y a déjà un user en place"""
|
||||
force = forms.BooleanField(label="Forcer le déménagement ?", initial=False, required=False)
|
||||
""" Utile pour forcer un déménagement quand il y a déjà un user en place
|
||||
Formuaire utilisé pour la creation initiale"""
|
||||
force = forms.BooleanField(
|
||||
label="Forcer le déménagement ?",
|
||||
initial=False,
|
||||
required=False
|
||||
)
|
||||
|
||||
def clean_force(self):
|
||||
"""On supprime l'ancien user de la chambre si et seulement si la
|
||||
case est cochée"""
|
||||
if self.cleaned_data.get('force', False):
|
||||
remove_user_room(self.cleaned_data.get('room'))
|
||||
return
|
||||
|
||||
|
||||
class UserForm(InfoForm):
|
||||
""" Model form general"""
|
||||
class Meta(InfoForm.Meta):
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class PasswordForm(ModelForm):
|
||||
""" Formulaire de changement brut de mot de passe. Ne pas utiliser sans traitement"""
|
||||
""" Formulaire de changement brut de mot de passe.
|
||||
Ne pas utiliser sans traitement"""
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['password', 'pwd_ntlm']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(PasswordForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
|
||||
class ServiceUserForm(ModelForm):
|
||||
""" Modification d'un service user"""
|
||||
password = forms.CharField(label=u'Nouveau mot de passe', max_length=255, validators=[MinLengthValidator(8)], widget=forms.PasswordInput, required=False)
|
||||
password = forms.CharField(
|
||||
label=u'Nouveau mot de passe',
|
||||
max_length=255,
|
||||
validators=[MinLengthValidator(8)],
|
||||
widget=forms.PasswordInput,
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ServiceUser
|
||||
fields = ('pseudo', 'access_group')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(ServiceUserForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
|
||||
class EditServiceUserForm(ServiceUserForm):
|
||||
"""Formulaire d'edition de base d'un service user. Ne permet
|
||||
d'editer que son group d'acl et son commentaire"""
|
||||
class Meta(ServiceUserForm.Meta):
|
||||
fields = ['access_group', 'comment']
|
||||
|
||||
|
||||
class StateForm(ModelForm):
|
||||
""" Changement de l'état d'un user"""
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['state']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(StateForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
|
||||
class SchoolForm(ModelForm):
|
||||
"""Edition, creation d'un école"""
|
||||
class Meta:
|
||||
model = School
|
||||
fields = ['name']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SchoolForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(SchoolForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['name'].label = 'Établissement'
|
||||
|
||||
|
||||
class ListRightForm(ModelForm):
|
||||
"""Edition, d'un groupe , équivalent à un droit
|
||||
Ne peremet pas d'editer le gid, car il sert de primary key"""
|
||||
class Meta:
|
||||
model = ListRight
|
||||
fields = ['listright', 'details']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ListRightForm, self).__init__(*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'
|
||||
|
||||
|
||||
class NewListRightForm(ListRightForm):
|
||||
"""Ajout d'un groupe/list de droit """
|
||||
class Meta(ListRightForm.Meta):
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NewListRightForm, self).__init__(*args, **kwargs)
|
||||
self.fields['gid'].label = 'Gid, attention, cet attribut ne doit pas être modifié après création'
|
||||
self.fields['gid'].label = 'Gid, attention, cet attribut ne doit\
|
||||
pas être modifié après création'
|
||||
|
||||
|
||||
class DelListRightForm(Form):
|
||||
listrights = forms.ModelMultipleChoiceField(queryset=ListRight.objects.all(), label="Droits actuels", widget=forms.CheckboxSelectMultiple)
|
||||
"""Suppression d'un ou plusieurs groupes"""
|
||||
listrights = forms.ModelMultipleChoiceField(
|
||||
queryset=ListRight.objects.all(),
|
||||
label="Droits actuels",
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
||||
class DelSchoolForm(Form):
|
||||
schools = forms.ModelMultipleChoiceField(queryset=School.objects.all(), label="Etablissements actuels", widget=forms.CheckboxSelectMultiple)
|
||||
"""Suppression d'une ou plusieurs écoles"""
|
||||
schools = forms.ModelMultipleChoiceField(
|
||||
queryset=School.objects.all(),
|
||||
label="Etablissements actuels",
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
||||
class RightForm(ModelForm):
|
||||
"""Assignation d'un droit à un user"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RightForm, self).__init__(*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"
|
||||
|
||||
|
@ -289,15 +450,22 @@ class RightForm(ModelForm):
|
|||
|
||||
|
||||
class DelRightForm(Form):
|
||||
rights = forms.ModelMultipleChoiceField(queryset=Right.objects.all(), widget=forms.CheckboxSelectMultiple)
|
||||
"""Suppression d'un droit d'un user"""
|
||||
rights = forms.ModelMultipleChoiceField(
|
||||
queryset=Right.objects.all(),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
def __init__(self, right, *args, **kwargs):
|
||||
super(DelRightForm, self).__init__(*args, **kwargs)
|
||||
self.fields['rights'].queryset = Right.objects.filter(right=right)
|
||||
|
||||
|
||||
class BanForm(ModelForm):
|
||||
"""Creation, edition d'un objet bannissement"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(BanForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(BanForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['date_end'].label = 'Date de fin'
|
||||
|
||||
class Meta:
|
||||
|
@ -305,15 +473,19 @@ class BanForm(ModelForm):
|
|||
exclude = ['user']
|
||||
|
||||
def clean_date_end(self):
|
||||
"""Verification que date_end est après now"""
|
||||
date_end = self.cleaned_data['date_end']
|
||||
if date_end < timezone.now():
|
||||
raise forms.ValidationError("Triple buse, la date de fin ne peut pas être avant maintenant... Re2o ne voyage pas dans le temps")
|
||||
if date_end < NOW:
|
||||
raise forms.ValidationError("Triple buse, la date de fin ne peut\
|
||||
pas être avant maintenant... Re2o ne voyage pas dans le temps")
|
||||
return date_end
|
||||
|
||||
|
||||
class WhitelistForm(ModelForm):
|
||||
"""Creation, edition d'un objet whitelist"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WhitelistForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(WhitelistForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['date_end'].label = 'Date de fin'
|
||||
|
||||
class Meta:
|
||||
|
@ -321,7 +493,9 @@ class WhitelistForm(ModelForm):
|
|||
exclude = ['user']
|
||||
|
||||
def clean_date_end(self):
|
||||
"""Verification que la date_end est posterieur à now"""
|
||||
date_end = self.cleaned_data['date_end']
|
||||
if date_end < timezone.now():
|
||||
raise forms.ValidationError("Triple buse, la date de fin ne peut pas être avant maintenant... Re2o ne voyage pas dans le temps")
|
||||
if date_end < NOW:
|
||||
raise forms.ValidationError("Triple buse, la date de fin ne peut pas\
|
||||
être avant maintenant... Re2o ne voyage pas dans le temps")
|
||||
return date_end
|
||||
|
|
530
users/models.py
530
users/models.py
File diff suppressed because it is too large
Load diff
|
@ -35,19 +35,28 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<tbody>
|
||||
{% for key, values in userform.items %}
|
||||
<th>{{ key }}</th>
|
||||
{% endfor %}
|
||||
<tr class="active">
|
||||
<td>
|
||||
<a data-toggle="collapse" href="#collapseRight_{{key}}" aria-expanded="false" aria-controls="collapseRights_{{key}}">
|
||||
<b>{{ key }}</b> ( {{values.rights|length }} users )
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr>
|
||||
{% for key, values in userform.items %}
|
||||
{% bootstrap_form_errors values %}
|
||||
<th>{{ values.rights }}</th>
|
||||
<td>
|
||||
<div class="collapse" id="collapseRight_{{key}}">
|
||||
<ul class="list-group" style="margin-bottom: 0px">
|
||||
{% for user in values.rights %}
|
||||
<li class="list-group-item col-xs-6 col-sm-4 col-md-3" style="border: none;">{{ user }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% bootstrap_button "Modifier" button_type="submit" icon="star" %}
|
||||
</form>
|
||||
|
|
|
@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load massive_bootstrap_form %}
|
||||
|
||||
{% block title %}Création et modification d'utilisateur{% endblock %}
|
||||
|
||||
|
@ -32,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form userform %}
|
||||
{% massive_bootstrap_form userform 'room' %}
|
||||
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
|
||||
</form>
|
||||
<br />
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
"""
|
||||
Definition des urls, pointant vers les views
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
@ -32,39 +35,88 @@ urlpatterns = [
|
|||
url(r'^state/(?P<userid>[0-9]+)$', views.state, name='state'),
|
||||
url(r'^password/(?P<userid>[0-9]+)$', views.password, name='password'),
|
||||
url(r'^new_serviceuser/$', views.new_serviceuser, name='new-serviceuser'),
|
||||
url(r'^edit_serviceuser/(?P<userid>[0-9]+)$', views.edit_serviceuser, name='edit-serviceuser'),
|
||||
url(r'^del_serviceuser/(?P<userid>[0-9]+)$', views.del_serviceuser, name='del-serviceuser'),
|
||||
url(
|
||||
r'^edit_serviceuser/(?P<userid>[0-9]+)$',
|
||||
views.edit_serviceuser,
|
||||
name='edit-serviceuser'
|
||||
),
|
||||
url(
|
||||
r'^del_serviceuser/(?P<userid>[0-9]+)$',
|
||||
views.del_serviceuser,
|
||||
name='del-serviceuser'
|
||||
),
|
||||
url(r'^add_ban/(?P<userid>[0-9]+)$', views.add_ban, name='add-ban'),
|
||||
url(r'^edit_ban/(?P<banid>[0-9]+)$', views.edit_ban, name='edit-ban'),
|
||||
url(r'^add_whitelist/(?P<userid>[0-9]+)$', views.add_whitelist, name='add-whitelist'),
|
||||
url(r'^edit_whitelist/(?P<whitelistid>[0-9]+)$', views.edit_whitelist, name='edit-whitelist'),
|
||||
url(
|
||||
r'^add_whitelist/(?P<userid>[0-9]+)$',
|
||||
views.add_whitelist,
|
||||
name='add-whitelist'
|
||||
),
|
||||
url(
|
||||
r'^edit_whitelist/(?P<whitelistid>[0-9]+)$',
|
||||
views.edit_whitelist,
|
||||
name='edit-whitelist'
|
||||
),
|
||||
url(r'^add_right/(?P<userid>[0-9]+)$', views.add_right, name='add-right'),
|
||||
url(r'^del_right/$', views.del_right, name='del-right'),
|
||||
url(r'^add_school/$', views.add_school, name='add-school'),
|
||||
url(r'^edit_school/(?P<schoolid>[0-9]+)$', views.edit_school, name='edit-school'),
|
||||
url(
|
||||
r'^edit_school/(?P<schoolid>[0-9]+)$',
|
||||
views.edit_school,
|
||||
name='edit-school'
|
||||
),
|
||||
url(r'^del_school/$', views.del_school, name='del-school'),
|
||||
url(r'^add_listright/$', views.add_listright, name='add-listright'),
|
||||
url(r'^edit_listright/(?P<listrightid>[0-9]+)$', views.edit_listright, name='edit-listright'),
|
||||
url(
|
||||
r'^edit_listright/(?P<listrightid>[0-9]+)$',
|
||||
views.edit_listright,
|
||||
name='edit-listright'
|
||||
),
|
||||
url(r'^del_listright/$', views.del_listright, name='del-listright'),
|
||||
url(r'^profil/(?P<userid>[0-9]+)$', views.profil, name='profil'),
|
||||
url(r'^index_ban/$', views.index_ban, name='index-ban'),
|
||||
url(r'^index_white/$', views.index_white, name='index-white'),
|
||||
url(r'^index_school/$', views.index_school, name='index-school'),
|
||||
url(r'^index_listright/$', views.index_listright, name='index-listright'),
|
||||
url(r'^index_serviceusers/$', views.index_serviceusers, name='index-serviceusers'),
|
||||
url(
|
||||
r'^index_serviceusers/$',
|
||||
views.index_serviceusers,
|
||||
name='index-serviceusers'
|
||||
),
|
||||
url(r'^mon_profil/$', views.mon_profil, name='mon-profil'),
|
||||
url(r'^process/(?P<token>[a-z0-9]{32})/$', views.process, name='process'),
|
||||
url(r'^reset_password/$', views.reset_password, name='reset-password'),
|
||||
url(r'^mass_archive/$', views.mass_archive, name='mass-archive'),
|
||||
url(r'^history/(?P<object>user)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||
url(r'^history/(?P<object>ban)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||
url(r'^history/(?P<object>whitelist)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||
url(r'^history/(?P<object>school)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||
url(r'^history/(?P<object>listright)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||
url(r'^history/(?P<object>serviceuser)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||
url(
|
||||
r'^history/(?P<object>user)/(?P<id>[0-9]+)$',
|
||||
views.history,
|
||||
name='history'
|
||||
),
|
||||
url(
|
||||
r'^history/(?P<object>ban)/(?P<id>[0-9]+)$',
|
||||
views.history,
|
||||
name='history'
|
||||
),
|
||||
url(
|
||||
r'^history/(?P<object>whitelist)/(?P<id>[0-9]+)$',
|
||||
views.history,
|
||||
name='history'
|
||||
),
|
||||
url(
|
||||
r'^history/(?P<object>school)/(?P<id>[0-9]+)$',
|
||||
views.history,
|
||||
name='history'
|
||||
),
|
||||
url(
|
||||
r'^history/(?P<object>listright)/(?P<id>[0-9]+)$',
|
||||
views.history,
|
||||
name='history'
|
||||
),
|
||||
url(
|
||||
r'^history/(?P<object>serviceuser)/(?P<id>[0-9]+)$',
|
||||
views.history,
|
||||
name='history'
|
||||
),
|
||||
url(r'^$', views.index, name='index'),
|
||||
url(r'^rest/mailing/$', views.mailing, name='mailing'),
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
|
331
users/views.py
331
users/views.py
|
@ -23,20 +23,25 @@
|
|||
# App de gestion des users pour re2o
|
||||
# Goulven Kermarec, Gabriel Détraz, Lemesle Augustin
|
||||
# Gplv2
|
||||
"""
|
||||
Module des views.
|
||||
|
||||
On définit les vues pour l'ajout, l'edition des users : infos personnelles,
|
||||
mot de passe, etc
|
||||
|
||||
Permet aussi l'ajout, edition et suppression des droits, des bannissements,
|
||||
des whitelist, des services users et des écoles
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.shortcuts import get_object_or_404, render, redirect
|
||||
from django.template.context_processors import csrf
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.template import Context, RequestContext, loader
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.db.models import Max, ProtectedError
|
||||
from django.db.models import ProtectedError
|
||||
from django.db import IntegrityError
|
||||
from django.core.mail import send_mail
|
||||
from django.utils import timezone
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.http import HttpResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
@ -47,21 +52,20 @@ from rest_framework.renderers import JSONRenderer
|
|||
from reversion.models import Version
|
||||
from reversion import revisions as reversion
|
||||
from users.serializers import MailSerializer
|
||||
from users.models import User, Right, Ban, Whitelist, School, ListRight, Request, ServiceUser, all_has_access
|
||||
from users.forms import DelRightForm, BanForm, WhitelistForm, DelSchoolForm, DelListRightForm, NewListRightForm
|
||||
from users.forms import EditInfoForm, InfoForm, BaseInfoForm, StateForm, RightForm, SchoolForm, EditServiceUserForm, ServiceUserForm, ListRightForm
|
||||
from cotisations.models import Facture
|
||||
from machines.models import Machine, Interface
|
||||
from users.models import User, Right, Ban, Whitelist, School, ListRight
|
||||
from users.models import Request, ServiceUser
|
||||
from users.forms import DelRightForm, BanForm, WhitelistForm, DelSchoolForm
|
||||
from users.forms import DelListRightForm, NewListRightForm
|
||||
from users.forms import InfoForm, BaseInfoForm, StateForm
|
||||
from users.forms import RightForm, SchoolForm, EditServiceUserForm
|
||||
from users.forms import ServiceUserForm, ListRightForm
|
||||
from users.forms import MassArchiveForm, PassForm, ResetPasswordForm
|
||||
from preferences.models import OptionalUser, AssoOption, GeneralOption
|
||||
from cotisations.models import Facture
|
||||
from machines.models import Machine
|
||||
from preferences.models import OptionalUser, GeneralOption
|
||||
|
||||
from re2o.login import hashNT
|
||||
|
||||
|
||||
def form(ctx, template, request):
|
||||
c = ctx
|
||||
c.update(csrf(request))
|
||||
return render(request, template, c)
|
||||
from re2o.views import form
|
||||
from re2o.utils import all_has_access
|
||||
|
||||
def password_change_action(u_form, user, request, req=False):
|
||||
""" Fonction qui effectue le changeemnt de mdp bdd"""
|
||||
|
@ -75,10 +79,12 @@ def password_change_action(u_form, user, request, req=False):
|
|||
return redirect("/")
|
||||
return redirect("/users/profil/" + str(user.id))
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def new_user(request):
|
||||
""" Vue de création d'un nouvel utilisateur, envoie un mail pour le mot de passe"""
|
||||
""" Vue de création d'un nouvel utilisateur,
|
||||
envoie un mail pour le mot de passe"""
|
||||
user = InfoForm(request.POST or None)
|
||||
if user.is_valid():
|
||||
user = user.save(commit=False)
|
||||
|
@ -87,21 +93,25 @@ def new_user(request):
|
|||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Création")
|
||||
user.reset_passwd_mail(request)
|
||||
messages.success(request, "L'utilisateur %s a été crée, un mail pour l'initialisation du mot de passe a été envoyé" % user.pseudo)
|
||||
messages.success(request, "L'utilisateur %s a été crée, un mail\
|
||||
pour l'initialisation du mot de passe a été envoyé" % user.pseudo)
|
||||
return redirect("/users/profil/" + str(user.id))
|
||||
return form({'userform': user}, 'users/user.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
def edit_info(request, userid):
|
||||
""" Edite un utilisateur à partir de son id,
|
||||
si l'id est différent de request.user, vérifie la possession du droit cableur """
|
||||
si l'id est différent de request.user, vérifie la
|
||||
possession du droit cableur """
|
||||
try:
|
||||
user = User.objects.get(pk=userid)
|
||||
except User.DoesNotExist:
|
||||
messages.error(request, "Utilisateur inexistant")
|
||||
return redirect("/users/")
|
||||
if not request.user.has_perms(('cableur',)) and user != request.user:
|
||||
messages.error(request, "Vous ne pouvez pas modifier un autre user que vous sans droit cableur")
|
||||
messages.error(request, "Vous ne pouvez pas modifier un autre\
|
||||
user que vous sans droit cableur")
|
||||
return redirect("/users/profil/" + str(request.user.id))
|
||||
if not request.user.has_perms(('cableur',)):
|
||||
user = BaseInfoForm(request.POST or None, instance=user)
|
||||
|
@ -111,15 +121,19 @@ def edit_info(request, userid):
|
|||
with transaction.atomic(), reversion.create_revision():
|
||||
user.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in user.changed_data))
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in user.changed_data
|
||||
))
|
||||
messages.success(request, "L'user a bien été modifié")
|
||||
return redirect("/users/profil/" + userid)
|
||||
return form({'userform': user}, 'users/user.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('bureau')
|
||||
def state(request, userid):
|
||||
""" Changer l'etat actif/desactivé/archivé d'un user, need droit bureau """
|
||||
""" Changer l'etat actif/desactivé/archivé d'un user,
|
||||
need droit bureau """
|
||||
try:
|
||||
user = User.objects.get(pk=userid)
|
||||
except User.DoesNotExist:
|
||||
|
@ -135,12 +149,15 @@ def state(request, userid):
|
|||
elif state.cleaned_data['state'] == User.STATE_DISABLED:
|
||||
user.state = User.STATE_DISABLED
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in state.changed_data))
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in state.changed_data
|
||||
))
|
||||
user.save()
|
||||
messages.success(request, "Etat changé avec succès")
|
||||
return redirect("/users/profil/" + userid)
|
||||
return form({'userform': state}, 'users/user.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
def password(request, userid):
|
||||
""" Reinitialisation d'un mot de passe à partir de l'userid,
|
||||
|
@ -152,16 +169,20 @@ def password(request, userid):
|
|||
messages.error(request, "Utilisateur inexistant")
|
||||
return redirect("/users/")
|
||||
if not request.user.has_perms(('cableur',)) and user != request.user:
|
||||
messages.error(request, "Vous ne pouvez pas modifier un autre user que vous sans droit cableur")
|
||||
messages.error(request, "Vous ne pouvez pas modifier un\
|
||||
autre user que vous sans droit cableur")
|
||||
return redirect("/users/profil/" + str(request.user.id))
|
||||
if not request.user.has_perms(('bureau',)) and user != request.user and Right.objects.filter(user=user):
|
||||
messages.error(request, "Il faut les droits bureau pour modifier le mot de passe d'un membre actif")
|
||||
if not request.user.has_perms(('bureau',)) and user != request.user\
|
||||
and Right.objects.filter(user=user):
|
||||
messages.error(request, "Il faut les droits bureau pour modifier le\
|
||||
mot de passe d'un membre actif")
|
||||
return redirect("/users/profil/" + str(request.user.id))
|
||||
u_form = PassForm(request.POST or None)
|
||||
if u_form.is_valid():
|
||||
return password_change_action(u_form, user, request)
|
||||
return form({'userform': u_form}, 'users/user.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
def new_serviceuser(request):
|
||||
|
@ -174,15 +195,20 @@ def new_serviceuser(request):
|
|||
user_object.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Création")
|
||||
messages.success(request, "L'utilisateur %s a été crée" % user_object.pseudo)
|
||||
messages.success(
|
||||
request,
|
||||
"L'utilisateur %s a été crée" % user_object.pseudo
|
||||
)
|
||||
return redirect("/users/index_serviceusers/")
|
||||
return form({'userform': user}, 'users/user.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
def edit_serviceuser(request, userid):
|
||||
""" Edite un utilisateur à partir de son id,
|
||||
si l'id est différent de request.user, vérifie la possession du droit cableur """
|
||||
si l'id est différent de request.user,
|
||||
vérifie la possession du droit cableur """
|
||||
try:
|
||||
user = ServiceUser.objects.get(pk=userid)
|
||||
except ServiceUser.DoesNotExist:
|
||||
|
@ -196,14 +222,18 @@ def edit_serviceuser(request, userid):
|
|||
user_object.set_password(user.cleaned_data['password'])
|
||||
user_object.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in user.changed_data))
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in user.changed_data
|
||||
))
|
||||
messages.success(request, "L'user a bien été modifié")
|
||||
return redirect("/users/index_serviceusers")
|
||||
return form({'userform': user}, 'users/user.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('infra')
|
||||
def del_serviceuser(request, userid):
|
||||
"""Suppression d'un ou plusieurs serviceusers"""
|
||||
try:
|
||||
user = ServiceUser.objects.get(pk=userid)
|
||||
except ServiceUser.DoesNotExist:
|
||||
|
@ -215,7 +245,12 @@ def del_serviceuser(request, userid):
|
|||
reversion.set_user(request.user)
|
||||
messages.success(request, "L'user a été détruite")
|
||||
return redirect("/users/index_serviceusers/")
|
||||
return form({'objet': user, 'objet_name': 'serviceuser'}, 'users/delete.html', request)
|
||||
return form(
|
||||
{'objet': user, 'objet_name': 'serviceuser'},
|
||||
'users/delete.html',
|
||||
request
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('bureau')
|
||||
|
@ -241,6 +276,7 @@ def add_right(request, userid):
|
|||
return redirect("/users/profil/" + userid)
|
||||
return form({'userform': right}, 'users/user.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('bureau')
|
||||
def del_right(request):
|
||||
|
@ -248,21 +284,25 @@ def del_right(request):
|
|||
user_right_list = dict()
|
||||
for right in ListRight.objects.all():
|
||||
user_right_list[right] = DelRightForm(right, request.POST or None)
|
||||
for keys, right_item in user_right_list.items():
|
||||
for _keys, right_item in user_right_list.items():
|
||||
if right_item.is_valid():
|
||||
right_del = right_item.cleaned_data['rights']
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Retrait des droit %s" % ','.join(str(deleted_right) for deleted_right in right_del))
|
||||
reversion.set_comment("Retrait des droit %s" % ','.join(
|
||||
str(deleted_right) for deleted_right in right_del
|
||||
))
|
||||
right_del.delete()
|
||||
messages.success(request, "Droit retiré avec succès")
|
||||
return redirect("/users/")
|
||||
return form({'userform': user_right_list}, 'users/del_right.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('bofh')
|
||||
def add_ban(request, userid):
|
||||
""" Ajouter un banissement, nécessite au moins le droit bofh (a fortiori bureau)
|
||||
""" Ajouter un banissement, nécessite au moins le droit bofh
|
||||
(a fortiori bureau)
|
||||
Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement"""
|
||||
try:
|
||||
user = User.objects.get(pk=userid)
|
||||
|
@ -273,7 +313,7 @@ def add_ban(request, userid):
|
|||
ban = BanForm(request.POST or None, instance=ban_instance)
|
||||
if ban.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
ban_object = ban.save()
|
||||
_ban_object = ban.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Création")
|
||||
messages.success(request, "Bannissement ajouté")
|
||||
|
@ -285,10 +325,12 @@ def add_ban(request, userid):
|
|||
)
|
||||
return form({'userform': ban}, 'users/user.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('bofh')
|
||||
def edit_ban(request, banid):
|
||||
""" Editer un bannissement, nécessite au moins le droit bofh (a fortiori bureau)
|
||||
""" Editer un bannissement, nécessite au moins le droit bofh
|
||||
(a fortiori bureau)
|
||||
Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement"""
|
||||
try:
|
||||
ban_instance = Ban.objects.get(pk=banid)
|
||||
|
@ -300,23 +342,31 @@ def edit_ban(request, banid):
|
|||
with transaction.atomic(), reversion.create_revision():
|
||||
ban.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in ban.changed_data))
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in ban.changed_data
|
||||
))
|
||||
messages.success(request, "Bannissement modifié")
|
||||
return redirect("/users/")
|
||||
return form({'userform': ban}, 'users/user.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def add_whitelist(request, userid):
|
||||
""" Accorder un accès gracieux, temporaire ou permanent. Need droit cableur
|
||||
Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement, raison obligatoire"""
|
||||
""" Accorder un accès gracieux, temporaire ou permanent.
|
||||
Need droit cableur
|
||||
Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement,
|
||||
raison obligatoire"""
|
||||
try:
|
||||
user = User.objects.get(pk=userid)
|
||||
except User.DoesNotExist:
|
||||
messages.error(request, "Utilisateur inexistant")
|
||||
return redirect("/users/")
|
||||
whitelist_instance = Whitelist(user=user)
|
||||
whitelist = WhitelistForm(request.POST or None, instance=whitelist_instance)
|
||||
whitelist = WhitelistForm(
|
||||
request.POST or None,
|
||||
instance=whitelist_instance
|
||||
)
|
||||
if whitelist.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
whitelist.save()
|
||||
|
@ -331,30 +381,40 @@ def add_whitelist(request, userid):
|
|||
)
|
||||
return form({'userform': whitelist}, 'users/user.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def edit_whitelist(request, whitelistid):
|
||||
""" Editer un accès gracieux, temporaire ou permanent. Need droit cableur
|
||||
Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement, raison obligatoire"""
|
||||
""" Editer un accès gracieux, temporaire ou permanent.
|
||||
Need droit cableur
|
||||
Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement,
|
||||
raison obligatoire"""
|
||||
try:
|
||||
whitelist_instance = Whitelist.objects.get(pk=whitelistid)
|
||||
except Whitelist.DoesNotExist:
|
||||
messages.error(request, "Entrée inexistante")
|
||||
return redirect("/users/")
|
||||
whitelist = WhitelistForm(request.POST or None, instance=whitelist_instance)
|
||||
whitelist = WhitelistForm(
|
||||
request.POST or None,
|
||||
instance=whitelist_instance
|
||||
)
|
||||
if whitelist.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
whitelist.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in whitelist.changed_data))
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in whitelist.changed_data
|
||||
))
|
||||
messages.success(request, "Whitelist modifiée")
|
||||
return redirect("/users/")
|
||||
return form({'userform': whitelist}, 'users/user.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def add_school(request):
|
||||
""" Ajouter un établissement d'enseignement à la base de donnée, need cableur"""
|
||||
""" Ajouter un établissement d'enseignement à la base de donnée,
|
||||
need cableur"""
|
||||
school = SchoolForm(request.POST or None)
|
||||
if school.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
|
@ -365,10 +425,12 @@ def add_school(request):
|
|||
return redirect("/users/index_school/")
|
||||
return form({'userform': school}, 'users/user.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def edit_school(request, schoolid):
|
||||
""" Editer un établissement d'enseignement à partir du schoolid dans la base de donnée, need cableur"""
|
||||
""" Editer un établissement d'enseignement à partir du schoolid dans
|
||||
la base de donnée, need cableur"""
|
||||
try:
|
||||
school_instance = School.objects.get(pk=schoolid)
|
||||
except School.DoesNotExist:
|
||||
|
@ -379,16 +441,21 @@ def edit_school(request, schoolid):
|
|||
with transaction.atomic(), reversion.create_revision():
|
||||
school.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in school.changed_data))
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in school.changed_data
|
||||
))
|
||||
messages.success(request, "Établissement modifié")
|
||||
return redirect("/users/index_school/")
|
||||
return form({'userform': school}, 'users/user.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def del_school(request):
|
||||
""" Supprimer un établissement d'enseignement à la base de donnée, need cableur
|
||||
Objet protégé, possible seulement si aucun user n'est affecté à l'établissement """
|
||||
""" Supprimer un établissement d'enseignement à la base de donnée,
|
||||
need cableur
|
||||
Objet protégé, possible seulement si aucun user n'est affecté à
|
||||
l'établissement """
|
||||
school = DelSchoolForm(request.POST or None)
|
||||
if school.is_valid():
|
||||
school_dels = school.cleaned_data['schools']
|
||||
|
@ -406,6 +473,7 @@ def del_school(request):
|
|||
return redirect("/users/index_school/")
|
||||
return form({'userform': school}, 'users/user.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('bureau')
|
||||
def add_listright(request):
|
||||
|
@ -421,29 +489,38 @@ def add_listright(request):
|
|||
return redirect("/users/index_listright/")
|
||||
return form({'userform': listright}, 'users/user.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('bureau')
|
||||
def edit_listright(request, listrightid):
|
||||
""" Editer un groupe/droit, necessite droit bureau, à partir du listright id """
|
||||
""" Editer un groupe/droit, necessite droit bureau,
|
||||
à partir du listright id """
|
||||
try:
|
||||
listright_instance = ListRight.objects.get(pk=listrightid)
|
||||
except ListRight.DoesNotExist:
|
||||
messages.error(request, u"Entrée inexistante")
|
||||
return redirect("/users/")
|
||||
listright = ListRightForm(request.POST or None, instance=listright_instance)
|
||||
listright = ListRightForm(
|
||||
request.POST or None,
|
||||
instance=listright_instance
|
||||
)
|
||||
if listright.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
listright.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in listright.changed_data))
|
||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
|
||||
field for field in listright.changed_data
|
||||
))
|
||||
messages.success(request, "Droit modifié")
|
||||
return redirect("/users/index_listright/")
|
||||
return form({'userform': listright}, 'users/user.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('bureau')
|
||||
def del_listright(request):
|
||||
""" Supprimer un ou plusieurs groupe, possible si il est vide, need droit bureau """
|
||||
""" Supprimer un ou plusieurs groupe, possible si il est vide, need droit
|
||||
bureau """
|
||||
listright = DelListRightForm(request.POST or None)
|
||||
if listright.is_valid():
|
||||
listright_dels = listright.cleaned_data['listrights']
|
||||
|
@ -461,6 +538,7 @@ def del_listright(request):
|
|||
return redirect("/users/index_listright/")
|
||||
return form({'userform': listright}, 'users/user.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('bureau')
|
||||
def mass_archive(request):
|
||||
|
@ -469,7 +547,10 @@ def mass_archive(request):
|
|||
to_archive_list = []
|
||||
if to_archive_date.is_valid():
|
||||
date = to_archive_date.cleaned_data['date']
|
||||
to_archive_list = [user for user in User.objects.exclude(state=User.STATE_ARCHIVE) if not user.end_access() or user.end_access() < date]
|
||||
to_archive_list = [user for user in
|
||||
User.objects.exclude(state=User.STATE_ARCHIVE)
|
||||
if not user.end_access()
|
||||
or user.end_access() < date]
|
||||
if "valider" in request.POST:
|
||||
for user in to_archive_list:
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
|
@ -477,15 +558,22 @@ def mass_archive(request):
|
|||
user.save()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment("Archivage")
|
||||
messages.success(request, "%s users ont été archivés" % len(to_archive_list))
|
||||
messages.success(request, "%s users ont été archivés" % len(
|
||||
to_archive_list
|
||||
))
|
||||
return redirect("/users/")
|
||||
return form({'userform': to_archive_date, 'to_archive_list': to_archive_list}, 'users/mass_archive.html', request)
|
||||
return form(
|
||||
{'userform': to_archive_date, 'to_archive_list': to_archive_list},
|
||||
'users/mass_archive.html',
|
||||
request
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def index(request):
|
||||
""" Affiche l'ensemble des users, need droit cableur """
|
||||
options, created = GeneralOption.objects.get_or_create()
|
||||
options, _created = GeneralOption.objects.get_or_create()
|
||||
pagination_number = options.pagination_number
|
||||
users_list = User.objects.select_related('room').order_by('state', 'name')
|
||||
paginator = Paginator(users_list, pagination_number)
|
||||
|
@ -500,13 +588,15 @@ def index(request):
|
|||
users_list = paginator.page(paginator.num_pages)
|
||||
return render(request, 'users/index.html', {'users_list': users_list})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def index_ban(request):
|
||||
""" Affiche l'ensemble des ban, need droit cableur """
|
||||
options, created = GeneralOption.objects.get_or_create()
|
||||
options, _created = GeneralOption.objects.get_or_create()
|
||||
pagination_number = options.pagination_number
|
||||
ban_list = Ban.objects.order_by('date_start').select_related('user').reverse()
|
||||
ban_list = Ban.objects.order_by('date_start')\
|
||||
.select_related('user').reverse()
|
||||
paginator = Paginator(ban_list, pagination_number)
|
||||
page = request.GET.get('page')
|
||||
try:
|
||||
|
@ -519,13 +609,15 @@ def index_ban(request):
|
|||
ban_list = paginator.page(paginator.num_pages)
|
||||
return render(request, 'users/index_ban.html', {'ban_list': ban_list})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def index_white(request):
|
||||
""" Affiche l'ensemble des whitelist, need droit cableur """
|
||||
options, created = GeneralOption.objects.get_or_create()
|
||||
options, _created = GeneralOption.objects.get_or_create()
|
||||
pagination_number = options.pagination_number
|
||||
white_list = Whitelist.objects.select_related('user').order_by('date_start')
|
||||
white_list = Whitelist.objects.select_related('user')\
|
||||
.order_by('date_start')
|
||||
paginator = Paginator(white_list, pagination_number)
|
||||
page = request.GET.get('page')
|
||||
try:
|
||||
|
@ -542,84 +634,106 @@ def index_white(request):
|
|||
{'white_list': white_list}
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def index_school(request):
|
||||
""" Affiche l'ensemble des établissement, need droit cableur """
|
||||
school_list = School.objects.order_by('name')
|
||||
return render(request, 'users/index_schools.html', {'school_list':school_list})
|
||||
return render(
|
||||
request,
|
||||
'users/index_schools.html',
|
||||
{'school_list': school_list}
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def index_listright(request):
|
||||
""" Affiche l'ensemble des droits , need droit cableur """
|
||||
listright_list = ListRight.objects.order_by('listright')
|
||||
return render(request, 'users/index_listright.html', {'listright_list':listright_list})
|
||||
return render(
|
||||
request,
|
||||
'users/index_listright.html',
|
||||
{'listright_list': listright_list}
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('cableur')
|
||||
def index_serviceusers(request):
|
||||
""" Affiche les users de services (pour les accès ldap)"""
|
||||
serviceusers_list = ServiceUser.objects.order_by('pseudo')
|
||||
return render(request, 'users/index_serviceusers.html', {'serviceusers_list':serviceusers_list})
|
||||
return render(
|
||||
request,
|
||||
'users/index_serviceusers.html',
|
||||
{'serviceusers_list': serviceusers_list}
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def history(request, object, id):
|
||||
def history(request, object_name, object_id):
|
||||
""" Affichage de l'historique : (acl, argument)
|
||||
user : self or cableur, userid,
|
||||
ban : self or cableur, banid,
|
||||
whitelist : self or cableur, whitelistid,
|
||||
school : cableur, schoolid,
|
||||
listright : cableur, listrightid """
|
||||
if object == 'user':
|
||||
if object_name == 'user':
|
||||
try:
|
||||
object_instance = User.objects.get(pk=id)
|
||||
object_instance = User.objects.get(pk=object_id)
|
||||
except User.DoesNotExist:
|
||||
messages.error(request, "Utilisateur inexistant")
|
||||
return redirect("/users/")
|
||||
if not request.user.has_perms(('cableur',)) and object_instance != request.user:
|
||||
messages.error(request, "Vous ne pouvez pas afficher l'historique d'un autre user que vous sans droit cableur")
|
||||
if not request.user.has_perms(('cableur',)) and\
|
||||
object_instance != request.user:
|
||||
messages.error(request, "Vous ne pouvez pas afficher\
|
||||
l'historique d'un autre user que vous sans droit cableur")
|
||||
return redirect("/users/profil/" + str(request.user.id))
|
||||
elif object == 'serviceuser' and request.user.has_perms(('cableur',)):
|
||||
elif object_name == 'serviceuser' and request.user.has_perms(('cableur',)):
|
||||
try:
|
||||
object_instance = ServiceUser.objects.get(pk=id)
|
||||
object_instance = ServiceUser.objects.get(pk=object_id)
|
||||
except ServiceUser.DoesNotExist:
|
||||
messages.error(request, "User service inexistant")
|
||||
return redirect("/users/")
|
||||
elif object == 'ban':
|
||||
elif object_name == 'ban':
|
||||
try:
|
||||
object_instance = Ban.objects.get(pk=id)
|
||||
object_instance = Ban.objects.get(pk=object_id)
|
||||
except Ban.DoesNotExist:
|
||||
messages.error(request, "Bannissement inexistant")
|
||||
return redirect("/users/")
|
||||
if not request.user.has_perms(('cableur',)) and object_instance.user != request.user:
|
||||
messages.error(request, "Vous ne pouvez pas afficher les bans d'un autre user que vous sans droit cableur")
|
||||
if not request.user.has_perms(('cableur',)) and\
|
||||
object_instance.user != request.user:
|
||||
messages.error(request, "Vous ne pouvez pas afficher les bans\
|
||||
d'un autre user que vous sans droit cableur")
|
||||
return redirect("/users/profil/" + str(request.user.id))
|
||||
elif object == 'whitelist':
|
||||
elif object_name == 'whitelist':
|
||||
try:
|
||||
object_instance = Whitelist.objects.get(pk=id)
|
||||
except Whiltelist.DoesNotExist:
|
||||
object_instance = Whitelist.objects.get(pk=object_id)
|
||||
except Whitelist.DoesNotExist:
|
||||
messages.error(request, "Whitelist inexistant")
|
||||
return redirect("/users/")
|
||||
if not request.user.has_perms(('cableur',)) and object_instance.user != request.user:
|
||||
messages.error(request, "Vous ne pouvez pas afficher les whitelist d'un autre user que vous sans droit cableur")
|
||||
if not request.user.has_perms(('cableur',)) and\
|
||||
object_instance.user != request.user:
|
||||
messages.error(request, "Vous ne pouvez pas afficher les\
|
||||
whitelist d'un autre user que vous sans droit cableur")
|
||||
return redirect("/users/profil/" + str(request.user.id))
|
||||
elif object == 'school' and request.user.has_perms(('cableur',)):
|
||||
elif object_name == 'school' and request.user.has_perms(('cableur',)):
|
||||
try:
|
||||
object_instance = School.objects.get(pk=id)
|
||||
object_instance = School.objects.get(pk=object_id)
|
||||
except School.DoesNotExist:
|
||||
messages.error(request, "Ecole inexistante")
|
||||
return redirect("/users/")
|
||||
elif object == 'listright' and request.user.has_perms(('cableur',)):
|
||||
elif object_name == 'listright' and request.user.has_perms(('cableur',)):
|
||||
try:
|
||||
object_instance = ListRight.objects.get(pk=id)
|
||||
object_instance = ListRight.objects.get(pk=object_id)
|
||||
except ListRight.DoesNotExist:
|
||||
messages.error(request, "Droit inexistant")
|
||||
return redirect("/users/")
|
||||
else:
|
||||
messages.error(request, "Objet inconnu")
|
||||
return redirect("/users/")
|
||||
options, created = GeneralOption.objects.get_or_create()
|
||||
options, _created = GeneralOption.objects.get_or_create()
|
||||
pagination_number = options.pagination_number
|
||||
reversions = Version.objects.get_for_object(object_instance)
|
||||
paginator = Paginator(reversions, pagination_number)
|
||||
|
@ -632,7 +746,11 @@ def history(request, object, id):
|
|||
except EmptyPage:
|
||||
# If page is out of range (e.g. 9999), deliver last page of results.
|
||||
reversions = paginator.page(paginator.num_pages)
|
||||
return render(request, 're2o/history.html', {'reversions': reversions, 'object': object_instance})
|
||||
return render(
|
||||
request,
|
||||
're2o/history.html',
|
||||
{'reversions': reversions, 'object': object_instance}
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -640,6 +758,7 @@ def mon_profil(request):
|
|||
""" Lien vers profil, renvoie request.id à la fonction """
|
||||
return redirect("/users/profil/" + str(request.user.id))
|
||||
|
||||
|
||||
@login_required
|
||||
def profil(request, userid):
|
||||
""" Affiche un profil, self or cableur, prend un userid en argument """
|
||||
|
@ -649,14 +768,19 @@ def profil(request, userid):
|
|||
messages.error(request, "Utilisateur inexistant")
|
||||
return redirect("/users/")
|
||||
if not request.user.has_perms(('cableur',)) and users != request.user:
|
||||
messages.error(request, "Vous ne pouvez pas afficher un autre user que vous sans droit cableur")
|
||||
messages.error(request, "Vous ne pouvez pas afficher un autre user\
|
||||
que vous sans droit cableur")
|
||||
return redirect("/users/profil/" + str(request.user.id))
|
||||
machines = Machine.objects.filter(user__pseudo=users).select_related('user').prefetch_related('interface_set__domain__extension').prefetch_related('interface_set__ipv4__ip_type__extension').prefetch_related('interface_set__type').prefetch_related('interface_set__domain__related_domain__extension')
|
||||
factures = Facture.objects.filter(user__pseudo=users)
|
||||
bans = Ban.objects.filter(user__pseudo=users)
|
||||
whitelists = Whitelist.objects.filter(user__pseudo=users)
|
||||
machines = Machine.objects.filter(user=users).select_related('user')\
|
||||
.prefetch_related('interface_set__domain__extension')\
|
||||
.prefetch_related('interface_set__ipv4__ip_type__extension')\
|
||||
.prefetch_related('interface_set__type')\
|
||||
.prefetch_related('interface_set__domain__related_domain__extension')
|
||||
factures = Facture.objects.filter(user=users)
|
||||
bans = Ban.objects.filter(user=users)
|
||||
whitelists = Whitelist.objects.filter(user=users)
|
||||
list_droits = Right.objects.filter(user=users)
|
||||
options, created = OptionalUser.objects.get_or_create()
|
||||
options, _created = OptionalUser.objects.get_or_create()
|
||||
user_solde = options.user_solde
|
||||
return render(
|
||||
request,
|
||||
|
@ -672,46 +796,56 @@ def profil(request, userid):
|
|||
}
|
||||
)
|
||||
|
||||
|
||||
def reset_password(request):
|
||||
""" Reintialisation du mot de passe si mdp oublié """
|
||||
userform = ResetPasswordForm(request.POST or None)
|
||||
if userform.is_valid():
|
||||
try:
|
||||
user = User.objects.get(pseudo=userform.cleaned_data['pseudo'],email=userform.cleaned_data['email'])
|
||||
user = User.objects.get(
|
||||
pseudo=userform.cleaned_data['pseudo'],
|
||||
email=userform.cleaned_data['email']
|
||||
)
|
||||
except User.DoesNotExist:
|
||||
messages.error(request, "Cet utilisateur n'existe pas")
|
||||
return form({'userform': userform}, 'users/user.html', request)
|
||||
user.reset_passwd_mail(request)
|
||||
messages.success(request, "Un mail pour l'initialisation du mot de passe a été envoyé")
|
||||
messages.success(request, "Un mail pour l'initialisation du mot\
|
||||
de passe a été envoyé")
|
||||
redirect("/")
|
||||
return form({'userform': userform}, 'users/user.html', request)
|
||||
|
||||
|
||||
def process(request, token):
|
||||
"""Process, lien pour la reinitialisation du mot de passe"""
|
||||
valid_reqs = Request.objects.filter(expires_at__gt=timezone.now())
|
||||
req = get_object_or_404(valid_reqs, token=token)
|
||||
|
||||
if req.type == Request.PASSWD:
|
||||
return process_passwd(request, req)
|
||||
elif req.type == Request.EMAIL:
|
||||
return process_email(request, req=req)
|
||||
else:
|
||||
messages.error(request, "Entrée incorrecte, contactez un admin")
|
||||
redirect("/")
|
||||
|
||||
|
||||
def process_passwd(request, req):
|
||||
"""Process le changeemnt de mot de passe, renvoie le formulaire
|
||||
demandant le nouveau password"""
|
||||
u_form = PassForm(request.POST or None)
|
||||
user = req.user
|
||||
if u_form.is_valid():
|
||||
return password_change_action(u_form, user, request, req=req)
|
||||
return form({'userform': u_form}, 'users/user.html', request)
|
||||
""" Framework Rest """
|
||||
|
||||
|
||||
class JSONResponse(HttpResponse):
|
||||
""" Framework Rest """
|
||||
def __init__(self, data, **kwargs):
|
||||
content = JSONRenderer().render(data)
|
||||
kwargs['content_type'] = 'application/json'
|
||||
super(JSONResponse, self).__init__(content, **kwargs)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@login_required
|
||||
@permission_required('serveur')
|
||||
|
@ -721,4 +855,3 @@ def mailing(request):
|
|||
mails = all_has_access().values('email').distinct()
|
||||
seria = MailSerializer(mails, many=True)
|
||||
return JSONResponse(seria.data)
|
||||
|
||||
|
|
Loading…
Reference in a new issue