mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-26 06:32:26 +00:00
Merge branch master into granuban
This commit is contained in:
commit
23cd06d589
126 changed files with 9400 additions and 2061 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,3 +4,4 @@ settings_local.py
|
||||||
re2o.png
|
re2o.png
|
||||||
__pycache__/*
|
__pycache__/*
|
||||||
static_files/*
|
static_files/*
|
||||||
|
static/logo/*
|
||||||
|
|
|
@ -28,23 +28,37 @@ from reversion.admin import VersionAdmin
|
||||||
|
|
||||||
from .models import Facture, Article, Banque, Paiement, Cotisation, Vente
|
from .models import Facture, Article, Banque, Paiement, Cotisation, Vente
|
||||||
|
|
||||||
|
|
||||||
class FactureAdmin(VersionAdmin):
|
class FactureAdmin(VersionAdmin):
|
||||||
list_display = ('user','paiement','date','valid','control')
|
"""Class admin d'une facture, tous les champs"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class VenteAdmin(VersionAdmin):
|
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):
|
class ArticleAdmin(VersionAdmin):
|
||||||
list_display = ('name','prix','iscotisation','duration')
|
"""Class admin d'un article en vente"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BanqueAdmin(VersionAdmin):
|
class BanqueAdmin(VersionAdmin):
|
||||||
list_display = ('name',)
|
"""Class admin de la liste des banques (facture related)"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PaiementAdmin(VersionAdmin):
|
class PaiementAdmin(VersionAdmin):
|
||||||
list_display = ('moyen','type_paiement')
|
"""Class admin d'un moyen de paiement (facture related"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CotisationAdmin(VersionAdmin):
|
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(Facture, FactureAdmin)
|
||||||
admin.site.register(Article, ArticleAdmin)
|
admin.site.register(Article, ArticleAdmin)
|
||||||
|
|
|
@ -19,74 +19,140 @@
|
||||||
# You should have received a copy of the GNU General Public License along
|
# 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.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 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 __future__ import unicode_literals
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.db.models import Q
|
||||||
from django.forms import ModelForm, Form
|
from django.forms import ModelForm, Form
|
||||||
from django import forms
|
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
from .models import Article, Paiement, Facture, Banque, Vente
|
from .models import Article, Paiement, Facture, Banque
|
||||||
|
|
||||||
|
|
||||||
class NewFactureForm(ModelForm):
|
class NewFactureForm(ModelForm):
|
||||||
|
"""Creation d'une facture, moyen de paiement, banque et numero
|
||||||
|
de cheque"""
|
||||||
def __init__(self, *args, **kwargs):
|
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['cheque'].required = False
|
||||||
self.fields['banque'].required = False
|
self.fields['banque'].required = False
|
||||||
self.fields['cheque'].label = 'Numero de chèque'
|
self.fields['cheque'].label = 'Numero de chèque'
|
||||||
self.fields['banque'].empty_label = "Non renseigné"
|
self.fields['banque'].empty_label = "Non renseigné"
|
||||||
self.fields['paiement'].empty_label = "Séléctionner un moyen de paiement"
|
self.fields['paiement'].empty_label = "Séléctionner\
|
||||||
self.fields['paiement'].widget.attrs['data-cheque'] = Paiement.objects.filter(type_paiement=1).first().id
|
un moyen de paiement"
|
||||||
|
self.fields['paiement'].widget.attrs['data-cheque'] = Paiement.objects\
|
||||||
|
.filter(type_paiement=1).first().id
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Facture
|
model = Facture
|
||||||
fields = ['paiement','banque','cheque']
|
fields = ['paiement', 'banque', 'cheque']
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data=super(NewFactureForm, self).clean()
|
cleaned_data = super(NewFactureForm, self).clean()
|
||||||
paiement = cleaned_data.get("paiement")
|
paiement = cleaned_data.get("paiement")
|
||||||
cheque = cleaned_data.get("cheque")
|
cheque = cleaned_data.get("cheque")
|
||||||
banque = cleaned_data.get("banque")
|
banque = cleaned_data.get("banque")
|
||||||
if not paiement:
|
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):
|
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
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
class CreditSoldeForm(NewFactureForm):
|
class CreditSoldeForm(NewFactureForm):
|
||||||
|
"""Permet de faire des opérations sur le solde si il est activé"""
|
||||||
class Meta(NewFactureForm.Meta):
|
class Meta(NewFactureForm.Meta):
|
||||||
model = Facture
|
model = Facture
|
||||||
fields = ['paiement','banque','cheque']
|
fields = ['paiement', 'banque', 'cheque']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(CreditSoldeForm, self).__init__(*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)
|
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)
|
class SelectUserArticleForm(Form):
|
||||||
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.filter(Q(type_user='All') | Q(type_user='Adherent')),
|
||||||
|
label="Article",
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
quantity = forms.IntegerField(
|
||||||
|
label="Quantité",
|
||||||
|
validators=[MinValueValidator(1)],
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SelectClubArticleForm(Form):
|
||||||
|
"""Selection d'un article lors de la creation d'une facture"""
|
||||||
|
article = forms.ModelChoiceField(
|
||||||
|
queryset=Article.objects.filter(Q(type_user='All') | Q(type_user='Club')),
|
||||||
|
label="Article",
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
quantity = forms.IntegerField(
|
||||||
|
label="Quantité",
|
||||||
|
validators=[MinValueValidator(1)],
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NewFactureFormPdf(Form):
|
class NewFactureFormPdf(Form):
|
||||||
article = forms.ModelMultipleChoiceField(queryset=Article.objects.all(), label="Article")
|
"""Creation d'un pdf facture par le trésorier"""
|
||||||
number = forms.IntegerField(label="Quantité", validators=[MinValueValidator(1)])
|
article = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=Article.objects.all(),
|
||||||
|
label="Article"
|
||||||
|
)
|
||||||
|
number = forms.IntegerField(
|
||||||
|
label="Quantité",
|
||||||
|
validators=[MinValueValidator(1)]
|
||||||
|
)
|
||||||
paid = forms.BooleanField(label="Payé", required=False)
|
paid = forms.BooleanField(label="Payé", required=False)
|
||||||
dest = forms.CharField(required=True, max_length=255, label="Destinataire")
|
dest = forms.CharField(required=True, max_length=255, label="Destinataire")
|
||||||
chambre = forms.CharField(required=False, max_length=10, label="Adresse")
|
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):
|
class EditFactureForm(NewFactureForm):
|
||||||
|
"""Edition d'une facture : moyen de paiement, banque, user parent"""
|
||||||
class Meta(NewFactureForm.Meta):
|
class Meta(NewFactureForm.Meta):
|
||||||
fields = ['paiement','banque','cheque','user']
|
fields = ['paiement', 'banque', 'cheque', 'user']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(EditFactureForm, self).__init__(*args, **kwargs)
|
super(EditFactureForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['user'].label = 'Adherent'
|
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):
|
class TrezEditFactureForm(EditFactureForm):
|
||||||
|
"""Vue pour édition controle trésorier"""
|
||||||
class Meta(EditFactureForm.Meta):
|
class Meta(EditFactureForm.Meta):
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
@ -97,38 +163,67 @@ class TrezEditFactureForm(EditFactureForm):
|
||||||
|
|
||||||
|
|
||||||
class ArticleForm(ModelForm):
|
class ArticleForm(ModelForm):
|
||||||
|
"""Creation d'un article. Champs : nom, cotisation, durée"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Article
|
model = Article
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
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"
|
self.fields['name'].label = "Désignation de l'article"
|
||||||
|
|
||||||
|
|
||||||
class DelArticleForm(Form):
|
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):
|
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:
|
class Meta:
|
||||||
model = Paiement
|
model = Paiement
|
||||||
fields = ['moyen', 'type_paiement']
|
fields = ['moyen', 'type_paiement']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
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['moyen'].label = 'Moyen de paiement à ajouter'
|
||||||
self.fields['type_paiement'].label = 'Type de paiement à ajouter'
|
self.fields['type_paiement'].label = 'Type de paiement à ajouter'
|
||||||
|
|
||||||
|
|
||||||
class DelPaiementForm(Form):
|
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):
|
class BanqueForm(ModelForm):
|
||||||
|
"""Creation d'une banque, field name"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Banque
|
model = Banque
|
||||||
fields = ['name']
|
fields = ['name']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
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'
|
self.fields['name'].label = 'Banque à ajouter'
|
||||||
|
|
||||||
|
|
||||||
class DelBanqueForm(Form):
|
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
|
||||||
|
)
|
||||||
|
|
26
cotisations/migrations/0024_auto_20171015_2033.py
Normal file
26
cotisations/migrations/0024_auto_20171015_2033.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-10-15 18:33
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cotisations', '0023_auto_20170902_1303'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='duration',
|
||||||
|
field=models.PositiveIntegerField(blank=True, help_text='Durée exprimée en mois entiers', null=True, validators=[django.core.validators.MinValueValidator(0)]),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='vente',
|
||||||
|
name='duration',
|
||||||
|
field=models.PositiveIntegerField(blank=True, help_text='Durée exprimée en mois entiers', null=True),
|
||||||
|
),
|
||||||
|
]
|
20
cotisations/migrations/0025_article_type_user.py
Normal file
20
cotisations/migrations/0025_article_type_user.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-10-27 03:02
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cotisations', '0024_auto_20171015_2033'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='article',
|
||||||
|
name='type_user',
|
||||||
|
field=models.CharField(choices=[('Adherent', 'Adherent'), ('Club', 'Club'), ('All', 'All')], default='All', max_length=255),
|
||||||
|
),
|
||||||
|
]
|
78
cotisations/migrations/0026_auto_20171028_0126.py
Normal file
78
cotisations/migrations/0026_auto_20171028_0126.py
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-10-27 23:26
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def create_type(apps, schema_editor):
|
||||||
|
Cotisation = apps.get_model('cotisations', 'Cotisation')
|
||||||
|
Vente = apps.get_model('cotisations', 'Vente')
|
||||||
|
Article = apps.get_model('cotisations', 'Article')
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
articles = Article.objects.using(db_alias).all()
|
||||||
|
ventes = Vente.objects.using(db_alias).all()
|
||||||
|
cotisations = Cotisation.objects.using(db_alias).all()
|
||||||
|
for article in articles:
|
||||||
|
if article.iscotisation:
|
||||||
|
article.type_cotisation='All'
|
||||||
|
article.save(using=db_alias)
|
||||||
|
for vente in ventes:
|
||||||
|
if vente.iscotisation:
|
||||||
|
vente.type_cotisation='All'
|
||||||
|
vente.save(using=db_alias)
|
||||||
|
for cotisation in cotisations:
|
||||||
|
cotisation.type_cotisation='All'
|
||||||
|
cotisation.save(using=db_alias)
|
||||||
|
|
||||||
|
def delete_type(apps, schema_editor):
|
||||||
|
Vente = apps.get_model('cotisations', 'Vente')
|
||||||
|
Article = apps.get_model('cotisations', 'Article')
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
articles = Articles.objects.using(db_alias).all()
|
||||||
|
ventes = Vente.objects.using(db_alias).all()
|
||||||
|
for article in articles:
|
||||||
|
if article.type_cotisation:
|
||||||
|
article.iscotisation=True
|
||||||
|
else:
|
||||||
|
article.iscotisation=False
|
||||||
|
article.save(using=db_alias)
|
||||||
|
for vente in ventes:
|
||||||
|
if vente.iscotisation:
|
||||||
|
vente.iscotisation=True
|
||||||
|
else:
|
||||||
|
vente.iscotisation=False
|
||||||
|
vente.save(using=db_alias)
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cotisations', '0025_article_type_user'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='article',
|
||||||
|
name='type_cotisation',
|
||||||
|
field=models.CharField(blank=True, choices=[('Connexion', 'Connexion'), ('Adhesion', 'Adhesion'), ('All', 'All')], default=None, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='cotisation',
|
||||||
|
name='type_cotisation',
|
||||||
|
field=models.CharField(choices=[('Connexion', 'Connexion'), ('Adhesion', 'Adhesion'), ('All', 'All')], max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vente',
|
||||||
|
name='type_cotisation',
|
||||||
|
field=models.CharField(blank=True, choices=[('Connexion', 'Connexion'), ('Adhesion', 'Adhesion'), ('All', 'All')], max_length=255, null=True),
|
||||||
|
),
|
||||||
|
migrations.RunPython(create_type, delete_type),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='article',
|
||||||
|
name='iscotisation',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='vente',
|
||||||
|
name='iscotisation',
|
||||||
|
),
|
||||||
|
]
|
20
cotisations/migrations/0027_auto_20171029_1156.py
Normal file
20
cotisations/migrations/0027_auto_20171029_1156.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-10-29 10:56
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cotisations', '0026_auto_20171028_0126'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=255),
|
||||||
|
),
|
||||||
|
]
|
|
@ -20,141 +20,269 @@
|
||||||
# You should have received a copy of the GNU General Public License along
|
# 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.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 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 __future__ import unicode_literals
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
from django.db.models.signals import post_save, post_delete
|
from django.db.models.signals import post_save, post_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from dateutil.relativedelta import relativedelta
|
|
||||||
from django.forms import ValidationError
|
from django.forms import ValidationError
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
|
|
||||||
from django.db.models import Max
|
from django.db.models import Max
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from machines.models import regen
|
from machines.models import regen
|
||||||
|
|
||||||
|
|
||||||
class Facture(models.Model):
|
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"
|
PRETTY_NAME = "Factures émises"
|
||||||
|
|
||||||
user = models.ForeignKey('users.User', on_delete=models.PROTECT)
|
user = models.ForeignKey('users.User', on_delete=models.PROTECT)
|
||||||
paiement = models.ForeignKey('Paiement', 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)
|
cheque = models.CharField(max_length=255, blank=True)
|
||||||
date = models.DateTimeField(auto_now_add=True)
|
date = models.DateTimeField(auto_now_add=True)
|
||||||
valid = models.BooleanField(default=True)
|
valid = models.BooleanField(default=True)
|
||||||
control = models.BooleanField(default=False)
|
control = models.BooleanField(default=False)
|
||||||
|
|
||||||
def prix(self):
|
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
|
return prix
|
||||||
|
|
||||||
def prix_total(self):
|
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):
|
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
|
return name
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.user) + ' ' + str(self.date)
|
return str(self.user) + ' ' + str(self.date)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Facture)
|
@receiver(post_save, sender=Facture)
|
||||||
def facture_post_save(sender, **kwargs):
|
def facture_post_save(sender, **kwargs):
|
||||||
|
"""Post save d'une facture, synchronise l'user ldap"""
|
||||||
facture = kwargs['instance']
|
facture = kwargs['instance']
|
||||||
user = facture.user
|
user = facture.user
|
||||||
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Facture)
|
@receiver(post_delete, sender=Facture)
|
||||||
def facture_post_delete(sender, **kwargs):
|
def facture_post_delete(sender, **kwargs):
|
||||||
|
"""Après la suppression d'une facture, on synchronise l'user ldap"""
|
||||||
user = kwargs['instance'].user
|
user = kwargs['instance'].user
|
||||||
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
||||||
|
|
||||||
|
|
||||||
class Vente(models.Model):
|
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"
|
PRETTY_NAME = "Ventes effectuées"
|
||||||
|
|
||||||
|
COTISATION_TYPE = (
|
||||||
|
('Connexion', 'Connexion'),
|
||||||
|
('Adhesion', 'Adhesion'),
|
||||||
|
('All', 'All'),
|
||||||
|
)
|
||||||
|
|
||||||
facture = models.ForeignKey('Facture', on_delete=models.CASCADE)
|
facture = models.ForeignKey('Facture', on_delete=models.CASCADE)
|
||||||
number = models.IntegerField(validators=[MinValueValidator(1)])
|
number = models.IntegerField(validators=[MinValueValidator(1)])
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
prix = models.DecimalField(max_digits=5, decimal_places=2)
|
prix = models.DecimalField(max_digits=5, decimal_places=2)
|
||||||
iscotisation = models.BooleanField()
|
duration = models.PositiveIntegerField(
|
||||||
duration = models.IntegerField(help_text="Durée exprimée en mois entiers", blank=True, null=True)
|
help_text="Durée exprimée en mois entiers",
|
||||||
|
blank=True,
|
||||||
|
null=True)
|
||||||
|
type_cotisation = models.CharField(
|
||||||
|
choices=COTISATION_TYPE,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
max_length=255
|
||||||
|
)
|
||||||
|
|
||||||
def prix_total(self):
|
def prix_total(self):
|
||||||
|
"""Renvoie le prix_total de self (nombre*prix)"""
|
||||||
return self.prix*self.number
|
return self.prix*self.number
|
||||||
|
|
||||||
def update_cotisation(self):
|
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'):
|
if hasattr(self, 'cotisation'):
|
||||||
cotisation = 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
|
return
|
||||||
|
|
||||||
def create_cotis(self, date_start=False):
|
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
|
||||||
if not hasattr(self, 'cotisation'):
|
en argument l'user, la facture pour la quantitéi, et l'article pour
|
||||||
cotisation=Cotisation(vente=self)
|
la durée"""
|
||||||
|
if not hasattr(self, 'cotisation') and self.type_cotisation:
|
||||||
|
cotisation = Cotisation(vente=self)
|
||||||
|
cotisation.type_cotisation = self.type_cotisation
|
||||||
if date_start:
|
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_cotisation = Cotisation.objects.filter(
|
||||||
|
vente__in=Vente.objects.filter(
|
||||||
|
facture__in=Facture.objects.filter(
|
||||||
|
user=self.facture.user
|
||||||
|
).exclude(valid=False))
|
||||||
|
).filter(Q(type_cotisation='All') | Q(type_cotisation=self.type_cotisation)
|
||||||
|
).filter(
|
||||||
|
date_start__lt=date_start
|
||||||
|
).aggregate(Max('date_end'))['date_end__max']
|
||||||
|
elif self.type_cotisation=="Adhesion":
|
||||||
|
end_cotisation = self.facture.user.end_adhesion()
|
||||||
else:
|
else:
|
||||||
end_adhesion = self.facture.user.end_adhesion()
|
end_cotisation = self.facture.user.end_connexion()
|
||||||
date_start = date_start or timezone.now()
|
date_start = date_start or timezone.now()
|
||||||
end_adhesion = end_adhesion or date_start
|
end_cotisation = end_cotisation or date_start
|
||||||
date_max = max(end_adhesion, date_start)
|
date_max = max(end_cotisation, date_start)
|
||||||
cotisation.date_start = date_max
|
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
|
return
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
# On verifie que si iscotisation, duration est présent
|
# On verifie que si iscotisation, duration est présent
|
||||||
if self.iscotisation and not self.duration:
|
if self.type_cotisation 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()
|
self.update_cotisation()
|
||||||
super(Vente, self).save(*args, **kwargs)
|
super(Vente, self).save(*args, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.name) + ' ' + str(self.facture)
|
return str(self.name) + ' ' + str(self.facture)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Vente)
|
@receiver(post_save, sender=Vente)
|
||||||
def vente_post_save(sender, **kwargs):
|
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']
|
vente = kwargs['instance']
|
||||||
if hasattr(vente, 'cotisation'):
|
if hasattr(vente, 'cotisation'):
|
||||||
vente.cotisation.vente = vente
|
vente.cotisation.vente = vente
|
||||||
vente.cotisation.save()
|
vente.cotisation.save()
|
||||||
if vente.iscotisation:
|
if vente.type_cotisation:
|
||||||
vente.create_cotis()
|
vente.create_cotis()
|
||||||
vente.cotisation.save()
|
vente.cotisation.save()
|
||||||
user = vente.facture.user
|
user = vente.facture.user
|
||||||
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Vente)
|
@receiver(post_delete, sender=Vente)
|
||||||
def vente_post_delete(sender, **kwargs):
|
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']
|
vente = kwargs['instance']
|
||||||
if vente.iscotisation:
|
if vente.type_cotisation:
|
||||||
user = vente.facture.user
|
user = vente.facture.user
|
||||||
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
||||||
|
|
||||||
|
|
||||||
class Article(models.Model):
|
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"
|
PRETTY_NAME = "Articles en vente"
|
||||||
|
|
||||||
name = models.CharField(max_length=255, unique=True)
|
USER_TYPES = (
|
||||||
|
('Adherent', 'Adherent'),
|
||||||
|
('Club', 'Club'),
|
||||||
|
('All', 'All'),
|
||||||
|
)
|
||||||
|
|
||||||
|
COTISATION_TYPE = (
|
||||||
|
('Connexion', 'Connexion'),
|
||||||
|
('Adhesion', 'Adhesion'),
|
||||||
|
('All', 'All'),
|
||||||
|
)
|
||||||
|
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
prix = models.DecimalField(max_digits=5, decimal_places=2)
|
prix = models.DecimalField(max_digits=5, decimal_places=2)
|
||||||
iscotisation = models.BooleanField()
|
duration = models.PositiveIntegerField(
|
||||||
duration = models.IntegerField(
|
|
||||||
help_text="Durée exprimée en mois entiers",
|
help_text="Durée exprimée en mois entiers",
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[MinValueValidator(0)])
|
validators=[MinValueValidator(0)])
|
||||||
|
type_user = models.CharField(
|
||||||
|
choices=USER_TYPES,
|
||||||
|
default='All',
|
||||||
|
max_length=255
|
||||||
|
)
|
||||||
|
type_cotisation = models.CharField(
|
||||||
|
choices=COTISATION_TYPE,
|
||||||
|
default=None,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
max_length=255
|
||||||
|
)
|
||||||
|
|
||||||
|
unique_together = ('name', 'type_user')
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.name.lower() == "solde":
|
if self.name.lower() == "solde":
|
||||||
raise ValidationError("Solde est un nom d'article invalide")
|
raise ValidationError("Solde est un nom d'article invalide")
|
||||||
|
if self.type_cotisation and not self.duration:
|
||||||
|
raise ValidationError(
|
||||||
|
"La durée est obligatoire si il s'agit d'une cotisation"
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Banque(models.Model):
|
class Banque(models.Model):
|
||||||
|
"""Liste des banques"""
|
||||||
PRETTY_NAME = "Banques enregistrées"
|
PRETTY_NAME = "Banques enregistrées"
|
||||||
|
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
|
@ -162,7 +290,9 @@ class Banque(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Paiement(models.Model):
|
class Paiement(models.Model):
|
||||||
|
"""Moyens de paiement"""
|
||||||
PRETTY_NAME = "Moyens de paiement"
|
PRETTY_NAME = "Moyens de paiement"
|
||||||
PAYMENT_TYPES = (
|
PAYMENT_TYPES = (
|
||||||
(0, 'Autre'),
|
(0, 'Autre'),
|
||||||
|
@ -179,29 +309,47 @@ class Paiement(models.Model):
|
||||||
self.moyen = self.moyen.title()
|
self.moyen = self.moyen.title()
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
"""Un seul type de paiement peut-etre cheque..."""
|
||||||
if Paiement.objects.filter(type_paiement=1).count() > 1:
|
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)
|
super(Paiement, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Cotisation(models.Model):
|
class Cotisation(models.Model):
|
||||||
|
"""Objet cotisation, debut et fin, relié en onetoone à une vente"""
|
||||||
PRETTY_NAME = "Cotisations"
|
PRETTY_NAME = "Cotisations"
|
||||||
|
|
||||||
|
COTISATION_TYPE = (
|
||||||
|
('Connexion', 'Connexion'),
|
||||||
|
('Adhesion', 'Adhesion'),
|
||||||
|
('All', 'All'),
|
||||||
|
)
|
||||||
|
|
||||||
vente = models.OneToOneField('Vente', on_delete=models.CASCADE, null=True)
|
vente = models.OneToOneField('Vente', on_delete=models.CASCADE, null=True)
|
||||||
|
type_cotisation = models.CharField(
|
||||||
|
choices=COTISATION_TYPE,
|
||||||
|
max_length=255,
|
||||||
|
)
|
||||||
date_start = models.DateTimeField()
|
date_start = models.DateTimeField()
|
||||||
date_end = models.DateTimeField()
|
date_end = models.DateTimeField()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.vente)
|
return str(self.vente)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Cotisation)
|
@receiver(post_save, sender=Cotisation)
|
||||||
def cotisation_post_save(sender, **kwargs):
|
def cotisation_post_save(sender, **kwargs):
|
||||||
|
"""Après modification d'une cotisation, regeneration des services"""
|
||||||
regen('dns')
|
regen('dns')
|
||||||
regen('dhcp')
|
regen('dhcp')
|
||||||
regen('mac_ip_list')
|
regen('mac_ip_list')
|
||||||
regen('mailing')
|
regen('mailing')
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Cotisation)
|
@receiver(post_delete, sender=Cotisation)
|
||||||
def vente_post_delete(sender, **kwargs):
|
def vente_post_delete(sender, **kwargs):
|
||||||
|
"""Après suppression d'une vente, régénération des services"""
|
||||||
cotisation = kwargs['instance']
|
cotisation = kwargs['instance']
|
||||||
regen('mac_ip_list')
|
regen('mac_ip_list')
|
||||||
regen('mailing')
|
regen('mailing')
|
||||||
|
|
|
@ -27,8 +27,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<th>Article</th>
|
<th>Article</th>
|
||||||
<th>Prix</th>
|
<th>Prix</th>
|
||||||
<th>Cotisation</th>
|
<th>Type Cotisation</th>
|
||||||
<th>Durée (mois)</th>
|
<th>Durée (mois)</th>
|
||||||
|
<th>Article pour</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -36,8 +37,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ article.name }}</td>
|
<td>{{ article.name }}</td>
|
||||||
<td>{{ article.prix }}</td>
|
<td>{{ article.prix }}</td>
|
||||||
<td>{{ article.iscotisation }}</td>
|
<td>{{ article.type_cotisation }}</td>
|
||||||
<td>{{ article.duration }}</td>
|
<td>{{ article.duration }}</td>
|
||||||
|
<td>{{ article.type_user }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{% if is_trez %}
|
{% if is_trez %}
|
||||||
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'cotisations:edit-article' article.id %}">
|
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'cotisations:edit-article' article.id %}">
|
||||||
|
|
|
@ -29,12 +29,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Utilisateur</th>
|
<th>{% include "buttons/sort.html" with prefix='cotis' col='user' text='Utilisateur' %}</th>
|
||||||
<th>Designation</th>
|
<th>Designation</th>
|
||||||
<th>Prix total</th>
|
<th>Prix total</th>
|
||||||
<th>Moyen de paiement</th>
|
<th>{% include "buttons/sort.html" with prefix='cotis' col='paiement' text='Moyen de paiement' %}</th>
|
||||||
<th>Date</th>
|
<th>{% include "buttons/sort.html" with prefix='cotis' col='date' text='Date' %}</th>
|
||||||
<th></th>
|
<th>{% include "buttons/sort.html" with prefix='cotis' col='id' text='Id facture' %}</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -46,17 +46,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<td>{{ facture.prix_total }}</td>
|
<td>{{ facture.prix_total }}</td>
|
||||||
<td>{{ facture.paiement }}</td>
|
<td>{{ facture.paiement }}</td>
|
||||||
<td>{{ facture.date }}</td>
|
<td>{{ facture.date }}</td>
|
||||||
|
<td>{{ facture.id }}</td>
|
||||||
{% if is_cableur %}
|
{% if is_cableur %}
|
||||||
<td>
|
<td>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button class="btn btn-default dropdown-toggle" type="button" id="editionfacture" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
<button class="btn btn-default dropdown-toggle" type="button" id="editionfacture" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||||
Modifier
|
Edition
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" aria-labelledby="editionfacture">
|
<ul class="dropdown-menu" aria-labelledby="editionfacture">
|
||||||
{% if facture.valid and not facture.control or is_trez %}
|
{% if facture.valid and not facture.control or is_trez %}
|
||||||
<li><a href="{% url 'cotisations:edit-facture' facture.id %}"><i class="glyphicon glyphicon-bitcoin"></i> Editer</a></li>
|
<li><a href="{% url 'cotisations:edit-facture' facture.id %}"><i class="glyphicon glyphicon-bitcoin"></i> Modifier</a></li>
|
||||||
<li><a href="{% url 'cotisations:del-facture' facture.id %}"><i class="glyphicon glyphicon-trash"></i> Supprimer</a></li>
|
<li><a href="{% url 'cotisations:del-facture' facture.id %}"><i class="glyphicon glyphicon-trash"></i> Supprimer</a></li>
|
||||||
|
<li><a href="{% url 'cotisations:history' 'facture' facture.id %}"><i class="glyphicon glyphicon-time"></i> Historique</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li>Facture controlée</li>
|
<li>Facture controlée</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -74,11 +76,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<font color="red">Facture invalide</font>
|
<font color="red">Facture invalide</font>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
|
||||||
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'cotisations:history' 'facture' facture.id %}">
|
|
||||||
<i class="glyphicon glyphicon-time"></i>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -40,14 +40,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Profil</th>
|
<th>Profil</th>
|
||||||
<th>Nom</th>
|
<th>{% include "buttons/sort.html" with prefix='control' col='name' text='Nom' %}</th>
|
||||||
<th>Prénom</th>
|
<th>{% include "buttons/sort.html" with prefix='control' col='surname' text='Prénom' %}</th>
|
||||||
<th>Designation</th>
|
<th>{% include "buttons/sort.html" with prefix='control' col='id' text='Id facture' %}</th>
|
||||||
|
<th>{% include "buttons/sort.html" with prefix='control' col='user-id' text='Id user' %}</th>
|
||||||
|
<th>Designation</th>
|
||||||
<th>Prix total</th>
|
<th>Prix total</th>
|
||||||
<th>Moyen de paiement</th>
|
<th>{% include "buttons/sort.html" with prefix='control' col='paiement' text='Moyen de paiement' %}</th>
|
||||||
<th>Date</th>
|
<th>{% include "buttons/sort.html" with prefix='control' col='date' text='Date' %}</th>
|
||||||
<th>Validité</th>
|
<th>{% include "buttons/sort.html" with prefix='control' col='valid' text='Valide' %}</th>
|
||||||
<th>Controle trésorier</th>
|
<th>{% include "buttons/sort.html" with prefix='control' col='control' text='Contrôlée' %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
{% for form in controlform.forms %}
|
{% for form in controlform.forms %}
|
||||||
|
@ -58,7 +60,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</td>
|
</td>
|
||||||
<td>{{ form.instance.user.name }}</td>
|
<td>{{ form.instance.user.name }}</td>
|
||||||
<td>{{ form.instance.user.surname }}</td>
|
<td>{{ form.instance.user.surname }}</td>
|
||||||
<td>{{ form.instance.name }}</td>
|
<td>{{ form.instance.id }}</td>
|
||||||
|
<td>{{ form.instance.user.id }}</td>
|
||||||
|
<td>{{ form.instance.name }}</td>
|
||||||
<td>{{ form.instance.prix_total }}</td>
|
<td>{{ form.instance.prix_total }}</td>
|
||||||
<td>{{ form.instance.paiement }}</td>
|
<td>{{ form.instance.paiement }}</td>
|
||||||
<td>{{ form.instance.date }}</td>
|
<td>{{ form.instance.date }}</td>
|
||||||
|
|
|
@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
{% load staticfiles%}
|
{% load staticfiles%}
|
||||||
|
{% load massive_bootstrap_form %}
|
||||||
|
|
||||||
{% block title %}Création et modification de factures{% endblock %}
|
{% 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">
|
<form class="form" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<h3>Editer la facture</h3>
|
<h3>Editer la facture</h3>
|
||||||
{% bootstrap_form factureform %}
|
{% massive_bootstrap_form factureform 'user' %}
|
||||||
{{ venteform.management_form }}
|
{{ venteform.management_form }}
|
||||||
<h3>Articles de la facture</h3>
|
<h3>Articles de la facture</h3>
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
|
|
|
@ -38,18 +38,20 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{{ venteform.management_form }}
|
{{ venteform.management_form }}
|
||||||
<!-- TODO: FIXME to include data-type="check" for right option in id_cheque select -->
|
<!-- TODO: FIXME to include data-type="check" for right option in id_cheque select -->
|
||||||
<h3>Articles de la facture</h3>
|
<h3>Articles de la facture</h3>
|
||||||
<div id="form_set">
|
<div id="form_set" class="form-group">
|
||||||
{% for form in venteform.forms %}
|
{% for form in venteform.forms %}
|
||||||
<div class='product_to_sell'>
|
<div class='product_to_sell form-inline'>
|
||||||
<p>
|
Article :
|
||||||
{{ form.as_table }}
|
{% bootstrap_form form label_class='sr-only' %}
|
||||||
</p>
|
|
||||||
|
<button class="btn btn-danger btn-sm"
|
||||||
|
id="id_form-0-article-remove" type="button">
|
||||||
|
<span class="glyphicon glyphicon-remove"></span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<p>
|
|
||||||
<input class="btn btn-primary btn-sm" role="button" value="Ajouter un article" id="add_one">
|
<input class="btn btn-primary btn-sm" role="button" value="Ajouter un article" id="add_one">
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
Prix total : <span id="total_price">0,00</span> €
|
Prix total : <span id="total_price">0,00</span> €
|
||||||
</p>
|
</p>
|
||||||
|
@ -63,19 +65,23 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
prices[{{ article.id|escapejs }}] = {{ article.prix }};
|
prices[{{ article.id|escapejs }}] = {{ article.prix }};
|
||||||
{% endfor %}
|
{% 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(){
|
function add_article(){
|
||||||
// Index start at 0 => new_index = number of items
|
// Index start at 0 => new_index = number of items
|
||||||
var new_index =
|
var new_index =
|
||||||
document.getElementsByClassName('product_to_sell').length;
|
document.getElementsByClassName('product_to_sell').length;
|
||||||
document.getElementById('id_form-TOTAL_FORMS').value =
|
document.getElementById('id_form-TOTAL_FORMS').value ++;
|
||||||
parseInt(document.getElementById('id_form-TOTAL_FORMS').value) + 1;
|
|
||||||
var new_article = document.createElement('div');
|
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);
|
new_article.innerHTML = template.replace(/__prefix__/g, new_index);
|
||||||
document.getElementById('form_set')
|
document.getElementById('form_set').appendChild(new_article);
|
||||||
.appendChild(new_article);
|
|
||||||
add_listenner_for_id(new_index);
|
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);
|
.addEventListener("onkeypress", update_price, true);
|
||||||
document.getElementById('id_form-' + i.toString() + '-quantity')
|
document.getElementById('id_form-' + i.toString() + '-quantity')
|
||||||
.addEventListener("change", update_price, true);
|
.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(){
|
function set_cheque_info_visibility() {
|
||||||
var visible = document.getElementById("id_paiement").value == document.getElementById("id_paiement").getAttribute('data-cheque');
|
var paiement = document.getElementById("id_Facture-paiement");
|
||||||
p = document.getElementById("id_paiement")
|
var visible = paiement.value == paiement.getAttribute('data-cheque');
|
||||||
console.log(p);
|
p = document.getElementById("id_Facture-paiement");
|
||||||
var display = 'none';
|
var display = 'none';
|
||||||
if (visible) {
|
if (visible) {
|
||||||
display = 'block';
|
display = 'block';
|
||||||
}
|
}
|
||||||
document.getElementById("id_cheque").parentNode.style.display = display;
|
document.getElementById("id_Facture-cheque")
|
||||||
document.getElementById("id_banque").parentNode.style.display = display;
|
.parentNode.style.display = display;
|
||||||
|
document.getElementById("id_Facture-banque")
|
||||||
|
.parentNode.style.display = display;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add events manager when DOM is fully loaded
|
// 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){
|
for (i = 0; i < product_count; ++i){
|
||||||
add_listenner_for_id(i);
|
add_listenner_for_id(i);
|
||||||
}
|
}
|
||||||
document.getElementById("id_paiement")
|
document.getElementById("id_Facture-paiement")
|
||||||
.addEventListener("change", set_cheque_info_visibility, true);
|
.addEventListener("change", set_cheque_info_visibility, true);
|
||||||
set_cheque_info_visibility();
|
set_cheque_info_visibility();
|
||||||
update_price();
|
update_price();
|
||||||
|
|
|
@ -27,30 +27,96 @@ from django.conf.urls import url
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^new_facture/(?P<userid>[0-9]+)$', views.new_facture, name='new-facture'),
|
url(r'^new_facture/(?P<userid>[0-9]+)$',
|
||||||
url(r'^edit_facture/(?P<factureid>[0-9]+)$', views.edit_facture, name='edit-facture'),
|
views.new_facture,
|
||||||
url(r'^del_facture/(?P<factureid>[0-9]+)$', views.del_facture, name='del-facture'),
|
name='new-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'^edit_facture/(?P<factureid>[0-9]+)$',
|
||||||
url(r'^credit_solde/(?P<userid>[0-9]+)$', views.credit_solde, name='credit-solde'),
|
views.edit_facture,
|
||||||
url(r'^add_article/$', views.add_article, name='add-article'),
|
name='edit-facture'
|
||||||
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'^del_facture/(?P<factureid>[0-9]+)$',
|
||||||
url(r'^add_paiement/$', views.add_paiement, name='add-paiement'),
|
views.del_facture,
|
||||||
url(r'^edit_paiement/(?P<paiementid>[0-9]+)$', views.edit_paiement, name='edit-paiement'),
|
name='del-facture'
|
||||||
url(r'^del_paiement/$', views.del_paiement, name='del-paiement'),
|
),
|
||||||
url(r'^add_banque/$', views.add_banque, name='add-banque'),
|
url(r'^facture_pdf/(?P<factureid>[0-9]+)$',
|
||||||
url(r'^edit_banque/(?P<banqueid>[0-9]+)$', views.edit_banque, name='edit-banque'),
|
views.facture_pdf,
|
||||||
url(r'^del_banque/$', views.del_banque, name='del-banque'),
|
name='facture-pdf'
|
||||||
url(r'^index_article/$', views.index_article, name='index-article'),
|
),
|
||||||
url(r'^index_banque/$', views.index_banque, name='index-banque'),
|
url(r'^new_facture_pdf/$',
|
||||||
url(r'^index_paiement/$', views.index_paiement, name='index-paiement'),
|
views.new_facture_pdf,
|
||||||
url(r'^history/(?P<object>facture)/(?P<id>[0-9]+)$', views.history, name='history'),
|
name='new-facture-pdf'
|
||||||
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'^credit_solde/(?P<userid>[0-9]+)$',
|
||||||
url(r'^history/(?P<object>banque)/(?P<id>[0-9]+)$', views.history, name='history'),
|
views.credit_solde,
|
||||||
url(r'^control/$', views.control, name='control'),
|
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_name>facture)/(?P<object_id>[0-9]+)$',
|
||||||
|
views.history,
|
||||||
|
name='history'
|
||||||
|
),
|
||||||
|
url(r'^history/(?P<object_name>article)/(?P<object_id>[0-9]+)$',
|
||||||
|
views.history,
|
||||||
|
name='history'
|
||||||
|
),
|
||||||
|
url(r'^history/(?P<object_name>paiement)/(?P<object_id>[0-9]+)$',
|
||||||
|
views.history,
|
||||||
|
name='history'),
|
||||||
|
url(r'^history/(?P<object_name>banque)/(?P<object_id>[0-9]+)$',
|
||||||
|
views.history,
|
||||||
|
name='history'
|
||||||
|
),
|
||||||
|
url(r'^control/$',
|
||||||
|
views.control,
|
||||||
|
name='control'
|
||||||
|
),
|
||||||
url(r'^$', views.index, name='index'),
|
url(r'^$', views.index, name='index'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,96 +24,145 @@
|
||||||
# Goulven Kermarec, Gabriel Détraz
|
# Goulven Kermarec, Gabriel Détraz
|
||||||
# Gplv2
|
# Gplv2
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
import os
|
||||||
from django.shortcuts import render, redirect
|
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.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.auth.decorators import login_required, permission_required
|
||||||
from django.contrib import messages
|
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.db import transaction
|
||||||
|
from django.db.models import Q
|
||||||
from django.forms import modelformset_factory, formset_factory
|
from django.forms import modelformset_factory, formset_factory
|
||||||
import os
|
from django.utils import timezone
|
||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
from reversion.models import Version
|
from reversion.models import Version
|
||||||
|
# Import des models, forms et fonctions re2o
|
||||||
from .models import Facture, Article, Vente, Cotisation, Paiement, Banque
|
|
||||||
from .forms import NewFactureForm, TrezEditFactureForm, EditFactureForm, ArticleForm, DelArticleForm, PaiementForm, DelPaiementForm, BanqueForm, DelBanqueForm, NewFactureFormPdf, CreditSoldeForm, SelectArticleForm
|
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from .tex import render_tex
|
|
||||||
from re2o.settings import LOGO_PATH
|
from re2o.settings import LOGO_PATH
|
||||||
from re2o import settings
|
from re2o import settings
|
||||||
|
from re2o.views import form
|
||||||
|
from re2o.utils import SortTable
|
||||||
from preferences.models import OptionalUser, AssoOption, GeneralOption
|
from preferences.models import OptionalUser, AssoOption, GeneralOption
|
||||||
|
from .models import Facture, Article, Vente, Paiement, Banque
|
||||||
|
from .forms import (
|
||||||
|
NewFactureForm,
|
||||||
|
TrezEditFactureForm,
|
||||||
|
EditFactureForm,
|
||||||
|
ArticleForm,
|
||||||
|
DelArticleForm,
|
||||||
|
PaiementForm,
|
||||||
|
DelPaiementForm,
|
||||||
|
BanqueForm,
|
||||||
|
DelBanqueForm,
|
||||||
|
NewFactureFormPdf,
|
||||||
|
SelectUserArticleForm,
|
||||||
|
SelectClubArticleForm,
|
||||||
|
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
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def new_facture(request, userid):
|
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:
|
try:
|
||||||
user = User.objects.get(pk=userid)
|
user = User.objects.get(pk=userid)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
messages.error(request, u"Utilisateur inexistant" )
|
messages.error(request, u"Utilisateur inexistant")
|
||||||
return redirect("/cotisations/")
|
return redirect("/cotisations/")
|
||||||
facture = Facture(user=user)
|
facture = Facture(user=user)
|
||||||
# Le template a besoin de connaitre les articles pour le js
|
# Le template a besoin de connaitre les articles pour le js
|
||||||
article_list = Article.objects.all()
|
article_list = Article.objects.filter(
|
||||||
|
Q(type_user='All') | Q(type_user=request.user.class_name)
|
||||||
|
)
|
||||||
# On envoie la form fature et un formset d'articles
|
# On envoie la form fature et un formset d'articles
|
||||||
facture_form = NewFactureForm(request.POST or None, instance=facture)
|
facture_form = NewFactureForm(request.POST or None, instance=facture)
|
||||||
article_formset = formset_factory(SelectArticleForm)(request.POST or None)
|
if request.user.is_class_club:
|
||||||
|
article_formset = formset_factory(SelectClubArticleForm)(request.POST or None)
|
||||||
|
else:
|
||||||
|
article_formset = formset_factory(SelectUserArticleForm)(request.POST or None)
|
||||||
if facture_form.is_valid() and article_formset.is_valid():
|
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
|
articles = article_formset
|
||||||
# Si au moins un article est rempli
|
# Si au moins un article est rempli
|
||||||
if any(art.cleaned_data for art in articles):
|
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
|
user_solde = options.user_solde
|
||||||
solde_negatif = options.solde_negatif
|
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 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
|
prix_total = 0
|
||||||
for art_item in articles:
|
for art_item in articles:
|
||||||
if art_item.cleaned_data:
|
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:
|
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)
|
return redirect("/users/profil/" + userid)
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
new_facture.save()
|
new_facture_instance.save()
|
||||||
reversion.set_user(request.user)
|
reversion.set_user(request.user)
|
||||||
reversion.set_comment("Création")
|
reversion.set_comment("Création")
|
||||||
for art_item in articles:
|
for art_item in articles:
|
||||||
if art_item.cleaned_data:
|
if art_item.cleaned_data:
|
||||||
article = art_item.cleaned_data['article']
|
article = art_item.cleaned_data['article']
|
||||||
quantity = art_item.cleaned_data['quantity']
|
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,
|
||||||
|
type_cotisation=article.type_cotisation,
|
||||||
|
duration=article.duration,
|
||||||
|
number=quantity
|
||||||
|
)
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
new_vente.save()
|
new_vente.save()
|
||||||
reversion.set_user(request.user)
|
reversion.set_user(request.user)
|
||||||
reversion.set_comment("Création")
|
reversion.set_comment("Création")
|
||||||
if any(art_item.cleaned_data['article'].iscotisation for art_item in articles if art_item.cleaned_data):
|
if any(art_item.cleaned_data['article'].type_cotisation
|
||||||
messages.success(request, "La cotisation a été prolongée pour l'adhérent %s jusqu'au %s" % (user.pseudo, user.end_adhesion()) )
|
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:
|
else:
|
||||||
messages.success(request, "La facture a été crée")
|
messages.success(request, "La facture a été crée")
|
||||||
return redirect("/users/profil/" + userid)
|
return redirect("/users/profil/" + userid)
|
||||||
messages.error(request, u"Il faut au moins un article valide pour créer une facture" )
|
messages.error(
|
||||||
return form({'factureform': facture_form, 'venteform': article_formset, 'articlelist': article_list}, 'cotisations/new_facture.html', request)
|
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
|
@login_required
|
||||||
@permission_required('tresorier')
|
@permission_required('tresorier')
|
||||||
def new_facture_pdf(request):
|
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)
|
facture_form = NewFactureFormPdf(request.POST or None)
|
||||||
if facture_form.is_valid():
|
if facture_form.is_valid():
|
||||||
options, created = AssoOption.objects.get_or_create()
|
options, _created = AssoOption.objects.get_or_create()
|
||||||
tbl = []
|
tbl = []
|
||||||
article = facture_form.cleaned_data['article']
|
article = facture_form.cleaned_data['article']
|
||||||
quantite = facture_form.cleaned_data['number']
|
quantite = facture_form.cleaned_data['number']
|
||||||
|
@ -121,71 +170,131 @@ def new_facture_pdf(request):
|
||||||
destinataire = facture_form.cleaned_data['dest']
|
destinataire = facture_form.cleaned_data['dest']
|
||||||
chambre = facture_form.cleaned_data['chambre']
|
chambre = facture_form.cleaned_data['chambre']
|
||||||
fid = facture_form.cleaned_data['fid']
|
fid = facture_form.cleaned_data['fid']
|
||||||
for a in article:
|
for art in article:
|
||||||
tbl.append([a, quantite, a.prix * quantite])
|
tbl.append([art, quantite, art.prix * quantite])
|
||||||
prix_total = sum(a[2] for a in tbl)
|
prix_total = sum(a[2] for a in tbl)
|
||||||
user = {'name':destinataire, 'room':chambre}
|
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 render_tex(request, 'cotisations/factures.tex', {
|
||||||
return form({'factureform': facture_form}, 'cotisations/facture.html', request)
|
'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
|
@login_required
|
||||||
def facture_pdf(request, factureid):
|
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:
|
try:
|
||||||
facture = Facture.objects.get(pk=factureid)
|
facture = Facture.objects.get(pk=factureid)
|
||||||
except Facture.DoesNotExist:
|
except Facture.DoesNotExist:
|
||||||
messages.error(request, u"Facture inexistante" )
|
messages.error(request, u"Facture inexistante")
|
||||||
return redirect("/cotisations/")
|
return redirect("/cotisations/")
|
||||||
if not request.user.has_perms(('cableur',)) and facture.user != request.user:
|
if not request.user.has_perms(('cableur',))\
|
||||||
messages.error(request, "Vous ne pouvez pas afficher une facture ne vous appartenant pas sans droit 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))
|
return redirect("/users/profil/" + str(request.user.id))
|
||||||
if not facture.valid:
|
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))
|
return redirect("/users/profil/" + str(request.user.id))
|
||||||
vente = Vente.objects.all().filter(facture=facture)
|
ventes_objects = Vente.objects.all().filter(facture=facture)
|
||||||
ventes = []
|
ventes = []
|
||||||
options, created = AssoOption.objects.get_or_create()
|
options, _created = AssoOption.objects.get_or_create()
|
||||||
for v in vente:
|
for vente in ventes_objects:
|
||||||
ventes.append([v, v.number, v.prix_total])
|
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)})
|
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
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def edit_facture(request, factureid):
|
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:
|
try:
|
||||||
facture = Facture.objects.get(pk=factureid)
|
facture = Facture.objects.get(pk=factureid)
|
||||||
except Facture.DoesNotExist:
|
except Facture.DoesNotExist:
|
||||||
messages.error(request, u"Facture inexistante" )
|
messages.error(request, u"Facture inexistante")
|
||||||
return redirect("/cotisations/")
|
return redirect("/cotisations/")
|
||||||
if request.user.has_perms(['tresorier']):
|
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:
|
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/")
|
return redirect("/cotisations/")
|
||||||
else:
|
else:
|
||||||
facture_form = EditFactureForm(request.POST or None, instance=facture)
|
facture_form = EditFactureForm(request.POST or None, instance=facture)
|
||||||
ventes_objects = Vente.objects.filter(facture=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)
|
vente_form = vente_form_set(request.POST or None, queryset=ventes_objects)
|
||||||
if facture_form.is_valid() and vente_form.is_valid():
|
if facture_form.is_valid() and vente_form.is_valid():
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
facture_form.save()
|
facture_form.save()
|
||||||
vente_form.save()
|
vente_form.save()
|
||||||
reversion.set_user(request.user)
|
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")
|
messages.success(request, "La facture a bien été modifiée")
|
||||||
return redirect("/cotisations/")
|
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
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def del_facture(request, factureid):
|
def del_facture(request, factureid):
|
||||||
|
"""Suppression d'une facture. Supprime en cascade les ventes
|
||||||
|
et cotisations filles"""
|
||||||
try:
|
try:
|
||||||
facture = Facture.objects.get(pk=factureid)
|
facture = Facture.objects.get(pk=factureid)
|
||||||
except Facture.DoesNotExist:
|
except Facture.DoesNotExist:
|
||||||
messages.error(request, u"Facture inexistante" )
|
messages.error(request, u"Facture inexistante")
|
||||||
return redirect("/cotisations/")
|
return redirect("/cotisations/")
|
||||||
if (facture.control or not facture.valid):
|
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")
|
messages.error(request, "Vous ne pouvez pas editer une facture\
|
||||||
|
controlée ou invalidée par le trésorier")
|
||||||
return redirect("/cotisations/")
|
return redirect("/cotisations/")
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
|
@ -193,7 +302,11 @@ def del_facture(request, factureid):
|
||||||
reversion.set_user(request.user)
|
reversion.set_user(request.user)
|
||||||
messages.success(request, "La facture a été détruite")
|
messages.success(request, "La facture a été détruite")
|
||||||
return redirect("/cotisations/")
|
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
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
|
@ -202,7 +315,7 @@ def credit_solde(request, userid):
|
||||||
try:
|
try:
|
||||||
user = User.objects.get(pk=userid)
|
user = User.objects.get(pk=userid)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
messages.error(request, u"Utilisateur inexistant" )
|
messages.error(request, u"Utilisateur inexistant")
|
||||||
return redirect("/cotisations/")
|
return redirect("/cotisations/")
|
||||||
facture = CreditSoldeForm(request.POST or None)
|
facture = CreditSoldeForm(request.POST or None)
|
||||||
if facture.is_valid():
|
if facture.is_valid():
|
||||||
|
@ -212,7 +325,12 @@ def credit_solde(request, userid):
|
||||||
facture_instance.save()
|
facture_instance.save()
|
||||||
reversion.set_user(request.user)
|
reversion.set_user(request.user)
|
||||||
reversion.set_comment("Création")
|
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'],
|
||||||
|
number=1
|
||||||
|
)
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
new_vente.save()
|
new_vente.save()
|
||||||
reversion.set_user(request.user)
|
reversion.set_user(request.user)
|
||||||
|
@ -225,6 +343,13 @@ def credit_solde(request, userid):
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('tresorier')
|
@permission_required('tresorier')
|
||||||
def add_article(request):
|
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)
|
article = ArticleForm(request.POST or None)
|
||||||
if article.is_valid():
|
if article.is_valid():
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
|
@ -235,27 +360,36 @@ def add_article(request):
|
||||||
return redirect("/cotisations/index_article/")
|
return redirect("/cotisations/index_article/")
|
||||||
return form({'factureform': article}, 'cotisations/facture.html', request)
|
return form({'factureform': article}, 'cotisations/facture.html', request)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('tresorier')
|
@permission_required('tresorier')
|
||||||
def edit_article(request, articleid):
|
def edit_article(request, articleid):
|
||||||
|
"""Edition d'un article (designation, prix, etc)
|
||||||
|
Réservé au trésorier"""
|
||||||
try:
|
try:
|
||||||
article_instance = Article.objects.get(pk=articleid)
|
article_instance = Article.objects.get(pk=articleid)
|
||||||
except Article.DoesNotExist:
|
except Article.DoesNotExist:
|
||||||
messages.error(request, u"Entrée inexistante" )
|
messages.error(request, u"Entrée inexistante")
|
||||||
return redirect("/cotisations/index_article/")
|
return redirect("/cotisations/index_article/")
|
||||||
article = ArticleForm(request.POST or None, instance=article_instance)
|
article = ArticleForm(request.POST or None, instance=article_instance)
|
||||||
if article.is_valid():
|
if article.is_valid():
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
article.save()
|
article.save()
|
||||||
reversion.set_user(request.user)
|
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é")
|
messages.success(request, "Type d'article modifié")
|
||||||
return redirect("/cotisations/index_article/")
|
return redirect("/cotisations/index_article/")
|
||||||
return form({'factureform': article}, 'cotisations/facture.html', request)
|
return form({'factureform': article}, 'cotisations/facture.html', request)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('tresorier')
|
@permission_required('tresorier')
|
||||||
def del_article(request):
|
def del_article(request):
|
||||||
|
"""Suppression d'un article en vente"""
|
||||||
article = DelArticleForm(request.POST or None)
|
article = DelArticleForm(request.POST or None)
|
||||||
if article.is_valid():
|
if article.is_valid():
|
||||||
article_del = article.cleaned_data['articles']
|
article_del = article.cleaned_data['articles']
|
||||||
|
@ -266,9 +400,12 @@ def del_article(request):
|
||||||
return redirect("/cotisations/index_article")
|
return redirect("/cotisations/index_article")
|
||||||
return form({'factureform': article}, 'cotisations/facture.html', request)
|
return form({'factureform': article}, 'cotisations/facture.html', request)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('tresorier')
|
@permission_required('tresorier')
|
||||||
def add_paiement(request):
|
def add_paiement(request):
|
||||||
|
"""Ajoute un moyen de paiement. Relié aux factures
|
||||||
|
via foreign key"""
|
||||||
paiement = PaiementForm(request.POST or None)
|
paiement = PaiementForm(request.POST or None)
|
||||||
if paiement.is_valid():
|
if paiement.is_valid():
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
|
@ -279,27 +416,35 @@ def add_paiement(request):
|
||||||
return redirect("/cotisations/index_paiement/")
|
return redirect("/cotisations/index_paiement/")
|
||||||
return form({'factureform': paiement}, 'cotisations/facture.html', request)
|
return form({'factureform': paiement}, 'cotisations/facture.html', request)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('tresorier')
|
@permission_required('tresorier')
|
||||||
def edit_paiement(request, paiementid):
|
def edit_paiement(request, paiementid):
|
||||||
|
"""Edition d'un moyen de paiement"""
|
||||||
try:
|
try:
|
||||||
paiement_instance = Paiement.objects.get(pk=paiementid)
|
paiement_instance = Paiement.objects.get(pk=paiementid)
|
||||||
except Paiement.DoesNotExist:
|
except Paiement.DoesNotExist:
|
||||||
messages.error(request, u"Entrée inexistante" )
|
messages.error(request, u"Entrée inexistante")
|
||||||
return redirect("/cotisations/index_paiement/")
|
return redirect("/cotisations/index_paiement/")
|
||||||
paiement = PaiementForm(request.POST or None, instance=paiement_instance)
|
paiement = PaiementForm(request.POST or None, instance=paiement_instance)
|
||||||
if paiement.is_valid():
|
if paiement.is_valid():
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
paiement.save()
|
paiement.save()
|
||||||
reversion.set_user(request.user)
|
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é")
|
messages.success(request, "Type de paiement modifié")
|
||||||
return redirect("/cotisations/index_paiement/")
|
return redirect("/cotisations/index_paiement/")
|
||||||
return form({'factureform': paiement}, 'cotisations/facture.html', request)
|
return form({'factureform': paiement}, 'cotisations/facture.html', request)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('tresorier')
|
@permission_required('tresorier')
|
||||||
def del_paiement(request):
|
def del_paiement(request):
|
||||||
|
"""Suppression d'un moyen de paiement"""
|
||||||
paiement = DelPaiementForm(request.POST or None)
|
paiement = DelPaiementForm(request.POST or None)
|
||||||
if paiement.is_valid():
|
if paiement.is_valid():
|
||||||
paiement_dels = paiement.cleaned_data['paiements']
|
paiement_dels = paiement.cleaned_data['paiements']
|
||||||
|
@ -309,15 +454,24 @@ def del_paiement(request):
|
||||||
paiement_del.delete()
|
paiement_del.delete()
|
||||||
reversion.set_user(request.user)
|
reversion.set_user(request.user)
|
||||||
reversion.set_comment("Destruction")
|
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:
|
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 redirect("/cotisations/index_paiement/")
|
||||||
return form({'factureform': paiement}, 'cotisations/facture.html', request)
|
return form({'factureform': paiement}, 'cotisations/facture.html', request)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def add_banque(request):
|
def add_banque(request):
|
||||||
|
"""Ajoute une banque à la liste des banques"""
|
||||||
banque = BanqueForm(request.POST or None)
|
banque = BanqueForm(request.POST or None)
|
||||||
if banque.is_valid():
|
if banque.is_valid():
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
|
@ -328,27 +482,35 @@ def add_banque(request):
|
||||||
return redirect("/cotisations/index_banque/")
|
return redirect("/cotisations/index_banque/")
|
||||||
return form({'factureform': banque}, 'cotisations/facture.html', request)
|
return form({'factureform': banque}, 'cotisations/facture.html', request)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('tresorier')
|
@permission_required('tresorier')
|
||||||
def edit_banque(request, banqueid):
|
def edit_banque(request, banqueid):
|
||||||
|
"""Edite le nom d'une banque"""
|
||||||
try:
|
try:
|
||||||
banque_instance = Banque.objects.get(pk=banqueid)
|
banque_instance = Banque.objects.get(pk=banqueid)
|
||||||
except Banque.DoesNotExist:
|
except Banque.DoesNotExist:
|
||||||
messages.error(request, u"Entrée inexistante" )
|
messages.error(request, u"Entrée inexistante")
|
||||||
return redirect("/cotisations/index_banque/")
|
return redirect("/cotisations/index_banque/")
|
||||||
banque = BanqueForm(request.POST or None, instance=banque_instance)
|
banque = BanqueForm(request.POST or None, instance=banque_instance)
|
||||||
if banque.is_valid():
|
if banque.is_valid():
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
banque.save()
|
banque.save()
|
||||||
reversion.set_user(request.user)
|
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")
|
messages.success(request, "Banque modifiée")
|
||||||
return redirect("/cotisations/index_banque/")
|
return redirect("/cotisations/index_banque/")
|
||||||
return form({'factureform': banque}, 'cotisations/facture.html', request)
|
return form({'factureform': banque}, 'cotisations/facture.html', request)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('tresorier')
|
@permission_required('tresorier')
|
||||||
def del_banque(request):
|
def del_banque(request):
|
||||||
|
"""Supprime une banque"""
|
||||||
banque = DelBanqueForm(request.POST or None)
|
banque = DelBanqueForm(request.POST or None)
|
||||||
if banque.is_valid():
|
if banque.is_valid():
|
||||||
banque_dels = banque.cleaned_data['banques']
|
banque_dels = banque.cleaned_data['banques']
|
||||||
|
@ -360,17 +522,31 @@ def del_banque(request):
|
||||||
reversion.set_comment("Destruction")
|
reversion.set_comment("Destruction")
|
||||||
messages.success(request, "La banque a été supprimée")
|
messages.success(request, "La banque a été supprimée")
|
||||||
except ProtectedError:
|
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 redirect("/cotisations/index_banque/")
|
||||||
return form({'factureform': banque}, 'cotisations/facture.html', request)
|
return form({'factureform': banque}, 'cotisations/facture.html', request)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('tresorier')
|
@permission_required('tresorier')
|
||||||
def control(request):
|
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
|
pagination_number = options.pagination_number
|
||||||
facture_list = Facture.objects.order_by('date').reverse()
|
facture_list = Facture.objects.select_related('user').select_related('paiement')
|
||||||
controlform_set = modelformset_factory(Facture, fields=('control','valid'), extra=0)
|
facture_list = SortTable.sort(
|
||||||
|
facture_list,
|
||||||
|
request.GET.get('col'),
|
||||||
|
request.GET.get('order'),
|
||||||
|
SortTable.COTISATIONS_CONTROL
|
||||||
|
)
|
||||||
|
controlform_set = modelformset_factory(
|
||||||
|
Facture,
|
||||||
|
fields=('control', 'valid'),
|
||||||
|
extra=0
|
||||||
|
)
|
||||||
paginator = Paginator(facture_list, pagination_number)
|
paginator = Paginator(facture_list, pagination_number)
|
||||||
page = request.GET.get('page')
|
page = request.GET.get('page')
|
||||||
try:
|
try:
|
||||||
|
@ -379,40 +555,63 @@ def control(request):
|
||||||
facture_list = paginator.page(1)
|
facture_list = paginator.page(1)
|
||||||
except EmptyPage:
|
except EmptyPage:
|
||||||
facture_list = paginator.page(paginator.num.pages)
|
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])
|
controlform = controlform_set(request.POST or None, queryset=facture_list.object_list)
|
||||||
controlform = controlform_set(request.POST or None, queryset=page_query)
|
|
||||||
if controlform.is_valid():
|
if controlform.is_valid():
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
controlform.save()
|
controlform.save()
|
||||||
reversion.set_user(request.user)
|
reversion.set_user(request.user)
|
||||||
reversion.set_comment("Controle trésorier")
|
reversion.set_comment("Controle trésorier")
|
||||||
return redirect("/cotisations/control/")
|
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
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def index_article(request):
|
def index_article(request):
|
||||||
|
"""Affiche l'ensemble des articles en vente"""
|
||||||
article_list = Article.objects.order_by('name')
|
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
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def index_paiement(request):
|
def index_paiement(request):
|
||||||
|
"""Affiche l'ensemble des moyens de paiement en vente"""
|
||||||
paiement_list = Paiement.objects.order_by('moyen')
|
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
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def index_banque(request):
|
def index_banque(request):
|
||||||
|
"""Affiche l'ensemble des banques"""
|
||||||
banque_list = Banque.objects.order_by('name')
|
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
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def index(request):
|
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
|
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.select_related('user')\
|
||||||
|
.select_related('paiement').prefetch_related('vente_set')
|
||||||
|
facture_list = SortTable.sort(
|
||||||
|
facture_list,
|
||||||
|
request.GET.get('col'),
|
||||||
|
request.GET.get('order'),
|
||||||
|
SortTable.COTISATIONS_INDEX
|
||||||
|
)
|
||||||
paginator = Paginator(facture_list, pagination_number)
|
paginator = Paginator(facture_list, pagination_number)
|
||||||
page = request.GET.get('page')
|
page = request.GET.get('page')
|
||||||
try:
|
try:
|
||||||
|
@ -423,41 +622,47 @@ def index(request):
|
||||||
except EmptyPage:
|
except EmptyPage:
|
||||||
# If page is out of range (e.g. 9999), deliver last page of results.
|
# If page is out of range (e.g. 9999), deliver last page of results.
|
||||||
facture_list = paginator.page(paginator.num_pages)
|
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
|
@login_required
|
||||||
def history(request, object, id):
|
def history(request, object_name, object_id):
|
||||||
if object == 'facture':
|
"""Affiche l'historique de chaque objet"""
|
||||||
|
if object_name == 'facture':
|
||||||
try:
|
try:
|
||||||
object_instance = Facture.objects.get(pk=id)
|
object_instance = Facture.objects.get(pk=object_id)
|
||||||
except Facture.DoesNotExist:
|
except Facture.DoesNotExist:
|
||||||
messages.error(request, "Facture inexistante")
|
messages.error(request, "Facture inexistante")
|
||||||
return redirect("/cotisations/")
|
return redirect("/cotisations/")
|
||||||
if not request.user.has_perms(('cableur',)) and object_instance.user != request.user:
|
if not request.user.has_perms(('cableur',))\
|
||||||
messages.error(request, "Vous ne pouvez pas afficher l'historique d'une facture d'un autre user que vous sans droit cableur")
|
and object_instance.user != request.user:
|
||||||
return redirect("/users/profil/" + str(request.user.id))
|
messages.error(request, "Vous ne pouvez pas afficher l'historique\
|
||||||
elif object == 'paiement' and request.user.has_perms(('cableur',)):
|
d'une facture d'un autre user que vous sans droit cableur")
|
||||||
|
return redirect("/users/profil/" + str(request.user.id))
|
||||||
|
elif object_name == 'paiement' and request.user.has_perms(('cableur',)):
|
||||||
try:
|
try:
|
||||||
object_instance = Paiement.objects.get(pk=id)
|
object_instance = Paiement.objects.get(pk=object_id)
|
||||||
except Paiement.DoesNotExist:
|
except Paiement.DoesNotExist:
|
||||||
messages.error(request, "Paiement inexistant")
|
messages.error(request, "Paiement inexistant")
|
||||||
return redirect("/cotisations/")
|
return redirect("/cotisations/")
|
||||||
elif object == 'article' and request.user.has_perms(('cableur',)):
|
elif object_name == 'article' and request.user.has_perms(('cableur',)):
|
||||||
try:
|
try:
|
||||||
object_instance = Article.objects.get(pk=id)
|
object_instance = Article.objects.get(pk=object_id)
|
||||||
except Article.DoesNotExist:
|
except Article.DoesNotExist:
|
||||||
messages.error(request, "Article inexistante")
|
messages.error(request, "Article inexistante")
|
||||||
return redirect("/cotisations/")
|
return redirect("/cotisations/")
|
||||||
elif object == 'banque' and request.user.has_perms(('cableur',)):
|
elif object_name == 'banque' and request.user.has_perms(('cableur',)):
|
||||||
try:
|
try:
|
||||||
object_instance = Banque.objects.get(pk=id)
|
object_instance = Banque.objects.get(pk=object_id)
|
||||||
except Banque.DoesNotExist:
|
except Banque.DoesNotExist:
|
||||||
messages.error(request, "Banque inexistante")
|
messages.error(request, "Banque inexistante")
|
||||||
return redirect("/cotisations/")
|
return redirect("/cotisations/")
|
||||||
else:
|
else:
|
||||||
messages.error(request, "Objet inconnu")
|
messages.error(request, "Objet inconnu")
|
||||||
return redirect("/cotisations/")
|
return redirect("/cotisations/")
|
||||||
options, created = GeneralOption.objects.get_or_create()
|
options, _created = GeneralOption.objects.get_or_create()
|
||||||
pagination_number = options.pagination_number
|
pagination_number = options.pagination_number
|
||||||
reversions = Version.objects.get_for_object(object_instance)
|
reversions = Version.objects.get_for_object(object_instance)
|
||||||
paginator = Paginator(reversions, pagination_number)
|
paginator = Paginator(reversions, pagination_number)
|
||||||
|
@ -470,4 +675,7 @@ def history(request, object, id):
|
||||||
except EmptyPage:
|
except EmptyPage:
|
||||||
# If page is out of range (e.g. 9999), deliver last page of results.
|
# If page is out of range (e.g. 9999), deliver last page of results.
|
||||||
reversions = paginator.page(paginator.num_pages)
|
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
|
||||||
|
})
|
||||||
|
|
BIN
docs_utils/re2o-archi.dia
Normal file
BIN
docs_utils/re2o-archi.dia
Normal file
Binary file not shown.
|
@ -3,6 +3,7 @@
|
||||||
# se veut agnostique au réseau considéré, de manière à être installable en
|
# se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
# quelques clics.
|
# quelques clics.
|
||||||
#
|
#
|
||||||
|
# Copyirght © 2017 Daniel Stan
|
||||||
# Copyright © 2017 Gabriel Détraz
|
# Copyright © 2017 Gabriel Détraz
|
||||||
# Copyright © 2017 Goulven Kermarec
|
# Copyright © 2017 Goulven Kermarec
|
||||||
# Copyright © 2017 Augustin Lemesle
|
# 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 :
|
Inspirés d'autres exemples trouvés ici :
|
||||||
https://github.com/FreeRADIUS/freeradius-server/blob/master/src/modules/rlm_python/
|
https://github.com/FreeRADIUS/freeradius-server/blob/master/src/modules/rlm_python/
|
||||||
|
|
||||||
|
Inspiré du travail de Daniel Stan au Crans
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import netaddr
|
import netaddr
|
||||||
import radiusd # Module magique freeradius (radiusd.py is dummy)
|
import radiusd # Module magique freeradius (radiusd.py is dummy)
|
||||||
import os
|
|
||||||
import binascii
|
import binascii
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
import os, sys
|
import os, sys
|
||||||
|
|
||||||
|
|
||||||
import os, sys
|
|
||||||
|
|
||||||
proj_path = "/var/www/re2o/"
|
proj_path = "/var/www/re2o/"
|
||||||
# This is so Django knows where to find stuff.
|
# This is so Django knows where to find stuff.
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "re2o.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "re2o.settings")
|
||||||
|
@ -248,6 +247,9 @@ def check_user_machine_and_register(nas_type, username, mac_address):
|
||||||
return (False, u"Machine enregistrée sur le compte d'un autre user...", '')
|
return (False, u"Machine enregistrée sur le compte d'un autre user...", '')
|
||||||
elif not interface.is_active:
|
elif not interface.is_active:
|
||||||
return (False, u"Machine desactivée", '')
|
return (False, u"Machine desactivée", '')
|
||||||
|
elif not interface.ipv4:
|
||||||
|
interface.assign_ipv4()
|
||||||
|
return (True, u"Ok, Reassignation de l'ipv4", user.pwd_ntlm)
|
||||||
else:
|
else:
|
||||||
return (True, u"Access ok", user.pwd_ntlm)
|
return (True, u"Access ok", user.pwd_ntlm)
|
||||||
elif nas_type:
|
elif nas_type:
|
||||||
|
@ -293,11 +295,12 @@ def decide_vlan_and_register_switch(nas, nas_type, port_number, mac_address):
|
||||||
if not port.room:
|
if not port.room:
|
||||||
return (sw_name, u'Chambre inconnue', VLAN_NOK)
|
return (sw_name, u'Chambre inconnue', VLAN_NOK)
|
||||||
|
|
||||||
room_user = User.objects.filter(room=Room.objects.filter(name=port.room))
|
room_user = User.objects.filter(Q(club__room=port.room) | Q(adherent__room=port.room))
|
||||||
if not room_user:
|
if not room_user:
|
||||||
return (sw_name, u'Chambre non cotisante', VLAN_NOK)
|
return (sw_name, u'Chambre non cotisante', VLAN_NOK)
|
||||||
elif not room_user.first().has_access():
|
for user in room_user:
|
||||||
return (sw_name, u'Chambre resident desactive', VLAN_NOK)
|
if not user.has_access():
|
||||||
|
return (sw_name, u'Chambre resident desactive', VLAN_NOK)
|
||||||
# else: user OK, on passe à la verif MAC
|
# else: user OK, on passe à la verif MAC
|
||||||
|
|
||||||
if port.radius == 'COMMON' or port.radius == 'STRICT':
|
if port.radius == 'COMMON' or port.radius == 'STRICT':
|
||||||
|
@ -310,9 +313,12 @@ def decide_vlan_and_register_switch(nas, nas_type, port_number, mac_address):
|
||||||
elif not port.room:
|
elif not port.room:
|
||||||
return (sw_name, u'Chambre et machine inconnues', VLAN_NOK)
|
return (sw_name, u'Chambre et machine inconnues', VLAN_NOK)
|
||||||
else:
|
else:
|
||||||
room_user = User.objects.filter(room=Room.objects.filter(name=port.room))
|
if not room_user:
|
||||||
|
room_user = User.objects.filter(Q(club__room=port.room) | Q(adherent__room=port.room))
|
||||||
if not room_user:
|
if not room_user:
|
||||||
return (sw_name, u'Machine et propriétaire de la chambre inconnus', VLAN_NOK)
|
return (sw_name, u'Machine et propriétaire de la chambre inconnus', VLAN_NOK)
|
||||||
|
elif room_user.count() > 1:
|
||||||
|
return (sw_name, u'Machine inconnue, il y a au moins 2 users dans la chambre/local -> ajout de mac automatique impossible', VLAN_NOK)
|
||||||
elif not room_user.first().has_access():
|
elif not room_user.first().has_access():
|
||||||
return (sw_name, u'Machine inconnue et adhérent non cotisant', VLAN_NOK)
|
return (sw_name, u'Machine inconnue et adhérent non cotisant', VLAN_NOK)
|
||||||
else:
|
else:
|
||||||
|
@ -321,9 +327,14 @@ def decide_vlan_and_register_switch(nas, nas_type, port_number, mac_address):
|
||||||
return (sw_name, u'Access Ok, Capture de la mac...' + extra_log, DECISION_VLAN)
|
return (sw_name, u'Access Ok, Capture de la mac...' + extra_log, DECISION_VLAN)
|
||||||
else:
|
else:
|
||||||
return (sw_name, u'Erreur dans le register mac %s' % reason + unicode(mac_address), VLAN_NOK)
|
return (sw_name, u'Erreur dans le register mac %s' % reason + unicode(mac_address), VLAN_NOK)
|
||||||
elif not interface.first().is_active:
|
|
||||||
return (sw_name, u'Machine non active / adherent non cotisant', VLAN_NOK)
|
|
||||||
else:
|
else:
|
||||||
return (sw_name, u'Machine OK' + extra_log, DECISION_VLAN)
|
interface = interface.first()
|
||||||
|
if not interface.is_active:
|
||||||
|
return (sw_name, u'Machine non active / adherent non cotisant', VLAN_NOK)
|
||||||
|
elif not interface.ipv4:
|
||||||
|
interface.assign_ipv4()
|
||||||
|
return (sw_name, u"Ok, Reassignation de l'ipv4" + extra_log, DECISION_VLAN)
|
||||||
|
else:
|
||||||
|
return (sw_name, u'Machine OK' + extra_log, DECISION_VLAN)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -33,8 +33,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<th>Objet modifié</th>
|
<th>Objet modifié</th>
|
||||||
<th>Type de l'objet</th>
|
<th>Type de l'objet</th>
|
||||||
<th>Modification par</th>
|
<th>{% include "buttons/sort.html" with prefix='logs' col='author' text='Modification par' %}</th>
|
||||||
<th>Date de modification</th>
|
<th>{% include "buttons/sort.html" with prefix='logs' col='date' text='Date de modification' %}</th>
|
||||||
<th>Commentaire</th>
|
<th>Commentaire</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -31,7 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Date</th>
|
<th>{% include "buttons/sort.html" with prefix='sum' col='date' text='Date' %}</th>
|
||||||
<th>Modification</th>
|
<th>Modification</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -19,7 +19,10 @@
|
||||||
# You should have received a copy of the GNU General Public License along
|
# 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.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 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 __future__ import unicode_literals
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
@ -29,7 +32,9 @@ from . import views
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', views.index, name='index'),
|
url(r'^$', views.index, name='index'),
|
||||||
url(r'^stats_logs$', views.stats_logs, name='stats-logs'),
|
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_general/$', views.stats_general, name='stats-general'),
|
||||||
url(r'^stats_models/$', views.stats_models, name='stats-models'),
|
url(r'^stats_models/$', views.stats_models, name='stats-models'),
|
||||||
url(r'^stats_users/$', views.stats_users, name='stats-users'),
|
url(r'^stats_users/$', views.stats_users, name='stats-users'),
|
||||||
|
|
440
logs/views.py
440
logs/views.py
|
@ -23,62 +23,110 @@
|
||||||
# App de gestion des statistiques pour re2o
|
# App de gestion des statistiques pour re2o
|
||||||
# Gabriel Détraz
|
# Gabriel Détraz
|
||||||
# Gplv2
|
# 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 __future__ import unicode_literals
|
||||||
|
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.shortcuts import render, redirect
|
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.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||||
from django.template import Context, RequestContext, loader
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required, permission_required
|
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 django.db.models import Count
|
||||||
|
|
||||||
from reversion.models import Revision
|
from reversion.models import Revision
|
||||||
from reversion.models import Version, ContentType
|
from reversion.models import Version, ContentType
|
||||||
|
|
||||||
from users.models import User, ServiceUser, Right, School, ListRight, ListShell, Ban, Whitelist
|
from users.models import (
|
||||||
from users.models import all_has_access, all_whitelisted, all_baned, all_adherent
|
User,
|
||||||
from cotisations.models import Facture, Vente, Article, Banque, Paiement, Cotisation
|
ServiceUser,
|
||||||
from machines.models import Machine, MachineType, IpType, Extension, Interface, Domain, IpList
|
Right,
|
||||||
from machines.views import all_active_assigned_interfaces_count, all_active_interfaces_count
|
School,
|
||||||
from topologie.models import Switch, Port, Room
|
ListRight,
|
||||||
|
ListShell,
|
||||||
|
Ban,
|
||||||
|
Whitelist,
|
||||||
|
Adherent,
|
||||||
|
Club
|
||||||
|
)
|
||||||
|
from cotisations.models import (
|
||||||
|
Facture,
|
||||||
|
Vente,
|
||||||
|
Article,
|
||||||
|
Banque,
|
||||||
|
Paiement,
|
||||||
|
Cotisation
|
||||||
|
)
|
||||||
|
from machines.models import (
|
||||||
|
Machine,
|
||||||
|
MachineType,
|
||||||
|
IpType,
|
||||||
|
Extension,
|
||||||
|
Interface,
|
||||||
|
Domain,
|
||||||
|
IpList,
|
||||||
|
OuverturePortList,
|
||||||
|
Service,
|
||||||
|
Vlan,
|
||||||
|
Nas,
|
||||||
|
SOA,
|
||||||
|
Mx,
|
||||||
|
Ns
|
||||||
|
)
|
||||||
|
from topologie.models import (
|
||||||
|
Switch,
|
||||||
|
Port,
|
||||||
|
Room,
|
||||||
|
Stack,
|
||||||
|
ModelSwitch,
|
||||||
|
ConstructorSwitch
|
||||||
|
)
|
||||||
from preferences.models import GeneralOption
|
from preferences.models import GeneralOption
|
||||||
|
from re2o.views import form
|
||||||
from django.utils import timezone
|
from re2o.utils import all_whitelisted, all_baned, all_has_access, all_adherent
|
||||||
from dateutil.relativedelta import relativedelta
|
from re2o.utils import all_active_assigned_interfaces_count
|
||||||
|
from re2o.utils import all_active_interfaces_count, SortTable
|
||||||
|
|
||||||
STATS_DICT = {
|
STATS_DICT = {
|
||||||
0 : ["Tout", 36],
|
0: ["Tout", 36],
|
||||||
1 : ["1 mois", 1],
|
1: ["1 mois", 1],
|
||||||
2 : ["2 mois", 2],
|
2: ["2 mois", 2],
|
||||||
3 : ["6 mois", 6],
|
3: ["6 mois", 6],
|
||||||
4 : ["1 an", 12],
|
4: ["1 an", 12],
|
||||||
5 : ["2 an", 24],
|
5: ["2 an", 24],
|
||||||
}
|
}
|
||||||
|
|
||||||
def form(ctx, template, request):
|
|
||||||
c = ctx
|
|
||||||
c.update(csrf(request))
|
|
||||||
return render(request, template, c)
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def index(request):
|
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
|
pagination_number = options.pagination_number
|
||||||
|
|
||||||
# The types of content kept for display
|
# The types of content kept for display
|
||||||
content_type_filter = ['ban', 'whitelist', 'vente', 'interface', 'user']
|
content_type_filter = ['ban', 'whitelist', 'vente', 'interface', 'user']
|
||||||
|
|
||||||
# Select only wanted versions
|
# 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
|
||||||
|
)
|
||||||
|
).select_related('revision')
|
||||||
|
versions = SortTable.sort(
|
||||||
|
versions,
|
||||||
|
request.GET.get('col'),
|
||||||
|
request.GET.get('order'),
|
||||||
|
SortTable.LOGS_INDEX
|
||||||
|
)
|
||||||
paginator = Paginator(versions, pagination_number)
|
paginator = Paginator(versions, pagination_number)
|
||||||
page = request.GET.get('page')
|
page = request.GET.get('page')
|
||||||
try:
|
try:
|
||||||
|
@ -87,7 +135,7 @@ def index(request):
|
||||||
# If page is not an integer, deliver first page.
|
# If page is not an integer, deliver first page.
|
||||||
versions = paginator.page(1)
|
versions = paginator.page(1)
|
||||||
except EmptyPage:
|
except EmptyPage:
|
||||||
# If page is out of range (e.g. 9999), deliver last page of results.
|
# If page is out of range (e.g. 9999), deliver last page of results.
|
||||||
versions = paginator.page(paginator.num_pages)
|
versions = paginator.page(paginator.num_pages)
|
||||||
|
|
||||||
# Force to have a list instead of QuerySet
|
# Force to have a list instead of QuerySet
|
||||||
|
@ -95,30 +143,43 @@ def index(request):
|
||||||
# Items to remove later because invalid
|
# Items to remove later because invalid
|
||||||
to_remove = []
|
to_remove = []
|
||||||
# Parse every item (max = pagination_number)
|
# Parse every item (max = pagination_number)
|
||||||
for i in range( len( versions.object_list ) ):
|
for i in range(len(versions.object_list)):
|
||||||
if versions.object_list[i].object :
|
if versions.object_list[i].object:
|
||||||
v = versions.object_list[i]
|
version = versions.object_list[i]
|
||||||
versions.object_list[i] = {
|
versions.object_list[i] = {
|
||||||
'rev_id' : v.revision.id,
|
'rev_id': version.revision.id,
|
||||||
'comment': v.revision.comment,
|
'comment': version.revision.comment,
|
||||||
'datetime': v.revision.date_created.strftime('%d/%m/%y %H:%M:%S'),
|
'datetime': version.revision.date_created.strftime(
|
||||||
'username': v.revision.user.get_username() if v.revision.user else '?',
|
'%d/%m/%y %H:%M:%S'
|
||||||
'user_id': v.revision.user_id,
|
),
|
||||||
'version': v }
|
'username':
|
||||||
else :
|
version.revision.user.get_username()
|
||||||
to_remove.insert(0,i)
|
if version.revision.user else '?',
|
||||||
|
'user_id': version.revision.user_id,
|
||||||
|
'version': version}
|
||||||
|
else:
|
||||||
|
to_remove.insert(0, i)
|
||||||
# Remove all tagged invalid items
|
# Remove all tagged invalid items
|
||||||
for i in to_remove :
|
for i in to_remove:
|
||||||
versions.object_list.pop(i)
|
versions.object_list.pop(i)
|
||||||
|
|
||||||
return render(request, 'logs/index.html', {'versions_list': versions})
|
return render(request, 'logs/index.html', {'versions_list': versions})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def stats_logs(request):
|
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
|
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().select_related('user')\
|
||||||
|
.prefetch_related('version_set__object')
|
||||||
|
revisions = SortTable.sort(
|
||||||
|
revisions,
|
||||||
|
request.GET.get('col'),
|
||||||
|
request.GET.get('order'),
|
||||||
|
SortTable.LOGS_STATS_LOGS
|
||||||
|
)
|
||||||
paginator = Paginator(revisions, pagination_number)
|
paginator = Paginator(revisions, pagination_number)
|
||||||
page = request.GET.get('page')
|
page = request.GET.get('page')
|
||||||
try:
|
try:
|
||||||
|
@ -127,9 +188,12 @@ def stats_logs(request):
|
||||||
# If page is not an integer, deliver first page.
|
# If page is not an integer, deliver first page.
|
||||||
revisions = paginator.page(1)
|
revisions = paginator.page(1)
|
||||||
except EmptyPage:
|
except EmptyPage:
|
||||||
# If page is out of range (e.g. 9999), deliver last page of results.
|
# If page is out of range (e.g. 9999), deliver last page of results.
|
||||||
revisions = paginator.page(paginator.num_pages)
|
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
|
@login_required
|
||||||
@permission_required('bureau')
|
@permission_required('bureau')
|
||||||
|
@ -138,121 +202,235 @@ def revert_action(request, revision_id):
|
||||||
try:
|
try:
|
||||||
revision = Revision.objects.get(id=revision_id)
|
revision = Revision.objects.get(id=revision_id)
|
||||||
except Revision.DoesNotExist:
|
except Revision.DoesNotExist:
|
||||||
messages.error(request, u"Revision inexistante" )
|
messages.error(request, u"Revision inexistante")
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
revision.revert()
|
revision.revert()
|
||||||
messages.success(request, "L'action a été supprimée")
|
messages.success(request, "L'action a été supprimée")
|
||||||
return redirect("/logs/")
|
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
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def stats_general(request):
|
def stats_general(request):
|
||||||
all_active_users = User.objects.filter(state=User.STATE_ACTIVE)
|
"""Statistiques générales affinées sur les ip, activées, utilisées par
|
||||||
ip = dict()
|
range, et les statistiques générales sur les users : users actifs,
|
||||||
for ip_range in IpType.objects.all():
|
cotisants, activés, archivés, etc"""
|
||||||
|
ip_dict = dict()
|
||||||
|
for ip_range in IpType.objects.select_related('vlan').all():
|
||||||
all_ip = IpList.objects.filter(ip_type=ip_range)
|
all_ip = IpList.objects.filter(ip_type=ip_range)
|
||||||
used_ip = Interface.objects.filter(ipv4__in=all_ip).count()
|
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()
|
active_ip = all_active_assigned_interfaces_count().filter(
|
||||||
ip[ip_range] = [ip_range, all_ip.count(), used_ip, active_ip, all_ip.count()-used_ip]
|
ipv4__in=IpList.objects.filter(ip_type=ip_range)
|
||||||
|
).count()
|
||||||
|
ip_dict[ip_range] = [ip_range, ip_range.vlan, all_ip.count(),
|
||||||
|
used_ip, active_ip, all_ip.count()-used_ip]
|
||||||
|
_all_adherent = all_adherent()
|
||||||
|
_all_has_access = all_has_access()
|
||||||
|
_all_baned = all_baned()
|
||||||
|
_all_whitelisted = all_whitelisted()
|
||||||
|
_all_active_interfaces_count = all_active_interfaces_count()
|
||||||
|
_all_active_assigned_interfaces_count = all_active_assigned_interfaces_count()
|
||||||
stats = [
|
stats = [
|
||||||
[["Categorie", "Nombre d'utilisateurs"], {
|
[["Categorie", "Nombre d'utilisateurs (total club et adhérents)", "Nombre d'adhérents", "Nombre de clubs"], {
|
||||||
'active_users' : ["Users actifs", User.objects.filter(state=User.STATE_ACTIVE).count()],
|
'active_users': [
|
||||||
'inactive_users' : ["Users désactivés", User.objects.filter(state=User.STATE_DISABLED).count()],
|
"Users actifs",
|
||||||
'archive_users' : ["Users archivés", User.objects.filter(state=User.STATE_ARCHIVE).count()],
|
User.objects.filter(state=User.STATE_ACTIVE).count(),
|
||||||
'adherent_users' : ["Adhérents à l'association", all_adherent().count()],
|
Adherent.objects.filter(state=Adherent.STATE_ACTIVE).count(),
|
||||||
'connexion_users' : ["Utilisateurs bénéficiant d'une connexion", all_has_access().count()],
|
Club.objects.filter(state=Club.STATE_ACTIVE).count()],
|
||||||
'ban_users' : ["Utilisateurs bannis", all_baned().count()],
|
'inactive_users': [
|
||||||
'whitelisted_user' : ["Utilisateurs bénéficiant d'une connexion gracieuse", all_whitelisted().count()],
|
"Users désactivés",
|
||||||
'actives_interfaces' : ["Interfaces actives (ayant accès au reseau)", all_active_interfaces_count().count()],
|
User.objects.filter(state=User.STATE_DISABLED).count(),
|
||||||
'actives_assigned_interfaces' : ["Interfaces actives et assignées ipv4", all_active_assigned_interfaces_count().count()]
|
Adherent.objects.filter(state=Adherent.STATE_DISABLED).count(),
|
||||||
}],
|
Club.objects.filter(state=Club.STATE_DISABLED).count()],
|
||||||
[["Range d'ip", "Nombre d'ip totales", "Ip assignées", "Ip assignées à une machine active", "Ip non assignées"] ,ip]
|
'archive_users': [
|
||||||
]
|
"Users archivés",
|
||||||
|
User.objects.filter(state=User.STATE_ARCHIVE).count(),
|
||||||
|
Adherent.objects.filter(state=Adherent.STATE_ARCHIVE).count(),
|
||||||
|
Club.objects.filter(state=Club.STATE_ARCHIVE).count()],
|
||||||
|
'adherent_users': [
|
||||||
|
"Cotisant à l'association",
|
||||||
|
_all_adherent.count(),
|
||||||
|
_all_adherent.exclude(adherent__isnull=True).count(),
|
||||||
|
_all_adherent.exclude(club__isnull=True).count()],
|
||||||
|
'connexion_users': [
|
||||||
|
"Utilisateurs bénéficiant d'une connexion",
|
||||||
|
_all_has_access.count(),
|
||||||
|
_all_has_access.exclude(adherent__isnull=True).count(),
|
||||||
|
_all_has_access.exclude(club__isnull=True).count()],
|
||||||
|
'ban_users': [
|
||||||
|
"Utilisateurs bannis",
|
||||||
|
_all_baned.count(),
|
||||||
|
_all_baned.exclude(adherent__isnull=True).count(),
|
||||||
|
_all_baned.exclude(club__isnull=True).count()],
|
||||||
|
'whitelisted_user': [
|
||||||
|
"Utilisateurs bénéficiant d'une connexion gracieuse",
|
||||||
|
_all_whitelisted.count(),
|
||||||
|
_all_whitelisted.exclude(adherent__isnull=True).count(),
|
||||||
|
_all_whitelisted.exclude(club__isnull=True).count()],
|
||||||
|
'actives_interfaces': [
|
||||||
|
"Interfaces actives (ayant accès au reseau)",
|
||||||
|
_all_active_interfaces_count.count(),
|
||||||
|
_all_active_interfaces_count.exclude(
|
||||||
|
machine__user__adherent__isnull=True
|
||||||
|
).count(),
|
||||||
|
_all_active_interfaces_count.exclude(
|
||||||
|
machine__user__club__isnull=True
|
||||||
|
).count()],
|
||||||
|
'actives_assigned_interfaces': [
|
||||||
|
"Interfaces actives et assignées ipv4",
|
||||||
|
_all_active_assigned_interfaces_count.count(),
|
||||||
|
_all_active_assigned_interfaces_count.exclude(
|
||||||
|
machine__user__adherent__isnull=True
|
||||||
|
).count(),
|
||||||
|
_all_active_assigned_interfaces_count.exclude(
|
||||||
|
machine__user__club__isnull=True
|
||||||
|
).count()]
|
||||||
|
}],
|
||||||
|
[["Range d'ip", "Vlan", "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})
|
return render(request, 'logs/stats_general.html', {'stats_list': stats})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def stats_models(request):
|
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 = {
|
stats = {
|
||||||
'Users' : {
|
'Users': {
|
||||||
'users' : [User.PRETTY_NAME, User.objects.count()],
|
'users': [User.PRETTY_NAME, User.objects.count()],
|
||||||
'serviceuser' : [ServiceUser.PRETTY_NAME, ServiceUser.objects.count()],
|
'adherents': [Adherent.PRETTY_NAME, Adherent.objects.count()],
|
||||||
'right' : [Right.PRETTY_NAME, Right.objects.count()],
|
'clubs': [Club.PRETTY_NAME, Club.objects.count()],
|
||||||
'school' : [School.PRETTY_NAME, School.objects.count()],
|
'serviceuser': [ServiceUser.PRETTY_NAME,
|
||||||
'listright' : [ListRight.PRETTY_NAME, ListRight.objects.count()],
|
ServiceUser.objects.count()],
|
||||||
'listshell' : [ListShell.PRETTY_NAME, ListShell.objects.count()],
|
'right': [Right.PRETTY_NAME, Right.objects.count()],
|
||||||
'ban' : [Ban.PRETTY_NAME, Ban.objects.count()],
|
'school': [School.PRETTY_NAME, School.objects.count()],
|
||||||
'whitelist' : [Whitelist.PRETTY_NAME, Whitelist.objects.count()]
|
'listright': [ListRight.PRETTY_NAME, ListRight.objects.count()],
|
||||||
},
|
'listshell': [ListShell.PRETTY_NAME, ListShell.objects.count()],
|
||||||
'Cotisations' : {
|
'ban': [Ban.PRETTY_NAME, Ban.objects.count()],
|
||||||
'factures' : [Facture.PRETTY_NAME, Facture.objects.count()],
|
'whitelist': [Whitelist.PRETTY_NAME, Whitelist.objects.count()]
|
||||||
'vente' : [Vente.PRETTY_NAME, Vente.objects.count()],
|
},
|
||||||
'cotisation' : [Cotisation.PRETTY_NAME, Cotisation.objects.count()],
|
'Cotisations': {
|
||||||
'article' : [Article.PRETTY_NAME, Article.objects.count()],
|
'factures': [Facture.PRETTY_NAME, Facture.objects.count()],
|
||||||
'banque' : [Banque.PRETTY_NAME, Banque.objects.count()],
|
'vente': [Vente.PRETTY_NAME, Vente.objects.count()],
|
||||||
'cotisation' : [Cotisation.PRETTY_NAME, Cotisation.objects.count()],
|
'cotisation': [Cotisation.PRETTY_NAME, Cotisation.objects.count()],
|
||||||
},
|
'article': [Article.PRETTY_NAME, Article.objects.count()],
|
||||||
'Machines' : {
|
'banque': [Banque.PRETTY_NAME, Banque.objects.count()],
|
||||||
'machine' : [Machine.PRETTY_NAME, Machine.objects.count()],
|
},
|
||||||
'typemachine' : [MachineType.PRETTY_NAME, MachineType.objects.count()],
|
'Machines': {
|
||||||
'typeip' : [IpType.PRETTY_NAME, IpType.objects.count()],
|
'machine': [Machine.PRETTY_NAME, Machine.objects.count()],
|
||||||
'extension' : [Extension.PRETTY_NAME, Extension.objects.count()],
|
'typemachine': [MachineType.PRETTY_NAME,
|
||||||
'interface' : [Interface.PRETTY_NAME, Interface.objects.count()],
|
MachineType.objects.count()],
|
||||||
'alias' : [Domain.PRETTY_NAME, Domain.objects.exclude(cname=None).count()],
|
'typeip': [IpType.PRETTY_NAME, IpType.objects.count()],
|
||||||
'iplist' : [IpList.PRETTY_NAME, IpList.objects.count()],
|
'extension': [Extension.PRETTY_NAME, Extension.objects.count()],
|
||||||
},
|
'interface': [Interface.PRETTY_NAME, Interface.objects.count()],
|
||||||
'Topologie' : {
|
'alias': [Domain.PRETTY_NAME,
|
||||||
'switch' : [Switch.PRETTY_NAME, Switch.objects.count()],
|
Domain.objects.exclude(cname=None).count()],
|
||||||
'port' : [Port.PRETTY_NAME, Port.objects.count()],
|
'iplist': [IpList.PRETTY_NAME, IpList.objects.count()],
|
||||||
'chambre' : [Room.PRETTY_NAME, Room.objects.count()],
|
'service': [Service.PRETTY_NAME, Service.objects.count()],
|
||||||
},
|
'ouvertureportlist': [
|
||||||
'Actions effectuées sur la base' :
|
OuverturePortList.PRETTY_NAME,
|
||||||
{
|
OuverturePortList.objects.count()
|
||||||
'revision' : ["Nombre d'actions", Revision.objects.count()],
|
],
|
||||||
},
|
'vlan': [Vlan.PRETTY_NAME, Vlan.objects.count()],
|
||||||
|
'SOA': [Mx.PRETTY_NAME, Mx.objects.count()],
|
||||||
|
'Mx': [Mx.PRETTY_NAME, Mx.objects.count()],
|
||||||
|
'Ns': [Ns.PRETTY_NAME, Ns.objects.count()],
|
||||||
|
'nas': [Nas.PRETTY_NAME, Nas.objects.count()],
|
||||||
|
},
|
||||||
|
'Topologie': {
|
||||||
|
'switch': [Switch.PRETTY_NAME, Switch.objects.count()],
|
||||||
|
'port': [Port.PRETTY_NAME, Port.objects.count()],
|
||||||
|
'chambre': [Room.PRETTY_NAME, Room.objects.count()],
|
||||||
|
'stack': [Stack.PRETTY_NAME, Stack.objects.count()],
|
||||||
|
'modelswitch': [
|
||||||
|
ModelSwitch.PRETTY_NAME,
|
||||||
|
ModelSwitch.objects.count()
|
||||||
|
],
|
||||||
|
'constructorswitch': [
|
||||||
|
ConstructorSwitch.PRETTY_NAME,
|
||||||
|
ConstructorSwitch.objects.count()
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'Actions effectuées sur la base':
|
||||||
|
{
|
||||||
|
'revision': ["Nombre d'actions", Revision.objects.count()],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return render(request, 'logs/stats_models.html', {'stats_list': stats})
|
return render(request, 'logs/stats_models.html', {'stats_list': stats})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def stats_users(request):
|
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')
|
onglet = request.GET.get('onglet')
|
||||||
try:
|
try:
|
||||||
search_field = STATS_DICT[onglet]
|
_search_field = STATS_DICT[onglet]
|
||||||
except:
|
except KeyError:
|
||||||
search_field = STATS_DICT[0]
|
_search_field = STATS_DICT[0]
|
||||||
onglet = 0
|
onglet = 0
|
||||||
start_date = timezone.now() + relativedelta(months=-search_field[1])
|
|
||||||
stats = {
|
stats = {
|
||||||
'Utilisateur' : {
|
'Utilisateur': {
|
||||||
'Machines' : User.objects.annotate(num=Count('machine')).order_by('-num')[:10],
|
'Machines': User.objects.annotate(
|
||||||
'Facture' : User.objects.annotate(num=Count('facture')).order_by('-num')[:10],
|
num=Count('machine')
|
||||||
'Bannissement' : User.objects.annotate(num=Count('ban')).order_by('-num')[:10],
|
).order_by('-num')[:10],
|
||||||
'Accès gracieux' : User.objects.annotate(num=Count('whitelist')).order_by('-num')[:10],
|
'Facture': User.objects.annotate(
|
||||||
'Droits' : User.objects.annotate(num=Count('right')).order_by('-num')[:10],
|
num=Count('facture')
|
||||||
},
|
).order_by('-num')[:10],
|
||||||
'Etablissement' : {
|
'Bannissement': User.objects.annotate(
|
||||||
'Utilisateur' : School.objects.annotate(num=Count('user')).order_by('-num')[:10],
|
num=Count('ban')
|
||||||
},
|
).order_by('-num')[:10],
|
||||||
'Moyen de paiement' : {
|
'Accès gracieux': User.objects.annotate(
|
||||||
'Utilisateur' : Paiement.objects.annotate(num=Count('facture')).order_by('-num')[:10],
|
num=Count('whitelist')
|
||||||
},
|
).order_by('-num')[:10],
|
||||||
'Banque' : {
|
'Droits': User.objects.annotate(
|
||||||
'Utilisateur' : Banque.objects.annotate(num=Count('facture')).order_by('-num')[:10],
|
num=Count('right')
|
||||||
},
|
).order_by('-num')[:10],
|
||||||
|
},
|
||||||
|
'Etablissement': {
|
||||||
|
'Utilisateur': School.objects.annotate(
|
||||||
|
num=Count('user')
|
||||||
|
).order_by('-num')[:10],
|
||||||
|
},
|
||||||
|
'Moyen de paiement': {
|
||||||
|
'Utilisateur': Paiement.objects.annotate(
|
||||||
|
num=Count('facture')
|
||||||
|
).order_by('-num')[:10],
|
||||||
|
},
|
||||||
|
'Banque': {
|
||||||
|
'Utilisateur': Banque.objects.annotate(
|
||||||
|
num=Count('facture')
|
||||||
|
).order_by('-num')[:10],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return render(request, 'logs/stats_users.html', {'stats_list': stats, 'stats_dict' : STATS_DICT, 'active_field': onglet})
|
return render(request, 'logs/stats_users.html', {
|
||||||
|
'stats_list': stats,
|
||||||
|
'stats_dict': STATS_DICT,
|
||||||
|
'active_field': onglet
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def stats_actions(request):
|
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 = {
|
stats = {
|
||||||
'Utilisateur' : {
|
'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})
|
return render(request, 'logs/stats_users.html', {'stats_list': stats})
|
||||||
|
|
|
@ -27,58 +27,79 @@ from django.contrib import admin
|
||||||
from reversion.admin import VersionAdmin
|
from reversion.admin import VersionAdmin
|
||||||
|
|
||||||
from .models import IpType, Machine, MachineType, Domain, IpList, Interface
|
from .models import IpType, Machine, MachineType, Domain, IpList, Interface
|
||||||
from .models import Extension, Mx, Ns, Vlan, Text, Nas, Service, OuverturePort
|
from .models import Extension, SOA, Mx, Ns, Vlan, Text, Nas, Service
|
||||||
from .models import OuverturePortList
|
from .models import OuverturePort, OuverturePortList
|
||||||
|
|
||||||
|
|
||||||
class MachineAdmin(VersionAdmin):
|
class MachineAdmin(VersionAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class IpTypeAdmin(VersionAdmin):
|
class IpTypeAdmin(VersionAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MachineTypeAdmin(VersionAdmin):
|
class MachineTypeAdmin(VersionAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class VlanAdmin(VersionAdmin):
|
class VlanAdmin(VersionAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ExtensionAdmin(VersionAdmin):
|
class ExtensionAdmin(VersionAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SOAAdmin(VersionAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MxAdmin(VersionAdmin):
|
class MxAdmin(VersionAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NsAdmin(VersionAdmin):
|
class NsAdmin(VersionAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TextAdmin(VersionAdmin):
|
class TextAdmin(VersionAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NasAdmin(VersionAdmin):
|
class NasAdmin(VersionAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class IpListAdmin(VersionAdmin):
|
class IpListAdmin(VersionAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class OuverturePortAdmin(VersionAdmin):
|
class OuverturePortAdmin(VersionAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class OuverturePortListAdmin(VersionAdmin):
|
class OuverturePortListAdmin(VersionAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InterfaceAdmin(VersionAdmin):
|
class InterfaceAdmin(VersionAdmin):
|
||||||
list_display = ('machine','type','mac_address','ipv4','details')
|
list_display = ('machine','type','mac_address','ipv4','details')
|
||||||
|
|
||||||
|
|
||||||
class DomainAdmin(VersionAdmin):
|
class DomainAdmin(VersionAdmin):
|
||||||
list_display = ('interface_parent', 'name', 'extension', 'cname')
|
list_display = ('interface_parent', 'name', 'extension', 'cname')
|
||||||
|
|
||||||
|
|
||||||
class ServiceAdmin(VersionAdmin):
|
class ServiceAdmin(VersionAdmin):
|
||||||
list_display = ('service_type', 'min_time_regen', 'regular_time_regen')
|
list_display = ('service_type', 'min_time_regen', 'regular_time_regen')
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Machine, MachineAdmin)
|
admin.site.register(Machine, MachineAdmin)
|
||||||
admin.site.register(MachineType, MachineTypeAdmin)
|
admin.site.register(MachineType, MachineTypeAdmin)
|
||||||
admin.site.register(IpType, IpTypeAdmin)
|
admin.site.register(IpType, IpTypeAdmin)
|
||||||
admin.site.register(Extension, ExtensionAdmin)
|
admin.site.register(Extension, ExtensionAdmin)
|
||||||
|
admin.site.register(SOA, SOAAdmin)
|
||||||
admin.site.register(Mx, MxAdmin)
|
admin.site.register(Mx, MxAdmin)
|
||||||
admin.site.register(Ns, NsAdmin)
|
admin.site.register(Ns, NsAdmin)
|
||||||
admin.site.register(Text, TextAdmin)
|
admin.site.register(Text, TextAdmin)
|
||||||
|
|
|
@ -21,208 +21,399 @@
|
||||||
# You should have received a copy of the GNU General Public License along
|
# 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.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
"""
|
||||||
|
Formulaires d'ajout, edition et suppressions de :
|
||||||
|
- machines
|
||||||
|
- interfaces
|
||||||
|
- domain (noms de machine)
|
||||||
|
- alias (cname)
|
||||||
|
- service (dhcp, dns..)
|
||||||
|
- ns (serveur dns)
|
||||||
|
- mx (serveur mail)
|
||||||
|
- ports ouverts et profils d'ouverture par interface
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
from django.forms import ModelForm, Form
|
||||||
|
|
||||||
from django.forms import ModelForm, Form, ValidationError
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from .models import Domain, Machine, Interface, IpList, MachineType, Extension, Mx, Text, Ns, Service, Vlan, Nas, IpType, OuverturePortList, OuverturePort
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.core.validators import validate_email
|
|
||||||
|
|
||||||
from users.models import User
|
from .models import (
|
||||||
|
Domain,
|
||||||
|
Machine,
|
||||||
|
Interface,
|
||||||
|
IpList,
|
||||||
|
MachineType,
|
||||||
|
Extension,
|
||||||
|
SOA,
|
||||||
|
Mx,
|
||||||
|
Text,
|
||||||
|
Ns,
|
||||||
|
Service,
|
||||||
|
Vlan,
|
||||||
|
Nas,
|
||||||
|
IpType,
|
||||||
|
OuverturePortList,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class EditMachineForm(ModelForm):
|
class EditMachineForm(ModelForm):
|
||||||
|
"""Formulaire d'édition d'une machine"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Machine
|
model = Machine
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
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'
|
self.fields['name'].label = 'Nom de la machine'
|
||||||
|
|
||||||
|
|
||||||
class NewMachineForm(EditMachineForm):
|
class NewMachineForm(EditMachineForm):
|
||||||
|
"""Creation d'une machine, ne renseigne que le nom"""
|
||||||
class Meta(EditMachineForm.Meta):
|
class Meta(EditMachineForm.Meta):
|
||||||
fields = ['name']
|
fields = ['name']
|
||||||
|
|
||||||
|
|
||||||
class BaseEditMachineForm(EditMachineForm):
|
class BaseEditMachineForm(EditMachineForm):
|
||||||
|
"""Edition basique, ne permet que de changer le nom et le statut.
|
||||||
|
Réservé aux users sans droits spécifiques"""
|
||||||
class Meta(EditMachineForm.Meta):
|
class Meta(EditMachineForm.Meta):
|
||||||
fields = ['name','active']
|
fields = ['name', 'active']
|
||||||
|
|
||||||
|
|
||||||
class EditInterfaceForm(ModelForm):
|
class EditInterfaceForm(ModelForm):
|
||||||
|
"""Edition d'une interface. Edition complète"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Interface
|
model = Interface
|
||||||
fields = ['machine', 'type', 'ipv4', 'mac_address', 'details']
|
fields = ['machine', 'type', 'ipv4', 'mac_address', 'details']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
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['mac_address'].label = 'Adresse mac'
|
||||||
self.fields['type'].label = 'Type de machine'
|
self.fields['type'].label = 'Type de machine'
|
||||||
self.fields['type'].empty_label = "Séléctionner un type de machine"
|
self.fields['type'].empty_label = "Séléctionner un type de machine"
|
||||||
if "ipv4" in self.fields:
|
if "ipv4" in self.fields:
|
||||||
self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4"
|
self.fields['ipv4'].empty_label = "Assignation automatique\
|
||||||
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True)
|
de l'ipv4"
|
||||||
|
self.fields['ipv4'].queryset = IpList.objects.filter(
|
||||||
|
interface__isnull=True
|
||||||
|
)
|
||||||
# Add it's own address
|
# Add it's own address
|
||||||
self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance)
|
self.fields['ipv4'].queryset |= IpList.objects.filter(
|
||||||
|
interface=self.instance
|
||||||
|
)
|
||||||
if "machine" in self.fields:
|
if "machine" in self.fields:
|
||||||
self.fields['machine'].queryset = Machine.objects.all().select_related('user')
|
self.fields['machine'].queryset = Machine.objects.all()\
|
||||||
|
.select_related('user')
|
||||||
|
|
||||||
|
|
||||||
class AddInterfaceForm(EditInterfaceForm):
|
class AddInterfaceForm(EditInterfaceForm):
|
||||||
|
"""Ajout d'une interface à une machine. En fonction des droits,
|
||||||
|
affiche ou non l'ensemble des ip disponibles"""
|
||||||
class Meta(EditInterfaceForm.Meta):
|
class Meta(EditInterfaceForm.Meta):
|
||||||
fields = ['type','ipv4','mac_address','details']
|
fields = ['type', 'ipv4', 'mac_address', 'details']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
infra = kwargs.pop('infra')
|
infra = kwargs.pop('infra')
|
||||||
super(AddInterfaceForm, self).__init__(*args, **kwargs)
|
super(AddInterfaceForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4"
|
self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4"
|
||||||
if not infra:
|
if not infra:
|
||||||
self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False))
|
self.fields['type'].queryset = MachineType.objects.filter(
|
||||||
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False))
|
ip_type__in=IpType.objects.filter(need_infra=False)
|
||||||
|
)
|
||||||
|
self.fields['ipv4'].queryset = IpList.objects.filter(
|
||||||
|
interface__isnull=True
|
||||||
|
).filter(ip_type__in=IpType.objects.filter(need_infra=False))
|
||||||
else:
|
else:
|
||||||
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True)
|
self.fields['ipv4'].queryset = IpList.objects.filter(
|
||||||
|
interface__isnull=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NewInterfaceForm(EditInterfaceForm):
|
class NewInterfaceForm(EditInterfaceForm):
|
||||||
|
"""Formulaire light, sans choix de l'ipv4; d'ajout d'une interface"""
|
||||||
class Meta(EditInterfaceForm.Meta):
|
class Meta(EditInterfaceForm.Meta):
|
||||||
fields = ['type','mac_address','details']
|
fields = ['type', 'mac_address', 'details']
|
||||||
|
|
||||||
|
|
||||||
class BaseEditInterfaceForm(EditInterfaceForm):
|
class BaseEditInterfaceForm(EditInterfaceForm):
|
||||||
|
"""Edition basique d'une interface. En fonction des droits,
|
||||||
|
ajoute ou non l'ensemble des ipv4 disponibles (infra)"""
|
||||||
class Meta(EditInterfaceForm.Meta):
|
class Meta(EditInterfaceForm.Meta):
|
||||||
fields = ['type','ipv4','mac_address','details']
|
fields = ['type', 'ipv4', 'mac_address', 'details']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
infra = kwargs.pop('infra')
|
infra = kwargs.pop('infra')
|
||||||
super(BaseEditInterfaceForm, self).__init__(*args, **kwargs)
|
super(BaseEditInterfaceForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4"
|
self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4"
|
||||||
if not infra:
|
if not infra:
|
||||||
self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False))
|
self.fields['type'].queryset = MachineType.objects.filter(
|
||||||
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False))
|
ip_type__in=IpType.objects.filter(need_infra=False)
|
||||||
|
)
|
||||||
|
self.fields['ipv4'].queryset = IpList.objects.filter(
|
||||||
|
interface__isnull=True
|
||||||
|
).filter(ip_type__in=IpType.objects.filter(need_infra=False))
|
||||||
# Add it's own address
|
# Add it's own address
|
||||||
self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance)
|
self.fields['ipv4'].queryset |= IpList.objects.filter(
|
||||||
|
interface=self.instance
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True)
|
self.fields['ipv4'].queryset = IpList.objects.filter(
|
||||||
self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance)
|
interface__isnull=True
|
||||||
|
)
|
||||||
|
self.fields['ipv4'].queryset |= IpList.objects.filter(
|
||||||
|
interface=self.instance
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AliasForm(ModelForm):
|
class AliasForm(ModelForm):
|
||||||
|
"""Ajout d'un alias (et edition), CNAME, contenant nom et extension"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Domain
|
model = Domain
|
||||||
fields = ['name','extension']
|
fields = ['name', 'extension']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
if 'infra' in kwargs:
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
infra = kwargs.pop('infra')
|
infra = kwargs.pop('infra')
|
||||||
super(AliasForm, self).__init__(*args, **kwargs)
|
super(AliasForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
|
if not infra:
|
||||||
|
self.fields['extension'].queryset = Extension.objects.filter(
|
||||||
|
need_infra=False
|
||||||
|
)
|
||||||
|
|
||||||
class DomainForm(AliasForm):
|
|
||||||
class Meta(AliasForm.Meta):
|
class DomainForm(ModelForm):
|
||||||
|
"""Ajout et edition d'un enregistrement de nom, relié à interface"""
|
||||||
|
class Meta:
|
||||||
|
model = Domain
|
||||||
fields = ['name']
|
fields = ['name']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
if 'user' in kwargs:
|
if 'user' in kwargs:
|
||||||
user = kwargs.pop('user')
|
user = kwargs.pop('user')
|
||||||
nb_machine = kwargs.pop('nb_machine')
|
|
||||||
initial = kwargs.get('initial', {})
|
initial = kwargs.get('initial', {})
|
||||||
initial['name'] = user.get_next_domain_name()
|
initial['name'] = user.get_next_domain_name()
|
||||||
kwargs['initial'] = initial
|
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):
|
class DelAliasForm(Form):
|
||||||
alias = forms.ModelMultipleChoiceField(queryset=Domain.objects.all(), label="Alias actuels", widget=forms.CheckboxSelectMultiple)
|
"""Suppression d'un ou plusieurs objets alias"""
|
||||||
|
alias = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=Domain.objects.all(),
|
||||||
|
label="Alias actuels",
|
||||||
|
widget=forms.CheckboxSelectMultiple
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
interface = kwargs.pop('interface')
|
interface = kwargs.pop('interface')
|
||||||
super(DelAliasForm, self).__init__(*args, **kwargs)
|
super(DelAliasForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['alias'].queryset = Domain.objects.filter(cname__in=Domain.objects.filter(interface_parent=interface))
|
self.fields['alias'].queryset = Domain.objects.filter(
|
||||||
|
cname__in=Domain.objects.filter(interface_parent=interface)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MachineTypeForm(ModelForm):
|
class MachineTypeForm(ModelForm):
|
||||||
|
"""Ajout et edition d'un machinetype, relié à un iptype"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = MachineType
|
model = MachineType
|
||||||
fields = ['type','ip_type']
|
fields = ['type', 'ip_type']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
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['type'].label = 'Type de machine à ajouter'
|
||||||
self.fields['ip_type'].label = "Type d'ip relié"
|
self.fields['ip_type'].label = "Type d'ip relié"
|
||||||
|
|
||||||
|
|
||||||
class DelMachineTypeForm(Form):
|
class DelMachineTypeForm(Form):
|
||||||
machinetypes = forms.ModelMultipleChoiceField(queryset=MachineType.objects.all(), label="Types de machines actuelles", widget=forms.CheckboxSelectMultiple)
|
"""Suppression d'un ou plusieurs machinetype"""
|
||||||
|
machinetypes = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=MachineType.objects.all(),
|
||||||
|
label="Types de machines actuelles",
|
||||||
|
widget=forms.CheckboxSelectMultiple
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class IpTypeForm(ModelForm):
|
class IpTypeForm(ModelForm):
|
||||||
|
"""Formulaire d'ajout d'un iptype. Pas d'edition de l'ip de start et de
|
||||||
|
stop après creation"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IpType
|
model = IpType
|
||||||
fields = ['type','extension','need_infra','domaine_ip_start','domaine_ip_stop', 'prefix_v6', 'vlan']
|
fields = ['type', 'extension', 'need_infra', 'domaine_ip_start',
|
||||||
|
'domaine_ip_stop', 'prefix_v6', 'vlan', 'ouverture_ports']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
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'
|
self.fields['type'].label = 'Type ip à ajouter'
|
||||||
|
|
||||||
|
|
||||||
class EditIpTypeForm(IpTypeForm):
|
class EditIpTypeForm(IpTypeForm):
|
||||||
|
"""Edition d'un iptype. Pas d'edition du rangev4 possible, car il faudrait
|
||||||
|
synchroniser les objets iplist"""
|
||||||
class Meta(IpTypeForm.Meta):
|
class Meta(IpTypeForm.Meta):
|
||||||
fields = ['extension','type','need_infra', 'prefix_v6', 'vlan']
|
fields = ['extension', 'type', 'need_infra', 'prefix_v6', 'vlan',
|
||||||
|
'ouverture_ports']
|
||||||
|
|
||||||
|
|
||||||
class DelIpTypeForm(Form):
|
class DelIpTypeForm(Form):
|
||||||
iptypes = forms.ModelMultipleChoiceField(queryset=IpType.objects.all(), label="Types d'ip actuelles", widget=forms.CheckboxSelectMultiple)
|
"""Suppression d'un ou plusieurs iptype"""
|
||||||
|
iptypes = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=IpType.objects.all(),
|
||||||
|
label="Types d'ip actuelles",
|
||||||
|
widget=forms.CheckboxSelectMultiple
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ExtensionForm(ModelForm):
|
class ExtensionForm(ModelForm):
|
||||||
|
"""Formulaire d'ajout et edition d'une extension"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Extension
|
model = Extension
|
||||||
fields = ['name', 'need_infra', 'origin']
|
fields = '__all__'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
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['name'].label = 'Extension à ajouter'
|
||||||
self.fields['origin'].label = 'Enregistrement A origin'
|
self.fields['origin'].label = 'Enregistrement A origin'
|
||||||
|
self.fields['origin_v6'].label = 'Enregistrement AAAA origin'
|
||||||
|
self.fields['soa'].label = 'En-tête SOA à utiliser'
|
||||||
|
|
||||||
|
|
||||||
class DelExtensionForm(Form):
|
class DelExtensionForm(Form):
|
||||||
extensions = forms.ModelMultipleChoiceField(queryset=Extension.objects.all(), label="Extensions actuelles", widget=forms.CheckboxSelectMultiple)
|
"""Suppression d'une ou plusieurs extensions"""
|
||||||
|
extensions = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=Extension.objects.all(),
|
||||||
|
label="Extensions actuelles",
|
||||||
|
widget=forms.CheckboxSelectMultiple
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SOAForm(ModelForm):
|
||||||
|
"""Ajout et edition d'un SOA"""
|
||||||
|
class Meta:
|
||||||
|
model = SOA
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
|
super(SOAForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class DelSOAForm(Form):
|
||||||
|
"""Suppression d'un ou plusieurs SOA"""
|
||||||
|
soa = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=SOA.objects.all(),
|
||||||
|
label="SOA actuels",
|
||||||
|
widget=forms.CheckboxSelectMultiple
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MxForm(ModelForm):
|
class MxForm(ModelForm):
|
||||||
|
"""Ajout et edition d'un MX"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Mx
|
model = Mx
|
||||||
fields = ['zone', 'priority', 'name']
|
fields = ['zone', 'priority', 'name']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(MxForm, self).__init__(*args, **kwargs)
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
self.fields['name'].queryset = Domain.objects.exclude(interface_parent=None)
|
super(MxForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
|
self.fields['name'].queryset = Domain.objects.exclude(
|
||||||
|
interface_parent=None
|
||||||
|
).select_related('extension')
|
||||||
|
|
||||||
|
|
||||||
class DelMxForm(Form):
|
class DelMxForm(Form):
|
||||||
mx = forms.ModelMultipleChoiceField(queryset=Mx.objects.all(), label="MX actuels", widget=forms.CheckboxSelectMultiple)
|
"""Suppression d'un ou plusieurs MX"""
|
||||||
|
mx = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=Mx.objects.all(),
|
||||||
|
label="MX actuels",
|
||||||
|
widget=forms.CheckboxSelectMultiple
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NsForm(ModelForm):
|
class NsForm(ModelForm):
|
||||||
|
"""Ajout d'un NS pour une zone
|
||||||
|
On exclue les CNAME dans les objets domain (interdit par la rfc)
|
||||||
|
donc on prend uniquemet """
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Ns
|
model = Ns
|
||||||
fields = ['zone', 'ns']
|
fields = ['zone', 'ns']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(NsForm, self).__init__(*args, **kwargs)
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
self.fields['ns'].queryset = Domain.objects.exclude(interface_parent=None)
|
super(NsForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
|
self.fields['ns'].queryset = Domain.objects.exclude(
|
||||||
|
interface_parent=None
|
||||||
|
).select_related('extension')
|
||||||
|
|
||||||
|
|
||||||
class DelNsForm(Form):
|
class DelNsForm(Form):
|
||||||
ns = forms.ModelMultipleChoiceField(queryset=Ns.objects.all(), label="Enregistrements NS actuels", widget=forms.CheckboxSelectMultiple)
|
"""Suppresion d'un ou plusieurs NS"""
|
||||||
|
ns = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=Ns.objects.all(),
|
||||||
|
label="Enregistrements NS actuels",
|
||||||
|
widget=forms.CheckboxSelectMultiple
|
||||||
|
)
|
||||||
|
|
||||||
class TextForm(ModelForm):
|
|
||||||
|
class TxtForm(ModelForm):
|
||||||
|
"""Ajout d'un txt pour une zone"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Text
|
model = Text
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
class DelTextForm(Form):
|
def __init__(self, *args, **kwargs):
|
||||||
text = forms.ModelMultipleChoiceField(queryset=Text.objects.all(), label="Enregistrements Text actuels", widget=forms.CheckboxSelectMultiple)
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
|
super(TxtForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class DelTxtForm(Form):
|
||||||
|
"""Suppression d'un ou plusieurs TXT"""
|
||||||
|
txt = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=Text.objects.all(),
|
||||||
|
label="Enregistrements Txt actuels",
|
||||||
|
widget=forms.CheckboxSelectMultiple
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NasForm(ModelForm):
|
class NasForm(ModelForm):
|
||||||
|
"""Ajout d'un type de nas (machine d'authentification,
|
||||||
|
swicths, bornes...)"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Nas
|
model = Nas
|
||||||
fields = '__all__'
|
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):
|
class DelNasForm(Form):
|
||||||
nas = forms.ModelMultipleChoiceField(queryset=Nas.objects.all(), label="Enregistrements Nas actuels", widget=forms.CheckboxSelectMultiple)
|
"""Suppression d'un ou plusieurs nas"""
|
||||||
|
nas = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=Nas.objects.all(),
|
||||||
|
label="Enregistrements Nas actuels",
|
||||||
|
widget=forms.CheckboxSelectMultiple
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ServiceForm(ModelForm):
|
class ServiceForm(ModelForm):
|
||||||
|
"""Ajout et edition d'une classe de service : dns, dhcp, etc"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Service
|
model = Service
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
|
super(ServiceForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
|
self.fields['servers'].queryset = Interface.objects.all()\
|
||||||
|
.select_related('domain__extension')
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
instance = super(ServiceForm, self).save(commit=False)
|
instance = super(ServiceForm, self).save(commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
|
@ -230,24 +421,63 @@ class ServiceForm(ModelForm):
|
||||||
instance.process_link(self.cleaned_data.get('servers'))
|
instance.process_link(self.cleaned_data.get('servers'))
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class DelServiceForm(Form):
|
class DelServiceForm(Form):
|
||||||
service = forms.ModelMultipleChoiceField(queryset=Service.objects.all(), label="Services actuels", widget=forms.CheckboxSelectMultiple)
|
"""Suppression d'un ou plusieurs service"""
|
||||||
|
service = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=Service.objects.all(),
|
||||||
|
label="Services actuels",
|
||||||
|
widget=forms.CheckboxSelectMultiple
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class VlanForm(ModelForm):
|
class VlanForm(ModelForm):
|
||||||
|
"""Ajout d'un vlan : id, nom"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Vlan
|
model = Vlan
|
||||||
fields = '__all__'
|
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):
|
class DelVlanForm(Form):
|
||||||
vlan = forms.ModelMultipleChoiceField(queryset=Vlan.objects.all(), label="Vlan actuels", widget=forms.CheckboxSelectMultiple)
|
"""Suppression d'un ou plusieurs vlans"""
|
||||||
|
vlan = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=Vlan.objects.all(),
|
||||||
|
label="Vlan actuels",
|
||||||
|
widget=forms.CheckboxSelectMultiple
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class EditOuverturePortConfigForm(ModelForm):
|
class EditOuverturePortConfigForm(ModelForm):
|
||||||
|
"""Edition de la liste des profils d'ouverture de ports
|
||||||
|
pour l'interface"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Interface
|
model = Interface
|
||||||
fields = ['port_lists']
|
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 EditOuverturePortListForm(ModelForm):
|
||||||
|
"""Edition de la liste des ports et profils d'ouverture
|
||||||
|
des ports"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = OuverturePortList
|
model = OuverturePortList
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
|
super(EditOuverturePortListForm, self).__init__(
|
||||||
|
*args,
|
||||||
|
prefix=prefix,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
21
machines/migrations/0060_iptype_ouverture_ports.py
Normal file
21
machines/migrations/0060_iptype_ouverture_ports.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-10-03 16:08
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('machines', '0059_iptype_prefix_v6'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='iptype',
|
||||||
|
name='ouverture_ports',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='machines.OuverturePortList'),
|
||||||
|
),
|
||||||
|
]
|
36
machines/migrations/0061_auto_20171015_2033.py
Normal file
36
machines/migrations/0061_auto_20171015_2033.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-10-15 18:33
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('machines', '0060_iptype_ouverture_ports'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='mx',
|
||||||
|
name='priority',
|
||||||
|
field=models.PositiveIntegerField(unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='ouvertureport',
|
||||||
|
name='begin',
|
||||||
|
field=models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(65535)]),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='ouvertureport',
|
||||||
|
name='end',
|
||||||
|
field=models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(65535)]),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='vlan',
|
||||||
|
name='vlan_id',
|
||||||
|
field=models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(4095)]),
|
||||||
|
),
|
||||||
|
]
|
20
machines/migrations/0062_extension_origin_v6.py
Normal file
20
machines/migrations/0062_extension_origin_v6.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-10-18 14:08
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('machines', '0061_auto_20171015_2033'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='extension',
|
||||||
|
name='origin_v6',
|
||||||
|
field=models.GenericIPAddressField(blank=True, null=True, protocol='IPv6'),
|
||||||
|
),
|
||||||
|
]
|
34
machines/migrations/0063_auto_20171020_0040.py
Normal file
34
machines/migrations/0063_auto_20171020_0040.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-10-19 22:40
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import machines.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('machines', '0062_extension_origin_v6'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SOA',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
('mail', models.EmailField(help_text='Email du contact pour la zone', max_length=254)),
|
||||||
|
('refresh', models.PositiveIntegerField(default=86400, help_text='Secondes avant que les DNS secondaires doivent demander le serial du DNS primaire pour détecter une modification')),
|
||||||
|
('retry', models.PositiveIntegerField(default=7200, help_text='Secondes avant que les DNS secondaires fassent une nouvelle demande de serial en cas de timeout du DNS primaire')),
|
||||||
|
('expire', models.PositiveIntegerField(default=3600000, help_text='Secondes après lesquelles les DNS secondaires arrêtent de de répondre aux requêtes en cas de timeout du DNS primaire')),
|
||||||
|
('ttl', models.PositiveIntegerField(default=172800, help_text='Time To Live')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='extension',
|
||||||
|
name='soa',
|
||||||
|
field=models.ForeignKey(default=machines.models.SOA.new_default_soa, on_delete=django.db.models.deletion.CASCADE, to='machines.SOA'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -23,44 +23,59 @@
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
import re
|
||||||
|
from netaddr import mac_bare, EUI, IPSet, IPRange, IPNetwork, IPAddress
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.signals import post_save, pre_delete, post_delete
|
from django.db.models.signals import post_save, post_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.forms import ValidationError
|
from django.forms import ValidationError
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.core.validators import MaxValueValidator
|
||||||
|
|
||||||
from macaddress.fields import MACAddressField
|
from macaddress.fields import MACAddressField
|
||||||
from netaddr import mac_bare, EUI, IPSet, IPRange, IPNetwork, IPAddress
|
|
||||||
from django.core.validators import MinValueValidator,MaxValueValidator
|
|
||||||
import re
|
|
||||||
from reversion import revisions as reversion
|
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
|
|
||||||
class Machine(models.Model):
|
class Machine(models.Model):
|
||||||
""" Class définissant une machine, object parent user, objets fils interfaces"""
|
""" Class définissant une machine, object parent user, objets fils
|
||||||
|
interfaces"""
|
||||||
PRETTY_NAME = "Machine"
|
PRETTY_NAME = "Machine"
|
||||||
|
|
||||||
user = models.ForeignKey('users.User', on_delete=models.PROTECT)
|
user = models.ForeignKey('users.User', on_delete=models.PROTECT)
|
||||||
name = models.CharField(max_length=255, help_text="Optionnel", blank=True, null=True)
|
name = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
help_text="Optionnel",
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
active = models.BooleanField(default=True)
|
active = models.BooleanField(default=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.user) + ' - ' + str(self.id) + ' - ' + str(self.name)
|
return str(self.user) + ' - ' + str(self.id) + ' - ' + str(self.name)
|
||||||
|
|
||||||
|
|
||||||
class MachineType(models.Model):
|
class MachineType(models.Model):
|
||||||
""" Type de machine, relié à un type d'ip, affecté aux interfaces"""
|
""" Type de machine, relié à un type d'ip, affecté aux interfaces"""
|
||||||
PRETTY_NAME = "Type de machine"
|
PRETTY_NAME = "Type de machine"
|
||||||
|
|
||||||
type = models.CharField(max_length=255)
|
type = models.CharField(max_length=255)
|
||||||
ip_type = models.ForeignKey('IpType', on_delete=models.PROTECT, blank=True, null=True)
|
ip_type = models.ForeignKey(
|
||||||
|
'IpType',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
|
||||||
def all_interfaces(self):
|
def all_interfaces(self):
|
||||||
""" Renvoie toutes les interfaces (cartes réseaux) de type machinetype"""
|
""" Renvoie toutes les interfaces (cartes réseaux) de type
|
||||||
|
machinetype"""
|
||||||
return Interface.objects.filter(type=self)
|
return Interface.objects.filter(type=self)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.type
|
return self.type
|
||||||
|
|
||||||
|
|
||||||
class IpType(models.Model):
|
class IpType(models.Model):
|
||||||
""" Type d'ip, définissant un range d'ip, affecté aux machine types"""
|
""" Type d'ip, définissant un range d'ip, affecté aux machine types"""
|
||||||
|
@ -71,13 +86,27 @@ class IpType(models.Model):
|
||||||
need_infra = models.BooleanField(default=False)
|
need_infra = models.BooleanField(default=False)
|
||||||
domaine_ip_start = models.GenericIPAddressField(protocol='IPv4')
|
domaine_ip_start = models.GenericIPAddressField(protocol='IPv4')
|
||||||
domaine_ip_stop = models.GenericIPAddressField(protocol='IPv4')
|
domaine_ip_stop = models.GenericIPAddressField(protocol='IPv4')
|
||||||
prefix_v6 = models.GenericIPAddressField(protocol='IPv6', null=True, blank=True)
|
prefix_v6 = models.GenericIPAddressField(
|
||||||
vlan = models.ForeignKey('Vlan', on_delete=models.PROTECT, blank=True, null=True)
|
protocol='IPv6',
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
vlan = models.ForeignKey(
|
||||||
|
'Vlan',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
ouverture_ports = models.ForeignKey(
|
||||||
|
'OuverturePortList',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def ip_range(self):
|
def ip_range(self):
|
||||||
""" Renvoie un objet IPRange à partir de l'objet IpType"""
|
""" Renvoie un objet IPRange à partir de l'objet IpType"""
|
||||||
return IPRange(self.domaine_ip_start, end=self.domaine_ip_stop)
|
return IPRange(self.domaine_ip_start, end=self.domaine_ip_stop)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def ip_set(self):
|
def ip_set(self):
|
||||||
|
@ -95,18 +124,22 @@ class IpType(models.Model):
|
||||||
|
|
||||||
def free_ip(self):
|
def free_ip(self):
|
||||||
""" Renvoie toutes les ip libres associées au type donné (self)"""
|
""" Renvoie toutes les ip libres associées au type donné (self)"""
|
||||||
return IpList.objects.filter(interface__isnull=True).filter(ip_type=self)
|
return IpList.objects.filter(
|
||||||
|
interface__isnull=True
|
||||||
|
).filter(ip_type=self)
|
||||||
|
|
||||||
def gen_ip_range(self):
|
def gen_ip_range(self):
|
||||||
""" Cree les IpList associées au type self. Parcours pédestrement et crée
|
""" Cree les IpList associées au type self. Parcours pédestrement et
|
||||||
les ip une par une. Si elles existent déjà, met à jour le type associé
|
crée les ip une par une. Si elles existent déjà, met à jour le type
|
||||||
à l'ip"""
|
associé à l'ip"""
|
||||||
# Creation du range d'ip dans les objets iplist
|
# Creation du range d'ip dans les objets iplist
|
||||||
networks = []
|
networks = []
|
||||||
for net in self.ip_range.cidrs():
|
for net in self.ip_range.cidrs():
|
||||||
networks += net.iter_hosts()
|
networks += net.iter_hosts()
|
||||||
ip_obj = [IpList(ip_type=self, ipv4=str(ip)) for ip in networks]
|
ip_obj = [IpList(ip_type=self, ipv4=str(ip)) for ip in networks]
|
||||||
listes_ip = IpList.objects.filter(ipv4__in=[str(ip) for ip in networks])
|
listes_ip = IpList.objects.filter(
|
||||||
|
ipv4__in=[str(ip) for ip in networks]
|
||||||
|
)
|
||||||
# Si il n'y a pas d'ip, on les crée
|
# Si il n'y a pas d'ip, on les crée
|
||||||
if not listes_ip:
|
if not listes_ip:
|
||||||
IpList.objects.bulk_create(ip_obj)
|
IpList.objects.bulk_create(ip_obj)
|
||||||
|
@ -116,9 +149,11 @@ class IpType(models.Model):
|
||||||
return
|
return
|
||||||
|
|
||||||
def del_ip_range(self):
|
def del_ip_range(self):
|
||||||
""" Methode dépréciée, IpList est en mode cascade et supprimé automatiquement"""
|
""" Methode dépréciée, IpList est en mode cascade et supprimé
|
||||||
|
automatiquement"""
|
||||||
if Interface.objects.filter(ipv4__in=self.ip_objects()):
|
if Interface.objects.filter(ipv4__in=self.ip_objects()):
|
||||||
raise ValidationError("Une ou plusieurs ip du range sont affectées, impossible de supprimer le range")
|
raise ValidationError("Une ou plusieurs ip du range sont\
|
||||||
|
affectées, impossible de supprimer le range")
|
||||||
for ip in self.ip_objects():
|
for ip in self.ip_objects():
|
||||||
ip.delete()
|
ip.delete()
|
||||||
|
|
||||||
|
@ -132,11 +167,13 @@ class IpType(models.Model):
|
||||||
raise ValidationError("Domaine end doit être après start...")
|
raise ValidationError("Domaine end doit être après start...")
|
||||||
# On ne crée pas plus grand qu'un /16
|
# On ne crée pas plus grand qu'un /16
|
||||||
if self.ip_range.size > 65536:
|
if self.ip_range.size > 65536:
|
||||||
raise ValidationError("Le range est trop gros, vous ne devez pas créer plus grand qu'un /16")
|
raise ValidationError("Le range est trop gros, vous ne devez\
|
||||||
|
pas créer plus grand qu'un /16")
|
||||||
# On check que les / ne se recoupent pas
|
# On check que les / ne se recoupent pas
|
||||||
for element in IpType.objects.all().exclude(pk=self.pk):
|
for element in IpType.objects.all().exclude(pk=self.pk):
|
||||||
if not self.ip_set.isdisjoint(element.ip_set):
|
if not self.ip_set.isdisjoint(element.ip_set):
|
||||||
raise ValidationError("Le range indiqué n'est pas disjoint des ranges existants")
|
raise ValidationError("Le range indiqué n'est pas disjoint\
|
||||||
|
des ranges existants")
|
||||||
# On formate le prefix v6
|
# On formate le prefix v6
|
||||||
if self.prefix_v6:
|
if self.prefix_v6:
|
||||||
self.prefix_v6 = str(IPNetwork(self.prefix_v6 + '/64').network)
|
self.prefix_v6 = str(IPNetwork(self.prefix_v6 + '/64').network)
|
||||||
|
@ -149,17 +186,20 @@ class IpType(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.type
|
return self.type
|
||||||
|
|
||||||
|
|
||||||
class Vlan(models.Model):
|
class Vlan(models.Model):
|
||||||
""" Un vlan : vlan_id et nom"""
|
""" Un vlan : vlan_id et nom
|
||||||
|
On limite le vlan id entre 0 et 4096, comme défini par la norme"""
|
||||||
PRETTY_NAME = "Vlans"
|
PRETTY_NAME = "Vlans"
|
||||||
|
|
||||||
vlan_id = models.IntegerField()
|
vlan_id = models.PositiveIntegerField(validators=[MaxValueValidator(4095)])
|
||||||
name = models.CharField(max_length=256)
|
name = models.CharField(max_length=256)
|
||||||
comment = models.CharField(max_length=256, blank=True)
|
comment = models.CharField(max_length=256, blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Nas(models.Model):
|
class Nas(models.Model):
|
||||||
""" Les nas. Associé à un machine_type.
|
""" Les nas. Associé à un machine_type.
|
||||||
Permet aussi de régler le port_access_mode (802.1X ou mac-address) pour
|
Permet aussi de régler le port_access_mode (802.1X ou mac-address) pour
|
||||||
|
@ -173,48 +213,161 @@ class Nas(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
name = models.CharField(max_length=255, unique=True)
|
name = models.CharField(max_length=255, unique=True)
|
||||||
nas_type = models.ForeignKey('MachineType', on_delete=models.PROTECT, related_name='nas_type')
|
nas_type = models.ForeignKey(
|
||||||
machine_type = models.ForeignKey('MachineType', on_delete=models.PROTECT, related_name='machinetype_on_nas')
|
'MachineType',
|
||||||
port_access_mode = models.CharField(choices=AUTH, default=default_mode, max_length=32)
|
on_delete=models.PROTECT,
|
||||||
|
related_name='nas_type'
|
||||||
|
)
|
||||||
|
machine_type = models.ForeignKey(
|
||||||
|
'MachineType',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='machinetype_on_nas'
|
||||||
|
)
|
||||||
|
port_access_mode = models.CharField(
|
||||||
|
choices=AUTH,
|
||||||
|
default=default_mode,
|
||||||
|
max_length=32
|
||||||
|
)
|
||||||
autocapture_mac = models.BooleanField(default=False)
|
autocapture_mac = models.BooleanField(default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class SOA(models.Model):
|
||||||
|
"""
|
||||||
|
Un enregistrement SOA associé à une extension
|
||||||
|
Les valeurs par défault viennent des recommandations RIPE :
|
||||||
|
https://www.ripe.net/publications/docs/ripe-203
|
||||||
|
"""
|
||||||
|
PRETTY_NAME = "Enregistrement SOA"
|
||||||
|
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
mail = models.EmailField(
|
||||||
|
help_text='Email du contact pour la zone'
|
||||||
|
)
|
||||||
|
refresh = models.PositiveIntegerField(
|
||||||
|
default=86400, # 24 hours
|
||||||
|
help_text='Secondes avant que les DNS secondaires doivent demander le\
|
||||||
|
serial du DNS primaire pour détecter une modification'
|
||||||
|
)
|
||||||
|
retry = models.PositiveIntegerField(
|
||||||
|
default=7200, # 2 hours
|
||||||
|
help_text='Secondes avant que les DNS secondaires fassent une nouvelle\
|
||||||
|
demande de serial en cas de timeout du DNS primaire'
|
||||||
|
)
|
||||||
|
expire = models.PositiveIntegerField(
|
||||||
|
default=3600000, # 1000 hours
|
||||||
|
help_text='Secondes après lesquelles les DNS secondaires arrêtent de\
|
||||||
|
de répondre aux requêtes en cas de timeout du DNS primaire'
|
||||||
|
)
|
||||||
|
ttl = models.PositiveIntegerField(
|
||||||
|
default=172800, # 2 days
|
||||||
|
help_text='Time To Live'
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.name)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def dns_soa_param(self):
|
||||||
|
"""
|
||||||
|
Renvoie la partie de l'enregistrement SOA correspondant aux champs :
|
||||||
|
<refresh> ; refresh
|
||||||
|
<retry> ; retry
|
||||||
|
<expire> ; expire
|
||||||
|
<ttl> ; TTL
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
' {refresh}; refresh\n'
|
||||||
|
' {retry}; retry\n'
|
||||||
|
' {expire}; expire\n'
|
||||||
|
' {ttl}; TTL'
|
||||||
|
).format(
|
||||||
|
refresh=str(self.refresh).ljust(12),
|
||||||
|
retry=str(self.retry).ljust(12),
|
||||||
|
expire=str(self.expire).ljust(12),
|
||||||
|
ttl=str(self.ttl).ljust(12)
|
||||||
|
)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def dns_soa_mail(self):
|
||||||
|
""" Renvoie le mail dans l'enregistrement SOA """
|
||||||
|
mail_fields = str(self.mail).split('@')
|
||||||
|
return mail_fields[0].replace('.', '\\.') + '.' + mail_fields[1] + '.'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new_default_soa(cls):
|
||||||
|
""" Fonction pour créer un SOA par défaut, utile pour les nouvelles
|
||||||
|
extensions .
|
||||||
|
/!\ Ne jamais supprimer ou renommer cette fonction car elle est
|
||||||
|
utilisée dans les migrations de la BDD. """
|
||||||
|
return cls.objects.get_or_create(name="SOA to edit", mail="postmaser@example.com")[0].pk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Extension(models.Model):
|
class Extension(models.Model):
|
||||||
""" Extension dns type example.org. Précise si tout le monde peut l'utiliser,
|
""" Extension dns type example.org. Précise si tout le monde peut
|
||||||
associé à un origin (ip d'origine)"""
|
l'utiliser, associé à un origin (ip d'origine)"""
|
||||||
PRETTY_NAME = "Extensions dns"
|
PRETTY_NAME = "Extensions dns"
|
||||||
|
|
||||||
name = models.CharField(max_length=255, unique=True)
|
name = models.CharField(max_length=255, unique=True)
|
||||||
need_infra = models.BooleanField(default=False)
|
need_infra = models.BooleanField(default=False)
|
||||||
origin = models.OneToOneField('IpList', on_delete=models.PROTECT, blank=True, null=True)
|
origin = models.OneToOneField(
|
||||||
|
'IpList',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
origin_v6 = models.GenericIPAddressField(
|
||||||
|
protocol='IPv6',
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
soa = models.ForeignKey(
|
||||||
|
'SOA',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
default=SOA.new_default_soa
|
||||||
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def dns_entry(self):
|
def dns_entry(self):
|
||||||
""" Une entrée DNS A"""
|
""" Une entrée DNS A et AAAA sur origin (zone self)"""
|
||||||
return "@ IN A " + str(self.origin)
|
entry = ""
|
||||||
|
if self.origin:
|
||||||
|
entry += "@ IN A " + str(self.origin)
|
||||||
|
if self.origin_v6:
|
||||||
|
if entry:
|
||||||
|
entry += "\n"
|
||||||
|
entry += "@ IN AAAA " + str(self.origin_v6)
|
||||||
|
return entry
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Mx(models.Model):
|
class Mx(models.Model):
|
||||||
""" Entrées des MX. Enregistre la zone (extension) associée et la priorité
|
""" Entrées des MX. Enregistre la zone (extension) associée et la
|
||||||
|
priorité
|
||||||
Todo : pouvoir associer un MX à une interface """
|
Todo : pouvoir associer un MX à une interface """
|
||||||
PRETTY_NAME = "Enregistrements MX"
|
PRETTY_NAME = "Enregistrements MX"
|
||||||
|
|
||||||
zone = models.ForeignKey('Extension', on_delete=models.PROTECT)
|
zone = models.ForeignKey('Extension', on_delete=models.PROTECT)
|
||||||
priority = models.IntegerField(unique=True)
|
priority = models.PositiveIntegerField(unique=True)
|
||||||
name = models.OneToOneField('Domain', on_delete=models.PROTECT)
|
name = models.OneToOneField('Domain', on_delete=models.PROTECT)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def dns_entry(self):
|
def dns_entry(self):
|
||||||
return "@ IN MX " + str(self.priority) + " " + str(self.name)
|
"""Renvoie l'entrée DNS complète pour un MX à mettre dans les
|
||||||
|
fichiers de zones"""
|
||||||
|
return "@ IN MX " + str(self.priority).ljust(3) + " " + str(self.name)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.zone) + ' ' + str(self.priority) + ' ' + str(self.name)
|
return str(self.zone) + ' ' + str(self.priority) + ' ' + str(self.name)
|
||||||
|
|
||||||
|
|
||||||
class Ns(models.Model):
|
class Ns(models.Model):
|
||||||
|
"""Liste des enregistrements name servers par zone considéérée"""
|
||||||
PRETTY_NAME = "Enregistrements NS"
|
PRETTY_NAME = "Enregistrements NS"
|
||||||
|
|
||||||
zone = models.ForeignKey('Extension', on_delete=models.PROTECT)
|
zone = models.ForeignKey('Extension', on_delete=models.PROTECT)
|
||||||
|
@ -222,36 +375,47 @@ class Ns(models.Model):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def dns_entry(self):
|
def dns_entry(self):
|
||||||
return "@ IN NS " + str(self.ns)
|
"""Renvoie un enregistrement NS complet pour les filezones"""
|
||||||
|
return "@ IN NS " + str(self.ns)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.zone) + ' ' + str(self.ns)
|
return str(self.zone) + ' ' + str(self.ns)
|
||||||
|
|
||||||
|
|
||||||
class Text(models.Model):
|
class Text(models.Model):
|
||||||
""" Un enregistrement TXT associé à une extension"""
|
""" Un enregistrement TXT associé à une extension"""
|
||||||
PRETTY_NAME = "Enregistrement text"
|
PRETTY_NAME = "Enregistrement TXT"
|
||||||
|
|
||||||
zone = models.ForeignKey('Extension', on_delete=models.PROTECT)
|
zone = models.ForeignKey('Extension', on_delete=models.PROTECT)
|
||||||
field1 = models.CharField(max_length=255)
|
field1 = models.CharField(max_length=255)
|
||||||
field2 = models.CharField(max_length=255)
|
field2 = models.CharField(max_length=255)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.zone) + " : " + str(self.field1) + " " + str(self.field2)
|
return str(self.zone) + " : " + str(self.field1) + " " +\
|
||||||
|
str(self.field2)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def dns_entry(self):
|
def dns_entry(self):
|
||||||
return str(self.field1) + " IN TXT " + str(self.field2)
|
"""Renvoie l'enregistrement TXT complet pour le fichier de zone"""
|
||||||
|
return str(self.field1).ljust(15) + " IN TXT " + str(self.field2)
|
||||||
|
|
||||||
|
|
||||||
class Interface(models.Model):
|
class Interface(models.Model):
|
||||||
""" Une interface. Objet clef de l'application machine :
|
""" Une interface. Objet clef de l'application machine :
|
||||||
- une address mac unique. Possibilité de la rendre unique avec le typemachine
|
- une address mac unique. Possibilité de la rendre unique avec le
|
||||||
|
typemachine
|
||||||
- une onetoone vers IpList pour attribution ipv4
|
- une onetoone vers IpList pour attribution ipv4
|
||||||
- le type parent associé au range ip et à l'extension
|
- le type parent associé au range ip et à l'extension
|
||||||
- un objet domain associé contenant son nom
|
- un objet domain associé contenant son nom
|
||||||
- la liste des ports oiuvert"""
|
- la liste des ports oiuvert"""
|
||||||
PRETTY_NAME = "Interface"
|
PRETTY_NAME = "Interface"
|
||||||
|
|
||||||
ipv4 = models.OneToOneField('IpList', on_delete=models.PROTECT, blank=True, null=True)
|
ipv4 = models.OneToOneField(
|
||||||
|
'IpList',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
mac_address = MACAddressField(integer=False, unique=True)
|
mac_address = MACAddressField(integer=False, unique=True)
|
||||||
machine = models.ForeignKey('Machine', on_delete=models.CASCADE)
|
machine = models.ForeignKey('Machine', on_delete=models.CASCADE)
|
||||||
type = models.ForeignKey('MachineType', on_delete=models.PROTECT)
|
type = models.ForeignKey('MachineType', on_delete=models.PROTECT)
|
||||||
|
@ -265,12 +429,14 @@ class Interface(models.Model):
|
||||||
user = self.machine.user
|
user = self.machine.user
|
||||||
return machine.active and user.has_access()
|
return machine.active and user.has_access()
|
||||||
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def ipv6_object(self):
|
def ipv6_object(self):
|
||||||
""" Renvoie un objet type ipv6 à partir du prefix associé à l'iptype parent"""
|
""" Renvoie un objet type ipv6 à partir du prefix associé à
|
||||||
|
l'iptype parent"""
|
||||||
if self.type.ip_type.prefix_v6:
|
if self.type.ip_type.prefix_v6:
|
||||||
return EUI(self.mac_address).ipv6(IPNetwork(self.type.ip_type.prefix_v6).network)
|
return EUI(self.mac_address).ipv6(
|
||||||
|
IPNetwork(self.type.ip_type.prefix_v6).network
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -284,15 +450,25 @@ class Interface(models.Model):
|
||||||
return str(EUI(self.mac_address, dialect=mac_bare)).lower()
|
return str(EUI(self.mac_address, dialect=mac_bare)).lower()
|
||||||
|
|
||||||
def filter_macaddress(self):
|
def filter_macaddress(self):
|
||||||
""" Tente un formatage mac_bare, si échoue, lève une erreur de validation"""
|
""" Tente un formatage mac_bare, si échoue, lève une erreur de
|
||||||
|
validation"""
|
||||||
try:
|
try:
|
||||||
self.mac_address = str(EUI(self.mac_address))
|
self.mac_address = str(EUI(self.mac_address))
|
||||||
except :
|
except:
|
||||||
raise ValidationError("La mac donnée est invalide")
|
raise ValidationError("La mac donnée est invalide")
|
||||||
|
|
||||||
def clean(self, *args, **kwargs):
|
def clean(self, *args, **kwargs):
|
||||||
""" Formate l'addresse mac en mac_bare (fonction filter_mac)
|
""" Formate l'addresse mac en mac_bare (fonction filter_mac)
|
||||||
et assigne une ipv4 dans le bon range si inexistante ou incohérente"""
|
et assigne une ipv4 dans le bon range si inexistante ou incohérente"""
|
||||||
|
# If type was an invalid value, django won't create an attribute type
|
||||||
|
# but try clean() as we may be able to create it from another value
|
||||||
|
# so even if the error as yet been detected at this point, django
|
||||||
|
# continues because the error might not prevent us from creating the
|
||||||
|
# instance.
|
||||||
|
# But in our case, it's impossible to create a type value so we raise
|
||||||
|
# the error.
|
||||||
|
if not hasattr(self, 'type') :
|
||||||
|
raise ValidationError("Le type d'ip choisi n'est pas valide")
|
||||||
self.filter_macaddress()
|
self.filter_macaddress()
|
||||||
self.mac_address = str(EUI(self.mac_address)) or None
|
self.mac_address = str(EUI(self.mac_address)) or None
|
||||||
if not self.ipv4 or self.type.ip_type != self.ipv4.ip_type:
|
if not self.ipv4 or self.type.ip_type != self.ipv4.ip_type:
|
||||||
|
@ -305,7 +481,8 @@ class Interface(models.Model):
|
||||||
if free_ips:
|
if free_ips:
|
||||||
self.ipv4 = free_ips[0]
|
self.ipv4 = free_ips[0]
|
||||||
else:
|
else:
|
||||||
raise ValidationError("Il n'y a plus d'ip disponibles dans le slash")
|
raise ValidationError("Il n'y a plus d'ip disponibles\
|
||||||
|
dans le slash")
|
||||||
return
|
return
|
||||||
|
|
||||||
def unassign_ipv4(self):
|
def unassign_ipv4(self):
|
||||||
|
@ -320,8 +497,10 @@ class Interface(models.Model):
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.filter_macaddress()
|
self.filter_macaddress()
|
||||||
# On verifie la cohérence en forçant l'extension par la méthode
|
# On verifie la cohérence en forçant l'extension par la méthode
|
||||||
if self.type.ip_type != self.ipv4.ip_type:
|
if self.ipv4:
|
||||||
raise ValidationError("L'ipv4 et le type de la machine ne correspondent pas")
|
if self.type.ip_type != self.ipv4.ip_type:
|
||||||
|
raise ValidationError("L'ipv4 et le type de la machine ne\
|
||||||
|
correspondent pas")
|
||||||
super(Interface, self).save(*args, **kwargs)
|
super(Interface, self).save(*args, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -340,18 +519,34 @@ class Interface(models.Model):
|
||||||
|
|
||||||
def may_have_port_open(self):
|
def may_have_port_open(self):
|
||||||
""" True si l'interface a une ip et une ip publique.
|
""" True si l'interface a une ip et une ip publique.
|
||||||
Permet de ne pas exporter des ouvertures sur des ip privées (useless)"""
|
Permet de ne pas exporter des ouvertures sur des ip privées
|
||||||
|
(useless)"""
|
||||||
return self.ipv4 and not self.has_private_ip()
|
return self.ipv4 and not self.has_private_ip()
|
||||||
|
|
||||||
|
|
||||||
class Domain(models.Model):
|
class Domain(models.Model):
|
||||||
""" Objet domain. Enregistrement A et CNAME en même temps : permet de stocker les
|
""" Objet domain. Enregistrement A et CNAME en même temps : permet de
|
||||||
alias et les nom de machines, suivant si interface_parent ou cname sont remplis"""
|
stocker les alias et les nom de machines, suivant si interface_parent
|
||||||
|
ou cname sont remplis"""
|
||||||
PRETTY_NAME = "Domaine dns"
|
PRETTY_NAME = "Domaine dns"
|
||||||
|
|
||||||
interface_parent = models.OneToOneField('Interface', on_delete=models.CASCADE, blank=True, null=True)
|
interface_parent = models.OneToOneField(
|
||||||
name = models.CharField(help_text="Obligatoire et unique, ne doit pas comporter de points", max_length=255)
|
'Interface',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
name = models.CharField(
|
||||||
|
help_text="Obligatoire et unique, ne doit pas comporter de points",
|
||||||
|
max_length=255
|
||||||
|
)
|
||||||
extension = models.ForeignKey('Extension', on_delete=models.PROTECT)
|
extension = models.ForeignKey('Extension', on_delete=models.PROTECT)
|
||||||
cname = models.ForeignKey('self', null=True, blank=True, related_name='related_domain')
|
cname = models.ForeignKey(
|
||||||
|
'self',
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name='related_domain'
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = (("name", "extension"),)
|
unique_together = (("name", "extension"),)
|
||||||
|
@ -361,7 +556,7 @@ class Domain(models.Model):
|
||||||
Retourne l'extension propre si c'est un cname, renvoie None sinon"""
|
Retourne l'extension propre si c'est un cname, renvoie None sinon"""
|
||||||
if self.interface_parent:
|
if self.interface_parent:
|
||||||
return self.interface_parent.type.ip_type.extension
|
return self.interface_parent.type.ip_type.extension
|
||||||
elif hasattr(self,'extension'):
|
elif hasattr(self, 'extension'):
|
||||||
return self.extension
|
return self.extension
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
@ -370,21 +565,26 @@ class Domain(models.Model):
|
||||||
""" Validation :
|
""" Validation :
|
||||||
- l'objet est bien soit A soit CNAME
|
- l'objet est bien soit A soit CNAME
|
||||||
- le cname est pas pointé sur lui-même
|
- le cname est pas pointé sur lui-même
|
||||||
- le nom contient bien les caractères autorisés par la norme dns et moins de 63 caractères au total
|
- le nom contient bien les caractères autorisés par la norme
|
||||||
|
dns et moins de 63 caractères au total
|
||||||
- le couple nom/extension est bien unique"""
|
- le couple nom/extension est bien unique"""
|
||||||
if self.get_extension():
|
if self.get_extension():
|
||||||
self.extension=self.get_extension()
|
self.extension = self.get_extension()
|
||||||
""" Validation du nom de domaine, extensions dans type de machine, prefixe pas plus long que 63 caractères """
|
|
||||||
if self.interface_parent and self.cname:
|
if self.interface_parent and self.cname:
|
||||||
raise ValidationError("On ne peut créer à la fois A et CNAME")
|
raise ValidationError("On ne peut créer à la fois A et CNAME")
|
||||||
if self.cname==self:
|
if self.cname == self:
|
||||||
raise ValidationError("On ne peut créer un cname sur lui même")
|
raise ValidationError("On ne peut créer un cname sur lui même")
|
||||||
HOSTNAME_LABEL_PATTERN = re.compile("(?!-)[A-Z\d-]+(?<!-)$", re.IGNORECASE)
|
HOSTNAME_LABEL_PATTERN = re.compile(
|
||||||
|
"(?!-)[A-Z\d-]+(?<!-)$",
|
||||||
|
re.IGNORECASE
|
||||||
|
)
|
||||||
dns = self.name.lower()
|
dns = self.name.lower()
|
||||||
if len(dns) > 63:
|
if len(dns) > 63:
|
||||||
raise ValidationError("Le nom de domaine %s est trop long (maximum de 63 caractères)." % dns)
|
raise ValidationError("Le nom de domaine %s est trop long\
|
||||||
|
(maximum de 63 caractères)." % dns)
|
||||||
if not HOSTNAME_LABEL_PATTERN.match(dns):
|
if not HOSTNAME_LABEL_PATTERN.match(dns):
|
||||||
raise ValidationError("Ce nom de domaine %s contient des carractères interdits." % dns)
|
raise ValidationError("Ce nom de domaine %s contient des\
|
||||||
|
carractères interdits." % dns)
|
||||||
self.validate_unique()
|
self.validate_unique()
|
||||||
super(Domain, self).clean()
|
super(Domain, self).clean()
|
||||||
|
|
||||||
|
@ -392,10 +592,11 @@ class Domain(models.Model):
|
||||||
def dns_entry(self):
|
def dns_entry(self):
|
||||||
""" Une entrée DNS"""
|
""" Une entrée DNS"""
|
||||||
if self.cname:
|
if self.cname:
|
||||||
return str(self.name) + " IN CNAME " + str(self.cname) + "."
|
return str(self.name).ljust(15) + " IN CNAME " + str(self.cname) + "."
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
""" Empèche le save sans extension valide. Force à avoir appellé clean avant"""
|
""" Empèche le save sans extension valide.
|
||||||
|
Force à avoir appellé clean avant"""
|
||||||
if not self.get_extension():
|
if not self.get_extension():
|
||||||
raise ValidationError("Extension invalide")
|
raise ValidationError("Extension invalide")
|
||||||
self.full_clean()
|
self.full_clean()
|
||||||
|
@ -404,6 +605,7 @@ class Domain(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.name) + str(self.extension)
|
return str(self.name) + str(self.extension)
|
||||||
|
|
||||||
|
|
||||||
class IpList(models.Model):
|
class IpList(models.Model):
|
||||||
PRETTY_NAME = "Addresses ipv4"
|
PRETTY_NAME = "Addresses ipv4"
|
||||||
|
|
||||||
|
@ -412,13 +614,15 @@ class IpList(models.Model):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def need_infra(self):
|
def need_infra(self):
|
||||||
""" Permet de savoir si un user basique peut assigner cette ip ou non"""
|
""" Permet de savoir si un user basique peut assigner cette ip ou
|
||||||
|
non"""
|
||||||
return self.ip_type.need_infra
|
return self.ip_type.need_infra
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
""" Erreur si l'ip_type est incorrect"""
|
""" Erreur si l'ip_type est incorrect"""
|
||||||
if not str(self.ipv4) in self.ip_type.ip_set_as_str:
|
if not str(self.ipv4) in self.ip_type.ip_set_as_str:
|
||||||
raise ValidationError("L'ipv4 et le range de l'iptype ne correspondent pas!")
|
raise ValidationError("L'ipv4 et le range de l'iptype ne\
|
||||||
|
correspondent pas!")
|
||||||
return
|
return
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
@ -428,24 +632,38 @@ class IpList(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.ipv4
|
return self.ipv4
|
||||||
|
|
||||||
|
|
||||||
class Service(models.Model):
|
class Service(models.Model):
|
||||||
""" Definition d'un service (dhcp, dns, etc)"""
|
""" Definition d'un service (dhcp, dns, etc)"""
|
||||||
|
PRETTY_NAME = "Services à générer (dhcp, dns, etc)"
|
||||||
|
|
||||||
service_type = models.CharField(max_length=255, blank=True, unique=True)
|
service_type = models.CharField(max_length=255, blank=True, unique=True)
|
||||||
min_time_regen = models.DurationField(default=timedelta(minutes=1), help_text="Temps minimal avant nouvelle génération du service")
|
min_time_regen = models.DurationField(
|
||||||
regular_time_regen = models.DurationField(default=timedelta(hours=1), help_text="Temps maximal avant nouvelle génération du service")
|
default=timedelta(minutes=1),
|
||||||
|
help_text="Temps minimal avant nouvelle génération du service"
|
||||||
|
)
|
||||||
|
regular_time_regen = models.DurationField(
|
||||||
|
default=timedelta(hours=1),
|
||||||
|
help_text="Temps maximal avant nouvelle génération du service"
|
||||||
|
)
|
||||||
servers = models.ManyToManyField('Interface', through='Service_link')
|
servers = models.ManyToManyField('Interface', through='Service_link')
|
||||||
|
|
||||||
def ask_regen(self):
|
def ask_regen(self):
|
||||||
""" Marque à True la demande de régénération pour un service x """
|
""" Marque à True la demande de régénération pour un service x """
|
||||||
Service_link.objects.filter(service=self).exclude(asked_regen=True).update(asked_regen=True)
|
Service_link.objects.filter(service=self).exclude(asked_regen=True)\
|
||||||
|
.update(asked_regen=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
def process_link(self, servers):
|
def process_link(self, servers):
|
||||||
""" Django ne peut créer lui meme les relations manytomany avec table intermediaire explicite"""
|
""" Django ne peut créer lui meme les relations manytomany avec table
|
||||||
for serv in servers.exclude(pk__in=Interface.objects.filter(service=self)):
|
intermediaire explicite"""
|
||||||
|
for serv in servers.exclude(
|
||||||
|
pk__in=Interface.objects.filter(service=self)
|
||||||
|
):
|
||||||
link = Service_link(service=self, server=serv)
|
link = Service_link(service=self, server=serv)
|
||||||
link.save()
|
link.save()
|
||||||
Service_link.objects.filter(service=self).exclude(server__in=servers).delete()
|
Service_link.objects.filter(service=self).exclude(server__in=servers)\
|
||||||
|
.delete()
|
||||||
return
|
return
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
@ -454,15 +672,20 @@ class Service(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.service_type)
|
return str(self.service_type)
|
||||||
|
|
||||||
|
|
||||||
def regen(service):
|
def regen(service):
|
||||||
""" Fonction externe pour régérération d'un service, prend un objet service en arg"""
|
""" Fonction externe pour régérération d'un service, prend un objet service
|
||||||
|
en arg"""
|
||||||
obj = Service.objects.filter(service_type=service)
|
obj = Service.objects.filter(service_type=service)
|
||||||
if obj:
|
if obj:
|
||||||
obj[0].ask_regen()
|
obj[0].ask_regen()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
class Service_link(models.Model):
|
class Service_link(models.Model):
|
||||||
""" Definition du lien entre serveurs et services"""
|
""" Definition du lien entre serveurs et services"""
|
||||||
|
PRETTY_NAME = "Relation entre service et serveur"
|
||||||
|
|
||||||
service = models.ForeignKey('Service', on_delete=models.CASCADE)
|
service = models.ForeignKey('Service', on_delete=models.CASCADE)
|
||||||
server = models.ForeignKey('Interface', on_delete=models.CASCADE)
|
server = models.ForeignKey('Interface', on_delete=models.CASCADE)
|
||||||
last_regen = models.DateTimeField(auto_now_add=True)
|
last_regen = models.DateTimeField(auto_now_add=True)
|
||||||
|
@ -475,11 +698,16 @@ class Service_link(models.Model):
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def need_regen(self):
|
def need_regen(self):
|
||||||
""" Décide si le temps minimal écoulé est suffisant pour provoquer une régénération de service"""
|
""" Décide si le temps minimal écoulé est suffisant pour provoquer une
|
||||||
if (self.asked_regen and (self.last_regen + self.service.min_time_regen) < timezone.now()) or (self.last_regen + self.service.regular_time_regen) < timezone.now():
|
régénération de service"""
|
||||||
return True
|
return bool(
|
||||||
else:
|
(self.asked_regen and (
|
||||||
return False
|
self.last_regen + self.service.min_time_regen
|
||||||
|
) < timezone.now()
|
||||||
|
) or (
|
||||||
|
self.last_regen + self.service.regular_time_regen
|
||||||
|
) < timezone.now()
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.server) + " " + str(self.service)
|
return str(self.server) + " " + str(self.service)
|
||||||
|
@ -487,22 +715,43 @@ class Service_link(models.Model):
|
||||||
|
|
||||||
class OuverturePortList(models.Model):
|
class OuverturePortList(models.Model):
|
||||||
"""Liste des ports ouverts sur une interface."""
|
"""Liste des ports ouverts sur une interface."""
|
||||||
name = models.CharField(help_text="Nom de la configuration des ports.", max_length=255)
|
PRETTY_NAME = "Profil d'ouverture de ports"
|
||||||
|
|
||||||
|
name = models.CharField(
|
||||||
|
help_text="Nom de la configuration des ports.",
|
||||||
|
max_length=255
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def tcp_ports_in(self):
|
def tcp_ports_in(self):
|
||||||
return self.ouvertureport_set.filter(protocole=OuverturePort.TCP, io=OuverturePort.IN)
|
"""Renvoie la liste des ports ouverts en TCP IN pour ce profil"""
|
||||||
|
return self.ouvertureport_set.filter(
|
||||||
|
protocole=OuverturePort.TCP,
|
||||||
|
io=OuverturePort.IN
|
||||||
|
)
|
||||||
|
|
||||||
def udp_ports_in(self):
|
def udp_ports_in(self):
|
||||||
return self.ouvertureport_set.filter(protocole=OuverturePort.UDP, io=OuverturePort.IN)
|
"""Renvoie la liste des ports ouverts en UDP IN pour ce profil"""
|
||||||
|
return self.ouvertureport_set.filter(
|
||||||
|
protocole=OuverturePort.UDP,
|
||||||
|
io=OuverturePort.IN
|
||||||
|
)
|
||||||
|
|
||||||
def tcp_ports_out(self):
|
def tcp_ports_out(self):
|
||||||
return self.ouvertureport_set.filter(protocole=OuverturePort.TCP, io=OuverturePort.OUT)
|
"""Renvoie la liste des ports ouverts en TCP OUT pour ce profil"""
|
||||||
|
return self.ouvertureport_set.filter(
|
||||||
|
protocole=OuverturePort.TCP,
|
||||||
|
io=OuverturePort.OUT
|
||||||
|
)
|
||||||
|
|
||||||
def udp_ports_out(self):
|
def udp_ports_out(self):
|
||||||
return self.ouvertureport_set.filter(protocole=OuverturePort.UDP, io=OuverturePort.OUT)
|
"""Renvoie la liste des ports ouverts en UDP OUT pour ce profil"""
|
||||||
|
return self.ouvertureport_set.filter(
|
||||||
|
protocole=OuverturePort.UDP,
|
||||||
|
io=OuverturePort.OUT
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OuverturePort(models.Model):
|
class OuverturePort(models.Model):
|
||||||
|
@ -511,119 +760,173 @@ class OuverturePort(models.Model):
|
||||||
|
|
||||||
Les ports de la plage sont compris entre begin et en inclus.
|
Les ports de la plage sont compris entre begin et en inclus.
|
||||||
Si begin == end alors on ne représente qu'un seul port.
|
Si begin == end alors on ne représente qu'un seul port.
|
||||||
|
|
||||||
|
On limite les ports entre 0 et 65535, tels que défini par la RFC
|
||||||
"""
|
"""
|
||||||
|
PRETTY_NAME = "Plage de port ouverte"
|
||||||
|
|
||||||
TCP = 'T'
|
TCP = 'T'
|
||||||
UDP = 'U'
|
UDP = 'U'
|
||||||
IN = 'I'
|
IN = 'I'
|
||||||
OUT = 'O'
|
OUT = 'O'
|
||||||
begin = models.IntegerField()
|
begin = models.PositiveIntegerField(validators=[MaxValueValidator(65535)])
|
||||||
end = models.IntegerField()
|
end = models.PositiveIntegerField(validators=[MaxValueValidator(65535)])
|
||||||
port_list = models.ForeignKey('OuverturePortList', on_delete=models.CASCADE)
|
port_list = models.ForeignKey(
|
||||||
|
'OuverturePortList',
|
||||||
|
on_delete=models.CASCADE
|
||||||
|
)
|
||||||
protocole = models.CharField(
|
protocole = models.CharField(
|
||||||
max_length=1,
|
max_length=1,
|
||||||
choices=(
|
choices=(
|
||||||
(TCP, 'TCP'),
|
(TCP, 'TCP'),
|
||||||
(UDP, 'UDP'),
|
(UDP, 'UDP'),
|
||||||
),
|
),
|
||||||
default=TCP,
|
default=TCP,
|
||||||
)
|
)
|
||||||
io = models.CharField(
|
io = models.CharField(
|
||||||
max_length=1,
|
max_length=1,
|
||||||
choices=(
|
choices=(
|
||||||
(IN, 'IN'),
|
(IN, 'IN'),
|
||||||
(OUT, 'OUT'),
|
(OUT, 'OUT'),
|
||||||
),
|
),
|
||||||
default=OUT,
|
default=OUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.begin == self.end :
|
if self.begin == self.end:
|
||||||
return str(self.begin)
|
return str(self.begin)
|
||||||
return '-'.join([str(self.begin), str(self.end)])
|
return '-'.join([str(self.begin), str(self.end)])
|
||||||
|
|
||||||
def show_port(self):
|
def show_port(self):
|
||||||
|
"""Formatage plus joli, alias pour str"""
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Machine)
|
@receiver(post_save, sender=Machine)
|
||||||
def machine_post_save(sender, **kwargs):
|
def machine_post_save(sender, **kwargs):
|
||||||
|
"""Synchronisation ldap et régen parefeu/dhcp lors de la modification
|
||||||
|
d'une machine"""
|
||||||
user = kwargs['instance'].user
|
user = kwargs['instance'].user
|
||||||
user.ldap_sync(base=False, access_refresh=False, mac_refresh=True)
|
user.ldap_sync(base=False, access_refresh=False, mac_refresh=True)
|
||||||
regen('dhcp')
|
regen('dhcp')
|
||||||
regen('mac_ip_list')
|
regen('mac_ip_list')
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Machine)
|
@receiver(post_delete, sender=Machine)
|
||||||
def machine_post_delete(sender, **kwargs):
|
def machine_post_delete(sender, **kwargs):
|
||||||
|
"""Synchronisation ldap et régen parefeu/dhcp lors de la suppression
|
||||||
|
d'une machine"""
|
||||||
machine = kwargs['instance']
|
machine = kwargs['instance']
|
||||||
user = machine.user
|
user = machine.user
|
||||||
user.ldap_sync(base=False, access_refresh=False, mac_refresh=True)
|
user.ldap_sync(base=False, access_refresh=False, mac_refresh=True)
|
||||||
regen('dhcp')
|
regen('dhcp')
|
||||||
regen('mac_ip_list')
|
regen('mac_ip_list')
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Interface)
|
@receiver(post_save, sender=Interface)
|
||||||
def interface_post_save(sender, **kwargs):
|
def interface_post_save(sender, **kwargs):
|
||||||
|
"""Synchronisation ldap et régen parefeu/dhcp lors de la modification
|
||||||
|
d'une interface"""
|
||||||
interface = kwargs['instance']
|
interface = kwargs['instance']
|
||||||
user = interface.machine.user
|
user = interface.machine.user
|
||||||
user.ldap_sync(base=False, access_refresh=False, mac_refresh=True)
|
user.ldap_sync(base=False, access_refresh=False, mac_refresh=True)
|
||||||
if not interface.may_have_port_open() and interface.port_lists.all():
|
|
||||||
interface.port_lists.clear()
|
|
||||||
# Regen services
|
# Regen services
|
||||||
regen('dhcp')
|
regen('dhcp')
|
||||||
regen('mac_ip_list')
|
regen('mac_ip_list')
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Interface)
|
@receiver(post_delete, sender=Interface)
|
||||||
def interface_post_delete(sender, **kwargs):
|
def interface_post_delete(sender, **kwargs):
|
||||||
|
"""Synchronisation ldap et régen parefeu/dhcp lors de la suppression
|
||||||
|
d'une interface"""
|
||||||
interface = kwargs['instance']
|
interface = kwargs['instance']
|
||||||
user = interface.machine.user
|
user = interface.machine.user
|
||||||
user.ldap_sync(base=False, access_refresh=False, mac_refresh=True)
|
user.ldap_sync(base=False, access_refresh=False, mac_refresh=True)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=IpType)
|
@receiver(post_save, sender=IpType)
|
||||||
def iptype_post_save(sender, **kwargs):
|
def iptype_post_save(sender, **kwargs):
|
||||||
|
"""Generation des objets ip après modification d'un range ip"""
|
||||||
iptype = kwargs['instance']
|
iptype = kwargs['instance']
|
||||||
iptype.gen_ip_range()
|
iptype.gen_ip_range()
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=MachineType)
|
@receiver(post_save, sender=MachineType)
|
||||||
def machine_post_save(sender, **kwargs):
|
def machine_post_save(sender, **kwargs):
|
||||||
|
"""Mise à jour des interfaces lorsque changement d'attribution
|
||||||
|
d'une machinetype (changement iptype parent)"""
|
||||||
machinetype = kwargs['instance']
|
machinetype = kwargs['instance']
|
||||||
for interface in machinetype.all_interfaces():
|
for interface in machinetype.all_interfaces():
|
||||||
interface.update_type()
|
interface.update_type()
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Domain)
|
@receiver(post_save, sender=Domain)
|
||||||
def domain_post_save(sender, **kwargs):
|
def domain_post_save(sender, **kwargs):
|
||||||
|
"""Regeneration dns après modification d'un domain object"""
|
||||||
regen('dns')
|
regen('dns')
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Domain)
|
@receiver(post_delete, sender=Domain)
|
||||||
def domain_post_delete(sender, **kwargs):
|
def domain_post_delete(sender, **kwargs):
|
||||||
|
"""Regeneration dns après suppression d'un domain object"""
|
||||||
regen('dns')
|
regen('dns')
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Extension)
|
@receiver(post_save, sender=Extension)
|
||||||
def extension_post_save(sender, **kwargs):
|
def extension_post_save(sender, **kwargs):
|
||||||
|
"""Regeneration dns après modification d'une extension"""
|
||||||
regen('dns')
|
regen('dns')
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Extension)
|
@receiver(post_delete, sender=Extension)
|
||||||
def extension_post_selete(sender, **kwargs):
|
def extension_post_selete(sender, **kwargs):
|
||||||
|
"""Regeneration dns après suppression d'une extension"""
|
||||||
regen('dns')
|
regen('dns')
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=SOA)
|
||||||
|
def soa_post_save(sender, **kwargs):
|
||||||
|
"""Regeneration dns après modification d'un SOA"""
|
||||||
|
regen('dns')
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_delete, sender=SOA)
|
||||||
|
def soa_post_delete(sender, **kwargs):
|
||||||
|
"""Regeneration dns après suppresson d'un SOA"""
|
||||||
|
regen('dns')
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Mx)
|
@receiver(post_save, sender=Mx)
|
||||||
def mx_post_save(sender, **kwargs):
|
def mx_post_save(sender, **kwargs):
|
||||||
|
"""Regeneration dns après modification d'un MX"""
|
||||||
regen('dns')
|
regen('dns')
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Mx)
|
@receiver(post_delete, sender=Mx)
|
||||||
def mx_post_delete(sender, **kwargs):
|
def mx_post_delete(sender, **kwargs):
|
||||||
|
"""Regeneration dns après suppresson d'un MX"""
|
||||||
regen('dns')
|
regen('dns')
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Ns)
|
@receiver(post_save, sender=Ns)
|
||||||
def ns_post_save(sender, **kwargs):
|
def ns_post_save(sender, **kwargs):
|
||||||
|
"""Regeneration dns après modification d'un NS"""
|
||||||
regen('dns')
|
regen('dns')
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Ns)
|
@receiver(post_delete, sender=Ns)
|
||||||
def ns_post_delete(sender, **kwargs):
|
def ns_post_delete(sender, **kwargs):
|
||||||
|
"""Regeneration dns après modification d'un NS"""
|
||||||
regen('dns')
|
regen('dns')
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Text)
|
@receiver(post_save, sender=Text)
|
||||||
def text_post_save(sender, **kwargs):
|
def text_post_save(sender, **kwargs):
|
||||||
|
"""Regeneration dns après modification d'un TXT"""
|
||||||
regen('dns')
|
regen('dns')
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Text)
|
@receiver(post_delete, sender=Text)
|
||||||
def text_post_delete(sender, **kwargs):
|
def text_post_delete(sender, **kwargs):
|
||||||
|
"""Regeneration dns après modification d'un TX"""
|
||||||
regen('dns')
|
regen('dns')
|
||||||
|
|
|
@ -24,20 +24,42 @@
|
||||||
#Augustin Lemesle
|
#Augustin Lemesle
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from machines.models import Interface, IpType, Extension, IpList, MachineType, Domain, Text, Mx, Service_link, Ns
|
from machines.models import (
|
||||||
|
Interface,
|
||||||
|
IpType,
|
||||||
|
Extension,
|
||||||
|
IpList,
|
||||||
|
MachineType,
|
||||||
|
Domain,
|
||||||
|
Text,
|
||||||
|
Mx,
|
||||||
|
Service_link,
|
||||||
|
Ns,
|
||||||
|
OuverturePortList,
|
||||||
|
OuverturePort
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class IpTypeField(serializers.RelatedField):
|
class IpTypeField(serializers.RelatedField):
|
||||||
|
"""Serialisation d'une iptype, renvoie son evaluation str"""
|
||||||
def to_representation(self, value):
|
def to_representation(self, value):
|
||||||
return value.type
|
return value.type
|
||||||
|
|
||||||
|
|
||||||
class IpListSerializer(serializers.ModelSerializer):
|
class IpListSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serialisation d'une iplist, ip_type etant une foreign_key,
|
||||||
|
on evalue sa methode str"""
|
||||||
ip_type = IpTypeField(read_only=True)
|
ip_type = IpTypeField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IpList
|
model = IpList
|
||||||
fields = ('ipv4', 'ip_type')
|
fields = ('ipv4', 'ip_type')
|
||||||
|
|
||||||
|
|
||||||
class InterfaceSerializer(serializers.ModelSerializer):
|
class InterfaceSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serialisation d'une interface, ipv4, domain et extension sont
|
||||||
|
des foreign_key, on les override et on les evalue avec des fonctions
|
||||||
|
get_..."""
|
||||||
ipv4 = IpListSerializer(read_only=True)
|
ipv4 = IpListSerializer(read_only=True)
|
||||||
mac_address = serializers.SerializerMethodField('get_macaddress')
|
mac_address = serializers.SerializerMethodField('get_macaddress')
|
||||||
domain = serializers.SerializerMethodField('get_dns')
|
domain = serializers.SerializerMethodField('get_dns')
|
||||||
|
@ -56,7 +78,9 @@ class InterfaceSerializer(serializers.ModelSerializer):
|
||||||
def get_macaddress(self, obj):
|
def get_macaddress(self, obj):
|
||||||
return str(obj.mac_address)
|
return str(obj.mac_address)
|
||||||
|
|
||||||
|
|
||||||
class FullInterfaceSerializer(serializers.ModelSerializer):
|
class FullInterfaceSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serialisation complete d'une interface avec l'ipv6 en plus"""
|
||||||
ipv4 = IpListSerializer(read_only=True)
|
ipv4 = IpListSerializer(read_only=True)
|
||||||
mac_address = serializers.SerializerMethodField('get_macaddress')
|
mac_address = serializers.SerializerMethodField('get_macaddress')
|
||||||
domain = serializers.SerializerMethodField('get_dns')
|
domain = serializers.SerializerMethodField('get_dns')
|
||||||
|
@ -75,24 +99,70 @@ class FullInterfaceSerializer(serializers.ModelSerializer):
|
||||||
def get_macaddress(self, obj):
|
def get_macaddress(self, obj):
|
||||||
return str(obj.mac_address)
|
return str(obj.mac_address)
|
||||||
|
|
||||||
|
|
||||||
class ExtensionNameField(serializers.RelatedField):
|
class ExtensionNameField(serializers.RelatedField):
|
||||||
|
"""Evaluation str d'un objet extension (.example.org)"""
|
||||||
def to_representation(self, value):
|
def to_representation(self, value):
|
||||||
return value.name
|
return value.name
|
||||||
|
|
||||||
|
|
||||||
class TypeSerializer(serializers.ModelSerializer):
|
class TypeSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serialisation d'un iptype : extension et la liste des
|
||||||
|
ouvertures de port son evalués en get_... etant des
|
||||||
|
foreign_key ou des relations manytomany"""
|
||||||
extension = ExtensionNameField(read_only=True)
|
extension = ExtensionNameField(read_only=True)
|
||||||
|
ouverture_ports_tcp_in = serializers\
|
||||||
|
.SerializerMethodField('get_port_policy_input_tcp')
|
||||||
|
ouverture_ports_tcp_out = serializers\
|
||||||
|
.SerializerMethodField('get_port_policy_output_tcp')
|
||||||
|
ouverture_ports_udp_in = serializers\
|
||||||
|
.SerializerMethodField('get_port_policy_input_udp')
|
||||||
|
ouverture_ports_udp_out = serializers\
|
||||||
|
.SerializerMethodField('get_port_policy_output_udp')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IpType
|
model = IpType
|
||||||
fields = ('type', 'extension', 'domaine_ip_start', 'domaine_ip_stop')
|
fields = ('type', 'extension', 'domaine_ip_start', 'domaine_ip_stop',
|
||||||
|
'ouverture_ports_tcp_in', 'ouverture_ports_tcp_out',
|
||||||
|
'ouverture_ports_udp_in', 'ouverture_ports_udp_out',)
|
||||||
|
|
||||||
|
def get_port_policy(self, obj, protocole, io):
|
||||||
|
if obj.ouverture_ports is None:
|
||||||
|
return []
|
||||||
|
return map(
|
||||||
|
str,
|
||||||
|
obj.ouverture_ports.ouvertureport_set.filter(
|
||||||
|
protocole=protocole
|
||||||
|
).filter(io=io)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_port_policy_input_tcp(self, obj):
|
||||||
|
"""Renvoie la liste des ports ouverts en entrée tcp"""
|
||||||
|
return self.get_port_policy(obj, OuverturePort.TCP, OuverturePort.IN)
|
||||||
|
|
||||||
|
def get_port_policy_output_tcp(self, obj):
|
||||||
|
"""Renvoie la liste des ports ouverts en sortie tcp"""
|
||||||
|
return self.get_port_policy(obj, OuverturePort.TCP, OuverturePort.OUT)
|
||||||
|
|
||||||
|
def get_port_policy_input_udp(self, obj):
|
||||||
|
"""Renvoie la liste des ports ouverts en entrée udp"""
|
||||||
|
return self.get_port_policy(obj, OuverturePort.UDP, OuverturePort.IN)
|
||||||
|
|
||||||
|
def get_port_policy_output_udp(self, obj):
|
||||||
|
"""Renvoie la liste des ports ouverts en sortie udp"""
|
||||||
|
return self.get_port_policy(obj, OuverturePort.UDP, OuverturePort.OUT)
|
||||||
|
|
||||||
|
|
||||||
class ExtensionSerializer(serializers.ModelSerializer):
|
class ExtensionSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serialisation d'une extension : origin_ip et la zone sont
|
||||||
|
des foreign_key donc evalués en get_..."""
|
||||||
origin = serializers.SerializerMethodField('get_origin_ip')
|
origin = serializers.SerializerMethodField('get_origin_ip')
|
||||||
zone_entry = serializers.SerializerMethodField('get_zone_name')
|
zone_entry = serializers.SerializerMethodField('get_zone_name')
|
||||||
|
soa = serializers.SerializerMethodField('get_soa_data')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Extension
|
model = Extension
|
||||||
fields = ('name', 'origin', 'zone_entry')
|
fields = ('name', 'origin', 'origin_v6', 'zone_entry', 'soa')
|
||||||
|
|
||||||
def get_origin_ip(self, obj):
|
def get_origin_ip(self, obj):
|
||||||
return obj.origin.ipv4
|
return obj.origin.ipv4
|
||||||
|
@ -100,7 +170,13 @@ class ExtensionSerializer(serializers.ModelSerializer):
|
||||||
def get_zone_name(self, obj):
|
def get_zone_name(self, obj):
|
||||||
return str(obj.dns_entry)
|
return str(obj.dns_entry)
|
||||||
|
|
||||||
|
def get_soa_data(self, obj):
|
||||||
|
return { 'mail': obj.soa.dns_soa_mail, 'param': obj.soa.dns_soa_param }
|
||||||
|
|
||||||
|
|
||||||
class MxSerializer(serializers.ModelSerializer):
|
class MxSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serialisation d'un MX, evaluation du nom, de la zone
|
||||||
|
et du serveur cible, etant des foreign_key"""
|
||||||
name = serializers.SerializerMethodField('get_entry_name')
|
name = serializers.SerializerMethodField('get_entry_name')
|
||||||
zone = serializers.SerializerMethodField('get_zone_name')
|
zone = serializers.SerializerMethodField('get_zone_name')
|
||||||
mx_entry = serializers.SerializerMethodField('get_mx_name')
|
mx_entry = serializers.SerializerMethodField('get_mx_name')
|
||||||
|
@ -118,13 +194,16 @@ class MxSerializer(serializers.ModelSerializer):
|
||||||
def get_mx_name(self, obj):
|
def get_mx_name(self, obj):
|
||||||
return str(obj.dns_entry)
|
return str(obj.dns_entry)
|
||||||
|
|
||||||
|
|
||||||
class TextSerializer(serializers.ModelSerializer):
|
class TextSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serialisation d'un txt : zone cible et l'entrée txt
|
||||||
|
sont evaluées à part"""
|
||||||
zone = serializers.SerializerMethodField('get_zone_name')
|
zone = serializers.SerializerMethodField('get_zone_name')
|
||||||
text_entry = serializers.SerializerMethodField('get_text_name')
|
text_entry = serializers.SerializerMethodField('get_text_name')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Text
|
model = Text
|
||||||
fields = ('zone','text_entry','field1', 'field2')
|
fields = ('zone', 'text_entry', 'field1', 'field2')
|
||||||
|
|
||||||
def get_zone_name(self, obj):
|
def get_zone_name(self, obj):
|
||||||
return str(obj.zone.name)
|
return str(obj.zone.name)
|
||||||
|
@ -132,7 +211,10 @@ class TextSerializer(serializers.ModelSerializer):
|
||||||
def get_text_name(self, obj):
|
def get_text_name(self, obj):
|
||||||
return str(obj.dns_entry)
|
return str(obj.dns_entry)
|
||||||
|
|
||||||
|
|
||||||
class NsSerializer(serializers.ModelSerializer):
|
class NsSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serialisation d'un NS : la zone, l'entrée ns complète et le serveur
|
||||||
|
ns sont évalués à part"""
|
||||||
zone = serializers.SerializerMethodField('get_zone_name')
|
zone = serializers.SerializerMethodField('get_zone_name')
|
||||||
ns = serializers.SerializerMethodField('get_domain_name')
|
ns = serializers.SerializerMethodField('get_domain_name')
|
||||||
ns_entry = serializers.SerializerMethodField('get_text_name')
|
ns_entry = serializers.SerializerMethodField('get_text_name')
|
||||||
|
@ -150,7 +232,10 @@ class NsSerializer(serializers.ModelSerializer):
|
||||||
def get_text_name(self, obj):
|
def get_text_name(self, obj):
|
||||||
return str(obj.dns_entry)
|
return str(obj.dns_entry)
|
||||||
|
|
||||||
|
|
||||||
class DomainSerializer(serializers.ModelSerializer):
|
class DomainSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serialisation d'un domain, extension, cname sont des foreign_key,
|
||||||
|
et l'entrée complète, sont évalués à part"""
|
||||||
extension = serializers.SerializerMethodField('get_zone_name')
|
extension = serializers.SerializerMethodField('get_zone_name')
|
||||||
cname = serializers.SerializerMethodField('get_alias_name')
|
cname = serializers.SerializerMethodField('get_alias_name')
|
||||||
cname_entry = serializers.SerializerMethodField('get_cname_name')
|
cname_entry = serializers.SerializerMethodField('get_cname_name')
|
||||||
|
@ -168,7 +253,9 @@ class DomainSerializer(serializers.ModelSerializer):
|
||||||
def get_cname_name(self, obj):
|
def get_cname_name(self, obj):
|
||||||
return str(obj.dns_entry)
|
return str(obj.dns_entry)
|
||||||
|
|
||||||
|
|
||||||
class ServiceServersSerializer(serializers.ModelSerializer):
|
class ServiceServersSerializer(serializers.ModelSerializer):
|
||||||
|
"""Evaluation d'un Service, et serialisation"""
|
||||||
server = serializers.SerializerMethodField('get_server_name')
|
server = serializers.SerializerMethodField('get_server_name')
|
||||||
service = serializers.SerializerMethodField('get_service_name')
|
service = serializers.SerializerMethodField('get_service_name')
|
||||||
need_regen = serializers.SerializerMethodField('get_regen_status')
|
need_regen = serializers.SerializerMethodField('get_regen_status')
|
||||||
|
@ -185,3 +272,31 @@ class ServiceServersSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
def get_regen_status(self, obj):
|
def get_regen_status(self, obj):
|
||||||
return obj.need_regen()
|
return obj.need_regen()
|
||||||
|
|
||||||
|
|
||||||
|
class OuverturePortsSerializer(serializers.Serializer):
|
||||||
|
"""Serialisation de l'ouverture des ports"""
|
||||||
|
ipv4 = serializers.SerializerMethodField()
|
||||||
|
ipv6 = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
def get_ipv4():
|
||||||
|
return {i.ipv4.ipv4:
|
||||||
|
{
|
||||||
|
"tcp_in":[j.tcp_ports_in() for j in i.port_lists.all()],
|
||||||
|
"tcp_out":[j.tcp_ports_out()for j in i.port_lists.all()],
|
||||||
|
"udp_in":[j.udp_ports_in() for j in i.port_lists.all()],
|
||||||
|
"udp_out":[j.udp_ports_out() for j in i.port_lists.all()],
|
||||||
|
}
|
||||||
|
for i in Interface.objects.all() if i.ipv4
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_ipv6():
|
||||||
|
return {i.ipv6:
|
||||||
|
{
|
||||||
|
"tcp_in":[j.tcp_ports_in() for j in i.port_lists.all()],
|
||||||
|
"tcp_out":[j.tcp_ports_out()for j in i.port_lists.all()],
|
||||||
|
"udp_in":[j.udp_ports_in() for j in i.port_lists.all()],
|
||||||
|
"udp_out":[j.udp_ports_out() for j in i.port_lists.all()],
|
||||||
|
}
|
||||||
|
for i in Interface.objects.all() if i.ipv6
|
||||||
|
}
|
||||||
|
|
|
@ -26,17 +26,25 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Extension</th>
|
<th>Extension</th>
|
||||||
<th>Autorisation infra pour utiliser l'extension</th>
|
<th>Droit infra pour utiliser ?</th>
|
||||||
|
<th>Enregistrement SOA</th>
|
||||||
<th>Enregistrement A origin</th>
|
<th>Enregistrement A origin</th>
|
||||||
<th></th>
|
{% if ipv6_enabled %}
|
||||||
|
<th>Enregistrement AAAA origin</th>
|
||||||
|
{% endif %}
|
||||||
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
{% for extension in extension_list %}
|
{% for extension in extension_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ extension.name }}</td>
|
<td>{{ extension.name }}</td>
|
||||||
<td>{{ extension.need_infra }}</td>
|
<td>{{ extension.need_infra }}</td>
|
||||||
|
<td>{{ extension.soa}}</td>
|
||||||
<td>{{ extension.origin }}</td>
|
<td>{{ extension.origin }}</td>
|
||||||
<td class="text-right">
|
{% if ipv6_enabled %}
|
||||||
|
<td>{{ extension.origin_v6 }}</td>
|
||||||
|
{% endif %}
|
||||||
|
<td class="text-right">
|
||||||
{% if is_infra %}
|
{% if is_infra %}
|
||||||
{% include 'buttons/edit.html' with href='machines:edit-extension' id=extension.id %}
|
{% include 'buttons/edit.html' with href='machines:edit-extension' id=extension.id %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<th>Fin</th>
|
<th>Fin</th>
|
||||||
<th>Préfixe v6</th>
|
<th>Préfixe v6</th>
|
||||||
<th>Sur vlan</th>
|
<th>Sur vlan</th>
|
||||||
|
<th>Ouverture ports par défault</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -45,6 +46,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<td>{{ type.domaine_ip_stop }}</td>
|
<td>{{ type.domaine_ip_stop }}</td>
|
||||||
<td>{{ type.prefix_v6 }}</td>
|
<td>{{ type.prefix_v6 }}</td>
|
||||||
<td>{{ type.vlan }}</td>
|
<td>{{ type.vlan }}</td>
|
||||||
|
<td>{{ type.ouverture_ports }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{% if is_infra %}
|
{% if is_infra %}
|
||||||
{% include 'buttons/edit.html' with href='machines:edit-iptype' id=type.id %}
|
{% include 'buttons/edit.html' with href='machines:edit-iptype' id=type.id %}
|
||||||
|
|
|
@ -35,7 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<col width="144px">
|
<col width="144px">
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<thead>
|
<thead>
|
||||||
<th>Nom DNS</th>
|
<th>{% include "buttons/sort.html" with prefix='machine' col='name' text='Nom DNS' %}</th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th>MAC</th>
|
<th>MAC</th>
|
||||||
<th>IP</th>
|
<th>IP</th>
|
||||||
|
@ -44,7 +44,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% for machine in machines_list %}
|
{% for machine in machines_list %}
|
||||||
<tr class="info">
|
<tr class="info">
|
||||||
<td colspan="4">
|
<td colspan="4">
|
||||||
<b>{{ machine.name }}</b> <i class="glyphicon glyphicon-chevron-right"></i>
|
<b>{{ machine.name|default:'<i>Pas de nom</i>' }}</b> <i class="glyphicon glyphicon-chevron-right"></i>
|
||||||
<a href="{% url 'users:profil' userid=machine.user.id %}" title="Voir le profil">
|
<a href="{% url 'users:profil' userid=machine.user.id %}" title="Voir le profil">
|
||||||
<i class="glyphicon glyphicon-user"></i> {{ machine.user }}
|
<i class="glyphicon glyphicon-user"></i> {{ machine.user }}
|
||||||
</a>
|
</a>
|
||||||
|
|
56
machines/templates/machines/aff_soa.html
Normal file
56
machines/templates/machines/aff_soa.html
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
{% comment %}
|
||||||
|
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
||||||
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
quelques clics.
|
||||||
|
|
||||||
|
Copyright © 2017 Gabriel Détraz
|
||||||
|
Copyright © 2017 Goulven Kermarec
|
||||||
|
Copyright © 2017 Augustin Lemesle
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nom</th>
|
||||||
|
<th>Mail</th>
|
||||||
|
<th>Refresh</th>
|
||||||
|
<th>Retry</th>
|
||||||
|
<th>Expire</th>
|
||||||
|
<th>TTL</th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% for soa in soa_list %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ soa.name }}</td>
|
||||||
|
<td>{{ soa.mail }}</td>
|
||||||
|
<td>{{ soa.refresh }}</td>
|
||||||
|
<td>{{ soa.retry }}</td>
|
||||||
|
<td>{{ soa.expire }}</td>
|
||||||
|
<td>{{ soa.ttl }}</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{% if is_infra %}
|
||||||
|
{% include 'buttons/edit.html' with href='machines:edit-soa' id=soa.id %}
|
||||||
|
{% endif %}
|
||||||
|
{% include 'buttons/history.html' with href='machines:history' name='soa' id=soa.id %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
|
@ -25,21 +25,21 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Zone concernée</th>
|
<th>Zone concernée</th>
|
||||||
<th>Enregistrement</th>
|
<th>Enregistrement</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
{% for text in text_list %}
|
{% for txt in txt_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ text.zone }}</td>
|
<td>{{ txt.zone }}</td>
|
||||||
<td>{{ text.dns_entry }}</td>
|
<td>{{ txt.dns_entry }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{% if is_infra %}
|
{% 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 %}
|
{% 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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
|
@ -35,6 +35,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include "machines/aff_extension.html" with extension_list=extension_list %}
|
{% include "machines/aff_extension.html" with extension_list=extension_list %}
|
||||||
|
|
||||||
|
<h2>Liste des enregistrements SOA</h2>
|
||||||
|
{% if is_infra %}
|
||||||
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-soa' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un enregistrement SOA</a>
|
||||||
|
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-soa' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un enregistrement SOA</a>
|
||||||
|
{% endif %}
|
||||||
|
{% include "machines/aff_soa.html" with soa_list=soa_list %}
|
||||||
<h2>Liste des enregistrements MX</h2>
|
<h2>Liste des enregistrements MX</h2>
|
||||||
{% if is_infra %}
|
{% if is_infra %}
|
||||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-mx' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un enregistrement MX</a>
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-mx' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un enregistrement MX</a>
|
||||||
|
@ -47,12 +53,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>
|
<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 %}
|
{% endif %}
|
||||||
{% include "machines/aff_ns.html" with ns_list=ns_list %}
|
{% 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 %}
|
{% 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-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-text' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer 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 %}
|
{% endif %}
|
||||||
{% include "machines/aff_text.html" with text_list=text_list %}
|
{% include "machines/aff_txt.html" with txt_list=txt_list %}
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -31,8 +31,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<h2>Liste des nas</h2>
|
<h2>Liste des nas</h2>
|
||||||
<h5>La correpondance nas-machinetype relie le type de nas à un type de machine.
|
<h5>La correpondance nas-machinetype relie le type de nas à un type de machine.
|
||||||
Elle est utile pour l'autoenregistrement des macs par radius, et permet de choisir le type de machine à affecter aux machines en fonction du type de nas</h5>
|
Elle est utile pour l'autoenregistrement des macs par radius, et permet de choisir le type de machine à affecter aux machines en fonction du type de nas</h5>
|
||||||
|
{% if is_infra %}
|
||||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-nas' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un type de nas</a>
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-nas' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un type de nas</a>
|
||||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-nas' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs types nas</a>
|
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-nas' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs types nas</a>
|
||||||
|
{% endif %}
|
||||||
{% include "machines/aff_nas.html" with nas_list=nas_list %}
|
{% include "machines/aff_nas.html" with nas_list=nas_list %}
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -29,8 +29,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Liste des vlans</h2>
|
<h2>Liste des vlans</h2>
|
||||||
|
{% if is_infra %}
|
||||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-vlan' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un vlan</a>
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-vlan' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un vlan</a>
|
||||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-vlan' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs vlan</a>
|
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-vlan' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs vlan</a>
|
||||||
|
{% endif %}
|
||||||
{% include "machines/aff_vlan.html" with vlan_list=vlan_list %}
|
{% include "machines/aff_vlan.html" with vlan_list=vlan_list %}
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
{% load bootstrap_form_typeahead %}
|
{% load massive_bootstrap_form %}
|
||||||
|
|
||||||
{% block title %}Création et modification de machines{% endblock %}
|
{% 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 %}
|
{% if domainform %}
|
||||||
{% bootstrap_form_errors domainform %}
|
{% bootstrap_form_errors domainform %}
|
||||||
{% endif %}
|
{% 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">
|
<form class="form" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
@ -48,24 +78,60 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if interfaceform %}
|
{% if interfaceform %}
|
||||||
<h3>Interface</h3>
|
<h3>Interface</h3>
|
||||||
{% if i_bft_param %}
|
{% if i_mbf_param %}
|
||||||
{% if 'machine' in interfaceform.fields %}
|
{% massive_bootstrap_form interfaceform 'ipv4,machine' mbf_param=i_mbf_param %}
|
||||||
{% bootstrap_form_typeahead interfaceform 'ipv4,machine' bft_param=i_bft_param %}
|
|
||||||
{% else %}
|
|
||||||
{% bootstrap_form_typeahead interfaceform 'ipv4' bft_param=i_bft_param %}
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if 'machine' in interfaceform.fields %}
|
{% massive_bootstrap_form interfaceform 'ipv4,machine' %}
|
||||||
{% bootstrap_form_typeahead interfaceform 'ipv4,machine' %}
|
|
||||||
{% else %}
|
|
||||||
{% bootstrap_form_typeahead interfaceform 'ipv4' %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if domainform %}
|
{% if domainform %}
|
||||||
<h3>Domaine</h3>
|
<h3>Domaine</h3>
|
||||||
{% bootstrap_form domainform %}
|
{% bootstrap_form domainform %}
|
||||||
{% endif %}
|
{% 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 soaform %}
|
||||||
|
<h3>Enregistrement SOA</h3>
|
||||||
|
{% bootstrap_form soaform %}
|
||||||
|
{% 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" %}
|
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
|
||||||
</form>
|
</form>
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -58,7 +58,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% if is_cableur %}
|
{% if is_cableur %}
|
||||||
<a class="list-group-item list-group-item-info" href="{% url "machines:index-portlist" %}">
|
<a class="list-group-item list-group-item-info" href="{% url "machines:index-portlist" %}">
|
||||||
<i class="glyphicon glyphicon-list"></i>
|
<i class="glyphicon glyphicon-list"></i>
|
||||||
Configuration de ports
|
Ouverture de ports
|
||||||
</a>
|
</a>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -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 )
|
|
||||||
)
|
|
||||||
|
|
|
@ -44,12 +44,15 @@ urlpatterns = [
|
||||||
url(r'^add_extension/$', views.add_extension, name='add-extension'),
|
url(r'^add_extension/$', views.add_extension, name='add-extension'),
|
||||||
url(r'^edit_extension/(?P<extensionid>[0-9]+)$', views.edit_extension, name='edit-extension'),
|
url(r'^edit_extension/(?P<extensionid>[0-9]+)$', views.edit_extension, name='edit-extension'),
|
||||||
url(r'^del_extension/$', views.del_extension, name='del-extension'),
|
url(r'^del_extension/$', views.del_extension, name='del-extension'),
|
||||||
|
url(r'^add_soa/$', views.add_soa, name='add-soa'),
|
||||||
|
url(r'^edit_soa/(?P<soaid>[0-9]+)$', views.edit_soa, name='edit-soa'),
|
||||||
|
url(r'^del_soa/$', views.del_soa, name='del-soa'),
|
||||||
url(r'^add_mx/$', views.add_mx, name='add-mx'),
|
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'^edit_mx/(?P<mxid>[0-9]+)$', views.edit_mx, name='edit-mx'),
|
||||||
url(r'^del_mx/$', views.del_mx, name='del-mx'),
|
url(r'^del_mx/$', views.del_mx, name='del-mx'),
|
||||||
url(r'^add_text/$', views.add_text, name='add-text'),
|
url(r'^add_txt/$', views.add_txt, name='add-txt'),
|
||||||
url(r'^edit_text/(?P<textid>[0-9]+)$', views.edit_text, name='edit-text'),
|
url(r'^edit_txt/(?P<textid>[0-9]+)$', views.edit_txt, name='edit-txt'),
|
||||||
url(r'^del_text/$', views.del_text, name='del-text'),
|
url(r'^del_txt/$', views.del_txt, name='del-txt'),
|
||||||
url(r'^add_ns/$', views.add_ns, name='add-ns'),
|
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'^edit_ns/(?P<nsid>[0-9]+)$', views.edit_ns, name='edit-ns'),
|
||||||
url(r'^del_ns/$', views.del_ns, name='del-ns'),
|
url(r'^del_ns/$', views.del_ns, name='del-ns'),
|
||||||
|
@ -74,9 +77,10 @@ urlpatterns = [
|
||||||
url(r'^history/(?P<object>interface)/(?P<id>[0-9]+)$', views.history, name='history'),
|
url(r'^history/(?P<object>interface)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||||
url(r'^history/(?P<object>machinetype)/(?P<id>[0-9]+)$', views.history, name='history'),
|
url(r'^history/(?P<object>machinetype)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||||
url(r'^history/(?P<object>extension)/(?P<id>[0-9]+)$', views.history, name='history'),
|
url(r'^history/(?P<object>extension)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||||
|
url(r'^history/(?P<object>soa)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||||
url(r'^history/(?P<object>mx)/(?P<id>[0-9]+)$', views.history, name='history'),
|
url(r'^history/(?P<object>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>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>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>alias)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||||
url(r'^history/(?P<object>vlan)/(?P<id>[0-9]+)$', views.history, name='history'),
|
url(r'^history/(?P<object>vlan)/(?P<id>[0-9]+)$', views.history, name='history'),
|
||||||
|
@ -93,6 +97,7 @@ urlpatterns = [
|
||||||
url(r'^rest/text/$', views.text, name='text'),
|
url(r'^rest/text/$', views.text, name='text'),
|
||||||
url(r'^rest/zones/$', views.zones, name='zones'),
|
url(r'^rest/zones/$', views.zones, name='zones'),
|
||||||
url(r'^rest/service_servers/$', views.service_servers, name='service-servers'),
|
url(r'^rest/service_servers/$', views.service_servers, name='service-servers'),
|
||||||
|
url(r'^rest/ouverture_ports/$', views.ouverture_ports, name='ouverture-ports'),
|
||||||
url(r'index_portlist/$', views.index_portlist, name='index-portlist'),
|
url(r'index_portlist/$', views.index_portlist, name='index-portlist'),
|
||||||
url(r'^edit_portlist/(?P<pk>[0-9]+)$', views.edit_portlist, name='edit-portlist'),
|
url(r'^edit_portlist/(?P<pk>[0-9]+)$', views.edit_portlist, name='edit-portlist'),
|
||||||
url(r'^del_portlist/(?P<pk>[0-9]+)$', views.del_portlist, name='del-portlist'),
|
url(r'^del_portlist/(?P<pk>[0-9]+)$', views.del_portlist, name='del-portlist'),
|
||||||
|
|
|
@ -43,56 +43,101 @@ from django.contrib.auth import authenticate, login
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
from rest_framework.renderers import JSONRenderer
|
from rest_framework.renderers import JSONRenderer
|
||||||
from machines.serializers import FullInterfaceSerializer, InterfaceSerializer, TypeSerializer, DomainSerializer, TextSerializer, MxSerializer, ExtensionSerializer, ServiceServersSerializer, NsSerializer
|
from machines.serializers import ( FullInterfaceSerializer,
|
||||||
|
InterfaceSerializer,
|
||||||
|
TypeSerializer,
|
||||||
|
DomainSerializer,
|
||||||
|
TextSerializer,
|
||||||
|
MxSerializer,
|
||||||
|
ExtensionSerializer,
|
||||||
|
ServiceServersSerializer,
|
||||||
|
NsSerializer,
|
||||||
|
OuverturePortsSerializer
|
||||||
|
)
|
||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
from reversion.models import Version
|
from reversion.models import Version
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from .forms import NewMachineForm, EditMachineForm, EditInterfaceForm, AddInterfaceForm, MachineTypeForm, DelMachineTypeForm, ExtensionForm, DelExtensionForm, BaseEditInterfaceForm, BaseEditMachineForm
|
from .forms import (
|
||||||
from .forms import EditIpTypeForm, IpTypeForm, DelIpTypeForm, DomainForm, AliasForm, DelAliasForm, NsForm, DelNsForm, TextForm, DelTextForm, MxForm, DelMxForm, VlanForm, DelVlanForm, ServiceForm, DelServiceForm, NasForm, DelNasForm
|
NewMachineForm,
|
||||||
|
EditMachineForm,
|
||||||
|
EditInterfaceForm,
|
||||||
|
AddInterfaceForm,
|
||||||
|
MachineTypeForm,
|
||||||
|
DelMachineTypeForm,
|
||||||
|
ExtensionForm,
|
||||||
|
DelExtensionForm,
|
||||||
|
BaseEditInterfaceForm,
|
||||||
|
BaseEditMachineForm
|
||||||
|
)
|
||||||
|
from .forms import (
|
||||||
|
EditIpTypeForm,
|
||||||
|
IpTypeForm,
|
||||||
|
DelIpTypeForm,
|
||||||
|
DomainForm,
|
||||||
|
AliasForm,
|
||||||
|
DelAliasForm,
|
||||||
|
SOAForm,
|
||||||
|
DelSOAForm,
|
||||||
|
NsForm,
|
||||||
|
DelNsForm,
|
||||||
|
TxtForm,
|
||||||
|
DelTxtForm,
|
||||||
|
MxForm,
|
||||||
|
DelMxForm,
|
||||||
|
VlanForm,
|
||||||
|
DelVlanForm,
|
||||||
|
ServiceForm,
|
||||||
|
DelServiceForm,
|
||||||
|
NasForm,
|
||||||
|
DelNasForm
|
||||||
|
)
|
||||||
from .forms import EditOuverturePortListForm, EditOuverturePortConfigForm
|
from .forms import EditOuverturePortListForm, EditOuverturePortConfigForm
|
||||||
from .models import IpType, Machine, Interface, IpList, MachineType, Extension, Mx, Ns, Domain, Service, Service_link, Vlan, Nas, Text, OuverturePortList, OuverturePort
|
from .models import (
|
||||||
|
IpType,
|
||||||
|
Machine,
|
||||||
|
Interface,
|
||||||
|
IpList,
|
||||||
|
MachineType,
|
||||||
|
Extension,
|
||||||
|
SOA,
|
||||||
|
Mx,
|
||||||
|
Ns,
|
||||||
|
Domain,
|
||||||
|
Service,
|
||||||
|
Service_link,
|
||||||
|
Vlan,
|
||||||
|
Nas,
|
||||||
|
Text,
|
||||||
|
OuverturePortList,
|
||||||
|
OuverturePort
|
||||||
|
)
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from users.models import all_has_access
|
|
||||||
from preferences.models import GeneralOption, OptionalMachine
|
from preferences.models import GeneralOption, OptionalMachine
|
||||||
from .templatetags.bootstrap_form_typeahead import hidden_id, input_id
|
from re2o.utils import (
|
||||||
|
all_active_assigned_interfaces,
|
||||||
def all_active_interfaces():
|
all_has_access,
|
||||||
"""Renvoie l'ensemble des machines autorisées à sortir sur internet """
|
filter_active_interfaces,
|
||||||
return Interface.objects.filter(machine__in=Machine.objects.filter(user__in=all_has_access()).filter(active=True)).select_related('domain').select_related('machine').select_related('type').select_related('ipv4').select_related('domain__extension').select_related('ipv4__ip_type').distinct()
|
SortTable
|
||||||
|
)
|
||||||
def all_active_assigned_interfaces():
|
from re2o.views import form
|
||||||
""" Renvoie l'ensemble des machines qui ont une ipv4 assignées et disposant de l'accès internet"""
|
|
||||||
return all_active_interfaces().filter(ipv4__isnull=False)
|
|
||||||
|
|
||||||
def all_active_interfaces_count():
|
|
||||||
""" Version light seulement pour compter"""
|
|
||||||
return Interface.objects.filter(machine__in=Machine.objects.filter(user__in=all_has_access()).filter(active=True))
|
|
||||||
|
|
||||||
def all_active_assigned_interfaces_count():
|
|
||||||
""" Version light seulement pour compter"""
|
|
||||||
return all_active_interfaces_count().filter(ipv4__isnull=False)
|
|
||||||
|
|
||||||
def form(ctx, template, request):
|
|
||||||
c = ctx
|
|
||||||
c.update(csrf(request))
|
|
||||||
return render(request, template, c)
|
|
||||||
|
|
||||||
def f_type_id( is_type_tt ):
|
def f_type_id( is_type_tt ):
|
||||||
""" The id that will be used in HTML to store the value of the field
|
""" 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
|
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 ) :
|
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']
|
f_ipv4 = form.fields['ipv4']
|
||||||
used_mtype_id = []
|
used_mtype_id = []
|
||||||
choices = '{"":[{key:"",value:"Choisissez d\'abord un type de machine"},'
|
choices = '{"":[{key:"",value:"Choisissez d\'abord un type de machine"},'
|
||||||
mtype_id = -1
|
mtype_id = -1
|
||||||
|
|
||||||
for ip in f_ipv4.queryset.annotate(mtype_id=F('ip_type__machinetype__id')).order_by('mtype_id', 'id') :
|
for ip in f_ipv4.queryset.annotate(mtype_id=F('ip_type__machinetype__id'))\
|
||||||
|
.order_by('mtype_id', 'id') :
|
||||||
if mtype_id != ip.mtype_id :
|
if mtype_id != ip.mtype_id :
|
||||||
mtype_id = ip.mtype_id
|
mtype_id = ip.mtype_id
|
||||||
used_mtype_id.append(mtype_id)
|
used_mtype_id.append(mtype_id)
|
||||||
|
@ -112,7 +157,7 @@ def generate_ipv4_choices( form ) :
|
||||||
return choices
|
return choices
|
||||||
|
|
||||||
def generate_ipv4_engine( is_type_tt ) :
|
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 (
|
return (
|
||||||
'new Bloodhound( {{'
|
'new Bloodhound( {{'
|
||||||
|
@ -126,7 +171,7 @@ def generate_ipv4_engine( is_type_tt ) :
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate_ipv4_match_func( 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 (
|
return (
|
||||||
'function(q, sync) {{'
|
'function(q, sync) {{'
|
||||||
|
@ -142,25 +187,27 @@ def generate_ipv4_match_func( is_type_tt ) :
|
||||||
type_id = f_type_id( is_type_tt )
|
type_id = f_type_id( is_type_tt )
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate_ipv4_bft_param( form, is_type_tt ):
|
def generate_ipv4_mbf_param( form, is_type_tt ):
|
||||||
""" Generate all the parameters to use with the bootstrap_form_typeahead
|
""" Generate all the parameters to use with the massive_bootstrap_form
|
||||||
tag """
|
tag """
|
||||||
i_choices = { 'ipv4': generate_ipv4_choices( form ) }
|
i_choices = { 'ipv4': generate_ipv4_choices( form ) }
|
||||||
i_engine = { 'ipv4': generate_ipv4_engine( is_type_tt ) }
|
i_engine = { 'ipv4': generate_ipv4_engine( is_type_tt ) }
|
||||||
i_match_func = { 'ipv4': generate_ipv4_match_func( 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_update_on = { 'ipv4': [f_type_id( is_type_tt )] }
|
||||||
i_bft_param = {
|
i_gen_select = { 'ipv4': False }
|
||||||
|
i_mbf_param = {
|
||||||
'choices': i_choices,
|
'choices': i_choices,
|
||||||
'engine': i_engine,
|
'engine': i_engine,
|
||||||
'match_func': i_match_func,
|
'match_func': i_match_func,
|
||||||
'update_on': i_update_on
|
'update_on': i_update_on,
|
||||||
|
'gen_select': i_gen_select
|
||||||
}
|
}
|
||||||
return i_bft_param
|
return i_mbf_param
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def new_machine(request, userid):
|
def new_machine(request, userid):
|
||||||
""" Fonction de creation d'une machine. Cree l'objet machine, le sous objet interface et l'objet domain
|
""" Fonction de creation d'une machine. Cree l'objet machine,
|
||||||
à partir de model forms.
|
le sous objet interface et l'objet domain à partir de model forms.
|
||||||
Trop complexe, devrait être simplifié"""
|
Trop complexe, devrait être simplifié"""
|
||||||
try:
|
try:
|
||||||
user = User.objects.get(pk=userid)
|
user = User.objects.get(pk=userid)
|
||||||
|
@ -171,15 +218,16 @@ def new_machine(request, userid):
|
||||||
max_lambdauser_interfaces = options.max_lambdauser_interfaces
|
max_lambdauser_interfaces = options.max_lambdauser_interfaces
|
||||||
if not request.user.has_perms(('cableur',)):
|
if not request.user.has_perms(('cableur',)):
|
||||||
if user != request.user:
|
if user != request.user:
|
||||||
messages.error(request, "Vous ne pouvez pas ajouter une machine à un autre user que vous sans droit")
|
messages.error(
|
||||||
|
request,
|
||||||
|
"Vous ne pouvez pas ajouter une machine à un autre user que vous sans droit")
|
||||||
return redirect("/users/profil/" + str(request.user.id))
|
return redirect("/users/profil/" + str(request.user.id))
|
||||||
if user.user_interfaces().count() >= max_lambdauser_interfaces:
|
if user.user_interfaces().count() >= max_lambdauser_interfaces:
|
||||||
messages.error(request, "Vous avez atteint le maximum d'interfaces autorisées que vous pouvez créer vous même (%s) " % max_lambdauser_interfaces)
|
messages.error(request, "Vous avez atteint le maximum d'interfaces autorisées que vous pouvez créer vous même (%s) " % max_lambdauser_interfaces)
|
||||||
return redirect("/users/profil/" + str(request.user.id))
|
return redirect("/users/profil/" + str(request.user.id))
|
||||||
machine = NewMachineForm(request.POST or None)
|
machine = NewMachineForm(request.POST or None)
|
||||||
interface = AddInterfaceForm(request.POST or None, infra=request.user.has_perms(('infra',)))
|
interface = AddInterfaceForm(request.POST or None, infra=request.user.has_perms(('infra',)))
|
||||||
nb_machine = Interface.objects.filter(machine__user=userid).count()
|
domain = DomainForm(request.POST or None, user=user)
|
||||||
domain = DomainForm(request.POST or None, user=user, nb_machine=nb_machine)
|
|
||||||
if machine.is_valid() and interface.is_valid():
|
if machine.is_valid() and interface.is_valid():
|
||||||
new_machine = machine.save(commit=False)
|
new_machine = machine.save(commit=False)
|
||||||
new_machine.user = user
|
new_machine.user = user
|
||||||
|
@ -203,8 +251,8 @@ def new_machine(request, userid):
|
||||||
reversion.set_comment("Création")
|
reversion.set_comment("Création")
|
||||||
messages.success(request, "La machine a été créée")
|
messages.success(request, "La machine a été créée")
|
||||||
return redirect("/users/profil/" + str(user.id))
|
return redirect("/users/profil/" + str(user.id))
|
||||||
i_bft_param = generate_ipv4_bft_param( interface, False )
|
i_mbf_param = generate_ipv4_mbf_param( interface, False )
|
||||||
return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain, 'i_bft_param': i_bft_param}, 'machines/machine.html', request)
|
return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain, 'i_mbf_param': i_mbf_param}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def edit_interface(request, interfaceid):
|
def edit_interface(request, interfaceid):
|
||||||
|
@ -243,8 +291,8 @@ def edit_interface(request, interfaceid):
|
||||||
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))
|
||||||
messages.success(request, "La machine a été modifiée")
|
messages.success(request, "La machine a été modifiée")
|
||||||
return redirect("/users/profil/" + str(interface.machine.user.id))
|
return redirect("/users/profil/" + str(interface.machine.user.id))
|
||||||
i_bft_param = generate_ipv4_bft_param( interface_form, False )
|
i_mbf_param = generate_ipv4_mbf_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)
|
return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form, 'i_mbf_param': i_mbf_param}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def del_machine(request, machineid):
|
def del_machine(request, machineid):
|
||||||
|
@ -302,8 +350,8 @@ def new_interface(request, machineid):
|
||||||
reversion.set_comment("Création")
|
reversion.set_comment("Création")
|
||||||
messages.success(request, "L'interface a été ajoutée")
|
messages.success(request, "L'interface a été ajoutée")
|
||||||
return redirect("/users/profil/" + str(machine.user.id))
|
return redirect("/users/profil/" + str(machine.user.id))
|
||||||
i_bft_param = generate_ipv4_bft_param( interface_form, False )
|
i_mbf_param = generate_ipv4_mbf_param( interface_form, False )
|
||||||
return form({'interfaceform': interface_form, 'domainform': domain_form, 'i_bft_param': i_bft_param}, 'machines/machine.html', request)
|
return form({'interfaceform': interface_form, 'domainform': domain_form, 'i_mbf_param': i_mbf_param}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def del_interface(request, interfaceid):
|
def del_interface(request, interfaceid):
|
||||||
|
@ -340,7 +388,7 @@ def add_iptype(request):
|
||||||
reversion.set_comment("Création")
|
reversion.set_comment("Création")
|
||||||
messages.success(request, "Ce type d'ip a été ajouté")
|
messages.success(request, "Ce type d'ip a été ajouté")
|
||||||
return redirect("/machines/index_iptype")
|
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
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -359,7 +407,7 @@ def edit_iptype(request, iptypeid):
|
||||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in iptype.changed_data))
|
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in iptype.changed_data))
|
||||||
messages.success(request, "Type d'ip modifié")
|
messages.success(request, "Type d'ip modifié")
|
||||||
return redirect("/machines/index_iptype/")
|
return redirect("/machines/index_iptype/")
|
||||||
return form({'machineform': iptype}, 'machines/machine.html', request)
|
return form({'iptypeform': iptype}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -377,7 +425,7 @@ def del_iptype(request):
|
||||||
except ProtectedError:
|
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)
|
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 redirect("/machines/index_iptype")
|
||||||
return form({'machineform': iptype, 'interfaceform': None}, 'machines/machine.html', request)
|
return form({'iptypeform': iptype}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -390,7 +438,7 @@ def add_machinetype(request):
|
||||||
reversion.set_comment("Création")
|
reversion.set_comment("Création")
|
||||||
messages.success(request, "Ce type de machine a été ajouté")
|
messages.success(request, "Ce type de machine a été ajouté")
|
||||||
return redirect("/machines/index_machinetype")
|
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
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -408,7 +456,7 @@ def edit_machinetype(request, machinetypeid):
|
||||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in machinetype.changed_data))
|
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in machinetype.changed_data))
|
||||||
messages.success(request, "Type de machine modifié")
|
messages.success(request, "Type de machine modifié")
|
||||||
return redirect("/machines/index_machinetype/")
|
return redirect("/machines/index_machinetype/")
|
||||||
return form({'machineform': machinetype}, 'machines/machine.html', request)
|
return form({'machinetypeform': machinetype}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -425,7 +473,7 @@ def del_machinetype(request):
|
||||||
except ProtectedError:
|
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)
|
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 redirect("/machines/index_machinetype")
|
||||||
return form({'machineform': machinetype, 'interfaceform': None}, 'machines/machine.html', request)
|
return form({'machinetypeform': machinetype}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -438,7 +486,7 @@ def add_extension(request):
|
||||||
reversion.set_comment("Création")
|
reversion.set_comment("Création")
|
||||||
messages.success(request, "Cette extension a été ajoutée")
|
messages.success(request, "Cette extension a été ajoutée")
|
||||||
return redirect("/machines/index_extension")
|
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
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -456,7 +504,7 @@ def edit_extension(request, extensionid):
|
||||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in extension.changed_data))
|
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in extension.changed_data))
|
||||||
messages.success(request, "Extension modifiée")
|
messages.success(request, "Extension modifiée")
|
||||||
return redirect("/machines/index_extension/")
|
return redirect("/machines/index_extension/")
|
||||||
return form({'machineform': extension}, 'machines/machine.html', request)
|
return form({'extensionform': extension}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -473,7 +521,55 @@ def del_extension(request):
|
||||||
except ProtectedError:
|
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)
|
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 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')
|
||||||
|
def add_soa(request):
|
||||||
|
soa = SOAForm(request.POST or None)
|
||||||
|
if soa.is_valid():
|
||||||
|
with transaction.atomic(), reversion.create_revision():
|
||||||
|
soa.save()
|
||||||
|
reversion.set_user(request.user)
|
||||||
|
reversion.set_comment("Création")
|
||||||
|
messages.success(request, "Cet enregistrement SOA a été ajouté")
|
||||||
|
return redirect("/machines/index_extension")
|
||||||
|
return form({'soaform': soa}, 'machines/machine.html', request)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@permission_required('infra')
|
||||||
|
def edit_soa(request, soaid):
|
||||||
|
try:
|
||||||
|
soa_instance = SOA.objects.get(pk=soaid)
|
||||||
|
except SOA.DoesNotExist:
|
||||||
|
messages.error(request, u"Entrée inexistante" )
|
||||||
|
return redirect("/machines/index_extension/")
|
||||||
|
soa = SOAForm(request.POST or None, instance=soa_instance)
|
||||||
|
if soa.is_valid():
|
||||||
|
with transaction.atomic(), reversion.create_revision():
|
||||||
|
soa.save()
|
||||||
|
reversion.set_user(request.user)
|
||||||
|
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in soa.changed_data))
|
||||||
|
messages.success(request, "SOA modifié")
|
||||||
|
return redirect("/machines/index_extension/")
|
||||||
|
return form({'soaform': soa}, 'machines/machine.html', request)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@permission_required('infra')
|
||||||
|
def del_soa(request):
|
||||||
|
soa = DelSOAForm(request.POST or None)
|
||||||
|
if soa.is_valid():
|
||||||
|
soa_dels = soa.cleaned_data['soa']
|
||||||
|
for soa_del in soa_dels:
|
||||||
|
try:
|
||||||
|
with transaction.atomic(), reversion.create_revision():
|
||||||
|
soa_del.delete()
|
||||||
|
reversion.set_user(request.user)
|
||||||
|
messages.success(request, "Le SOA a été supprimée")
|
||||||
|
except ProtectedError:
|
||||||
|
messages.error(request, "Erreur le SOA suivant %s ne peut être supprimé" % soa_del)
|
||||||
|
return redirect("/machines/index_extension")
|
||||||
|
return form({'soaform': soa}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -486,7 +582,7 @@ def add_mx(request):
|
||||||
reversion.set_comment("Création")
|
reversion.set_comment("Création")
|
||||||
messages.success(request, "Cet enregistrement mx a été ajouté")
|
messages.success(request, "Cet enregistrement mx a été ajouté")
|
||||||
return redirect("/machines/index_extension")
|
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
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -504,7 +600,7 @@ def edit_mx(request, mxid):
|
||||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in mx.changed_data))
|
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in mx.changed_data))
|
||||||
messages.success(request, "Mx modifié")
|
messages.success(request, "Mx modifié")
|
||||||
return redirect("/machines/index_extension/")
|
return redirect("/machines/index_extension/")
|
||||||
return form({'machineform': mx}, 'machines/machine.html', request)
|
return form({'mxform': mx}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -521,7 +617,7 @@ def del_mx(request):
|
||||||
except ProtectedError:
|
except ProtectedError:
|
||||||
messages.error(request, "Erreur le Mx suivant %s ne peut être supprimé" % mx_del)
|
messages.error(request, "Erreur le Mx suivant %s ne peut être supprimé" % mx_del)
|
||||||
return redirect("/machines/index_extension")
|
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
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -534,7 +630,7 @@ def add_ns(request):
|
||||||
reversion.set_comment("Création")
|
reversion.set_comment("Création")
|
||||||
messages.success(request, "Cet enregistrement ns a été ajouté")
|
messages.success(request, "Cet enregistrement ns a été ajouté")
|
||||||
return redirect("/machines/index_extension")
|
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
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -552,7 +648,7 @@ def edit_ns(request, nsid):
|
||||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in ns.changed_data))
|
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in ns.changed_data))
|
||||||
messages.success(request, "Ns modifié")
|
messages.success(request, "Ns modifié")
|
||||||
return redirect("/machines/index_extension/")
|
return redirect("/machines/index_extension/")
|
||||||
return form({'machineform': ns}, 'machines/machine.html', request)
|
return form({'nsform': ns}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -569,55 +665,55 @@ def del_ns(request):
|
||||||
except ProtectedError:
|
except ProtectedError:
|
||||||
messages.error(request, "Erreur le Ns suivant %s ne peut être supprimé" % ns_del)
|
messages.error(request, "Erreur le Ns suivant %s ne peut être supprimé" % ns_del)
|
||||||
return redirect("/machines/index_extension")
|
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
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
def add_text(request):
|
def add_txt(request):
|
||||||
text = TextForm(request.POST or None)
|
txt = TxtForm(request.POST or None)
|
||||||
if text.is_valid():
|
if txt.is_valid():
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
text.save()
|
txt.save()
|
||||||
reversion.set_user(request.user)
|
reversion.set_user(request.user)
|
||||||
reversion.set_comment("Création")
|
reversion.set_comment("Création")
|
||||||
messages.success(request, "Cet enregistrement text a été ajouté")
|
messages.success(request, "Cet enregistrement text a été ajouté")
|
||||||
return redirect("/machines/index_extension")
|
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
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
def edit_text(request, textid):
|
def edit_txt(request, txtid):
|
||||||
try:
|
try:
|
||||||
text_instance = Text.objects.get(pk=textid)
|
txt_instance = Text.objects.get(pk=txtid)
|
||||||
except Text.DoesNotExist:
|
except Text.DoesNotExist:
|
||||||
messages.error(request, u"Entrée inexistante" )
|
messages.error(request, u"Entrée inexistante" )
|
||||||
return redirect("/machines/index_extension/")
|
return redirect("/machines/index_extension/")
|
||||||
text = TextForm(request.POST or None, instance=text_instance)
|
txt = TxtForm(request.POST or None, instance=txt_instance)
|
||||||
if text.is_valid():
|
if txt.is_valid():
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
text.save()
|
txt.save()
|
||||||
reversion.set_user(request.user)
|
reversion.set_user(request.user)
|
||||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in text.changed_data))
|
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in txt.changed_data))
|
||||||
messages.success(request, "Text modifié")
|
messages.success(request, "Txt modifié")
|
||||||
return redirect("/machines/index_extension/")
|
return redirect("/machines/index_extension/")
|
||||||
return form({'machineform': text}, 'machines/machine.html', request)
|
return form({'txtform': txt}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
def del_text(request):
|
def del_txt(request):
|
||||||
text = DelTextForm(request.POST or None)
|
txt = DelTxtForm(request.POST or None)
|
||||||
if text.is_valid():
|
if txt.is_valid():
|
||||||
text_dels = text.cleaned_data['text']
|
txt_dels = txt.cleaned_data['txt']
|
||||||
for text_del in text_dels:
|
for txt_del in txt_dels:
|
||||||
try:
|
try:
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
text_del.delete()
|
txt_del.delete()
|
||||||
reversion.set_user(request.user)
|
reversion.set_user(request.user)
|
||||||
messages.success(request, "Le text a été supprimé")
|
messages.success(request, "Le txt a été supprimé")
|
||||||
except ProtectedError:
|
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 redirect("/machines/index_extension")
|
||||||
return form({'machineform': text, 'interfaceform': None}, 'machines/machine.html', request)
|
return form({'txtform': txt}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def add_alias(request, interfaceid):
|
def add_alias(request, interfaceid):
|
||||||
|
@ -645,7 +741,7 @@ def add_alias(request, interfaceid):
|
||||||
reversion.set_comment("Création")
|
reversion.set_comment("Création")
|
||||||
messages.success(request, "Cet alias a été ajouté")
|
messages.success(request, "Cet alias a été ajouté")
|
||||||
return redirect("/machines/index_alias/" + str(interfaceid))
|
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
|
@login_required
|
||||||
def edit_alias(request, aliasid):
|
def edit_alias(request, aliasid):
|
||||||
|
@ -665,7 +761,7 @@ def edit_alias(request, aliasid):
|
||||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in alias.changed_data))
|
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in alias.changed_data))
|
||||||
messages.success(request, "Alias modifié")
|
messages.success(request, "Alias modifié")
|
||||||
return redirect("/machines/index_alias/" + str(alias_instance.cname.interface_parent.id))
|
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
|
@login_required
|
||||||
def del_alias(request, interfaceid):
|
def del_alias(request, interfaceid):
|
||||||
|
@ -689,7 +785,7 @@ def del_alias(request, interfaceid):
|
||||||
except ProtectedError:
|
except ProtectedError:
|
||||||
messages.error(request, "Erreur l'alias suivant %s ne peut être supprimé" % alias_del)
|
messages.error(request, "Erreur l'alias suivant %s ne peut être supprimé" % alias_del)
|
||||||
return redirect("/machines/index_alias/" + str(interfaceid))
|
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
|
@login_required
|
||||||
|
@ -703,7 +799,7 @@ def add_service(request):
|
||||||
reversion.set_comment("Création")
|
reversion.set_comment("Création")
|
||||||
messages.success(request, "Cet enregistrement service a été ajouté")
|
messages.success(request, "Cet enregistrement service a été ajouté")
|
||||||
return redirect("/machines/index_service")
|
return redirect("/machines/index_service")
|
||||||
return form({'machineform': service}, 'machines/machine.html', request)
|
return form({'serviceform': service}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -721,7 +817,7 @@ def edit_service(request, serviceid):
|
||||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in service.changed_data))
|
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in service.changed_data))
|
||||||
messages.success(request, "Service modifié")
|
messages.success(request, "Service modifié")
|
||||||
return redirect("/machines/index_service/")
|
return redirect("/machines/index_service/")
|
||||||
return form({'machineform': service}, 'machines/machine.html', request)
|
return form({'serviceform': service}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -738,7 +834,7 @@ def del_service(request):
|
||||||
except ProtectedError:
|
except ProtectedError:
|
||||||
messages.error(request, "Erreur le service suivant %s ne peut être supprimé" % service_del)
|
messages.error(request, "Erreur le service suivant %s ne peut être supprimé" % service_del)
|
||||||
return redirect("/machines/index_service")
|
return redirect("/machines/index_service")
|
||||||
return form({'machineform': service}, 'machines/machine.html', request)
|
return form({'serviceform': service}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -751,7 +847,7 @@ def add_vlan(request):
|
||||||
reversion.set_comment("Création")
|
reversion.set_comment("Création")
|
||||||
messages.success(request, "Cet enregistrement vlan a été ajouté")
|
messages.success(request, "Cet enregistrement vlan a été ajouté")
|
||||||
return redirect("/machines/index_vlan")
|
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
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -769,7 +865,7 @@ def edit_vlan(request, vlanid):
|
||||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in vlan.changed_data))
|
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in vlan.changed_data))
|
||||||
messages.success(request, "Vlan modifié")
|
messages.success(request, "Vlan modifié")
|
||||||
return redirect("/machines/index_vlan/")
|
return redirect("/machines/index_vlan/")
|
||||||
return form({'machineform': vlan}, 'machines/machine.html', request)
|
return form({'vlanform': vlan}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -786,7 +882,7 @@ def del_vlan(request):
|
||||||
except ProtectedError:
|
except ProtectedError:
|
||||||
messages.error(request, "Erreur le Vlan suivant %s ne peut être supprimé" % vlan_del)
|
messages.error(request, "Erreur le Vlan suivant %s ne peut être supprimé" % vlan_del)
|
||||||
return redirect("/machines/index_vlan")
|
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
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -799,7 +895,7 @@ def add_nas(request):
|
||||||
reversion.set_comment("Création")
|
reversion.set_comment("Création")
|
||||||
messages.success(request, "Cet enregistrement nas a été ajouté")
|
messages.success(request, "Cet enregistrement nas a été ajouté")
|
||||||
return redirect("/machines/index_nas")
|
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
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -817,7 +913,7 @@ def edit_nas(request, nasid):
|
||||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in nas.changed_data))
|
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in nas.changed_data))
|
||||||
messages.success(request, "Nas modifié")
|
messages.success(request, "Nas modifié")
|
||||||
return redirect("/machines/index_nas/")
|
return redirect("/machines/index_nas/")
|
||||||
return form({'machineform': nas}, 'machines/machine.html', request)
|
return form({'nasform': nas}, 'machines/machine.html', request)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('infra')
|
@permission_required('infra')
|
||||||
|
@ -834,14 +930,20 @@ def del_nas(request):
|
||||||
except ProtectedError:
|
except ProtectedError:
|
||||||
messages.error(request, "Erreur le Nas suivant %s ne peut être supprimé" % nas_del)
|
messages.error(request, "Erreur le Nas suivant %s ne peut être supprimé" % nas_del)
|
||||||
return redirect("/machines/index_nas")
|
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
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def index(request):
|
def index(request):
|
||||||
options, created = GeneralOption.objects.get_or_create()
|
options, created = GeneralOption.objects.get_or_create()
|
||||||
pagination_large_number = options.pagination_large_number
|
pagination_large_number = options.pagination_large_number
|
||||||
machines_list = Machine.objects.select_related('user').prefetch_related('interface_set__domain__extension').prefetch_related('interface_set__ipv4__ip_type').prefetch_related('interface_set__type__ip_type__extension').prefetch_related('interface_set__domain__related_domain__extension').order_by('pk')
|
machines_list = Machine.objects.select_related('user').prefetch_related('interface_set__domain__extension').prefetch_related('interface_set__ipv4__ip_type').prefetch_related('interface_set__type__ip_type__extension').prefetch_related('interface_set__domain__related_domain__extension')
|
||||||
|
machines_list = SortTable.sort(
|
||||||
|
machines_list,
|
||||||
|
request.GET.get('col'),
|
||||||
|
request.GET.get('order'),
|
||||||
|
SortTable.MACHINES_INDEX
|
||||||
|
)
|
||||||
paginator = Paginator(machines_list, pagination_large_number)
|
paginator = Paginator(machines_list, pagination_large_number)
|
||||||
page = request.GET.get('page')
|
page = request.GET.get('page')
|
||||||
try:
|
try:
|
||||||
|
@ -857,13 +959,13 @@ def index(request):
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def index_iptype(request):
|
def index_iptype(request):
|
||||||
iptype_list = IpType.objects.select_related('extension').order_by('type')
|
iptype_list = IpType.objects.select_related('extension').select_related('vlan').order_by('type')
|
||||||
return render(request, 'machines/index_iptype.html', {'iptype_list':iptype_list})
|
return render(request, 'machines/index_iptype.html', {'iptype_list':iptype_list})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def index_vlan(request):
|
def index_vlan(request):
|
||||||
vlan_list = Vlan.objects.order_by('vlan_id')
|
vlan_list = Vlan.objects.prefetch_related('iptype_set').order_by('vlan_id')
|
||||||
return render(request, 'machines/index_vlan.html', {'vlan_list':vlan_list})
|
return render(request, 'machines/index_vlan.html', {'vlan_list':vlan_list})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -875,17 +977,18 @@ def index_machinetype(request):
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def index_nas(request):
|
def index_nas(request):
|
||||||
nas_list = Nas.objects.select_related('machine_type').order_by('name')
|
nas_list = Nas.objects.select_related('machine_type').select_related('nas_type').order_by('name')
|
||||||
return render(request, 'machines/index_nas.html', {'nas_list':nas_list})
|
return render(request, 'machines/index_nas.html', {'nas_list':nas_list})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def index_extension(request):
|
def index_extension(request):
|
||||||
extension_list = Extension.objects.select_related('origin').order_by('name')
|
extension_list = Extension.objects.select_related('origin').select_related('soa').order_by('name')
|
||||||
|
soa_list = SOA.objects.order_by('name')
|
||||||
mx_list = Mx.objects.order_by('zone').select_related('zone').select_related('name__extension')
|
mx_list = Mx.objects.order_by('zone').select_related('zone').select_related('name__extension')
|
||||||
ns_list = Ns.objects.order_by('zone').select_related('zone').select_related('ns__extension')
|
ns_list = Ns.objects.order_by('zone').select_related('zone').select_related('ns__extension')
|
||||||
text_list = Text.objects.all().select_related('zone')
|
text_list = Text.objects.all().select_related('zone')
|
||||||
return render(request, 'machines/index_extension.html', {'extension_list':extension_list, 'mx_list': mx_list, 'ns_list': ns_list, 'text_list' : text_list})
|
return render(request, 'machines/index_extension.html', {'extension_list':extension_list, 'soa_list': soa_list, 'mx_list': mx_list, 'ns_list': ns_list, 'text_list' : text_list})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def index_alias(request, interfaceid):
|
def index_alias(request, interfaceid):
|
||||||
|
@ -903,8 +1006,8 @@ def index_alias(request, interfaceid):
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def index_service(request):
|
def index_service(request):
|
||||||
service_list = Service.objects.all()
|
service_list = Service.objects.prefetch_related('service_link_set__server__domain__extension').all()
|
||||||
servers_list = Service_link.objects.all()
|
servers_list = Service_link.objects.select_related('server__domain__extension').select_related('service').all()
|
||||||
return render(request, 'machines/index_service.html', {'service_list':service_list, 'servers_list':servers_list})
|
return render(request, 'machines/index_service.html', {'service_list':service_list, 'servers_list':servers_list})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -954,17 +1057,23 @@ def history(request, object, id):
|
||||||
except Extension.DoesNotExist:
|
except Extension.DoesNotExist:
|
||||||
messages.error(request, "Extension inexistante")
|
messages.error(request, "Extension inexistante")
|
||||||
return redirect("/machines/")
|
return redirect("/machines/")
|
||||||
|
elif object == 'soa' and request.user.has_perms(('cableur',)):
|
||||||
|
try:
|
||||||
|
object_instance = SOA.objects.get(pk=id)
|
||||||
|
except SOA.DoesNotExist:
|
||||||
|
messages.error(request, "SOA inexistant")
|
||||||
|
return redirect("/machines/")
|
||||||
elif object == 'mx' and request.user.has_perms(('cableur',)):
|
elif object == 'mx' and request.user.has_perms(('cableur',)):
|
||||||
try:
|
try:
|
||||||
object_instance = Mx.objects.get(pk=id)
|
object_instance = Mx.objects.get(pk=id)
|
||||||
except Mx.DoesNotExist:
|
except Mx.DoesNotExist:
|
||||||
messages.error(request, "Mx inexistant")
|
messages.error(request, "Mx inexistant")
|
||||||
return redirect("/machines/")
|
return redirect("/machines/")
|
||||||
elif object == 'text' and request.user.has_perms(('cableur',)):
|
elif object == 'txt' and request.user.has_perms(('cableur',)):
|
||||||
try:
|
try:
|
||||||
object_instance = Text.objects.get(pk=id)
|
object_instance = Text.objects.get(pk=id)
|
||||||
except Text.DoesNotExist:
|
except Text.DoesNotExist:
|
||||||
messages.error(request, "Text inexistant")
|
messages.error(request, "Txt inexistant")
|
||||||
return redirect("/machines/")
|
return redirect("/machines/")
|
||||||
elif object == 'ns' and request.user.has_perms(('cableur',)):
|
elif object == 'ns' and request.user.has_perms(('cableur',)):
|
||||||
try:
|
try:
|
||||||
|
@ -1012,7 +1121,9 @@ def history(request, object, id):
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def index_portlist(request):
|
def index_portlist(request):
|
||||||
port_list = OuverturePortList.objects.all().order_by('name')
|
port_list = OuverturePortList.objects.prefetch_related('ouvertureport_set')\
|
||||||
|
.prefetch_related('interface_set__domain__extension')\
|
||||||
|
.prefetch_related('interface_set__machine__user').order_by('name')
|
||||||
return render(request, "machines/index_portlist.html", {'port_list':port_list})
|
return render(request, "machines/index_portlist.html", {'port_list':port_list})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -1203,6 +1314,34 @@ def service_servers(request):
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('serveur')
|
@permission_required('serveur')
|
||||||
|
def ouverture_ports(request):
|
||||||
|
r = {'ipv4':{}, 'ipv6':{}}
|
||||||
|
for o in OuverturePortList.objects.all().prefetch_related('ouvertureport_set').prefetch_related('interface_set', 'interface_set__ipv4'):
|
||||||
|
pl = {
|
||||||
|
"tcp_in":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.TCP, io=OuverturePort.IN))),
|
||||||
|
"tcp_out":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.TCP, io=OuverturePort.OUT))),
|
||||||
|
"udp_in":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.UDP, io=OuverturePort.IN))),
|
||||||
|
"udp_out":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.UDP, io=OuverturePort.OUT))),
|
||||||
|
}
|
||||||
|
for i in filter_active_interfaces(o.interface_set):
|
||||||
|
if i.may_have_port_open():
|
||||||
|
d = r['ipv4'].get(i.ipv4.ipv4, {})
|
||||||
|
d["tcp_in"] = d.get("tcp_in",set()).union(pl["tcp_in"])
|
||||||
|
d["tcp_out"] = d.get("tcp_out",set()).union(pl["tcp_out"])
|
||||||
|
d["udp_in"] = d.get("udp_in",set()).union(pl["udp_in"])
|
||||||
|
d["udp_out"] = d.get("udp_out",set()).union(pl["udp_out"])
|
||||||
|
r['ipv4'][i.ipv4.ipv4] = d
|
||||||
|
if i.ipv6_object:
|
||||||
|
d = r['ipv6'].get(i.ipv6, {})
|
||||||
|
d["tcp_in"] = d.get("tcp_in",set()).union(pl["tcp_in"])
|
||||||
|
d["tcp_out"] = d.get("tcp_out",set()).union(pl["tcp_out"])
|
||||||
|
d["udp_in"] = d.get("udp_in",set()).union(pl["udp_in"])
|
||||||
|
d["udp_out"] = d.get("udp_out",set()).union(pl["udp_out"])
|
||||||
|
r['ipv6'][i.ipv6] = d
|
||||||
|
return JSONResponse(r)
|
||||||
|
@csrf_exempt
|
||||||
|
@login_required
|
||||||
|
@permission_required('serveur')
|
||||||
def regen_achieved(request):
|
def regen_achieved(request):
|
||||||
obj = Service_link.objects.filter(service__in=Service.objects.filter(service_type=request.POST['service']), server__in=Interface.objects.filter(domain__in=Domain.objects.filter(name=request.POST['server'])))
|
obj = Service_link.objects.filter(service__in=Service.objects.filter(service_type=request.POST['service']), server__in=Interface.objects.filter(domain__in=Domain.objects.filter(name=request.POST['server'])))
|
||||||
if obj:
|
if obj:
|
||||||
|
|
|
@ -20,35 +20,53 @@
|
||||||
# You should have received a copy of the GNU General Public License along
|
# 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.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
"""
|
||||||
|
Classes admin pour les models de preferences
|
||||||
|
"""
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from reversion.admin import VersionAdmin
|
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 OptionalUserAdmin(VersionAdmin):
|
||||||
|
"""Class admin options user"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class OptionalTopologieAdmin(VersionAdmin):
|
class OptionalTopologieAdmin(VersionAdmin):
|
||||||
|
"""Class admin options topologie"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class OptionalMachineAdmin(VersionAdmin):
|
class OptionalMachineAdmin(VersionAdmin):
|
||||||
|
"""Class admin options machines"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class GeneralOptionAdmin(VersionAdmin):
|
class GeneralOptionAdmin(VersionAdmin):
|
||||||
|
"""Class admin options générales"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ServiceAdmin(VersionAdmin):
|
class ServiceAdmin(VersionAdmin):
|
||||||
|
"""Class admin gestion des services de la page d'accueil"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AssoOptionAdmin(VersionAdmin):
|
class AssoOptionAdmin(VersionAdmin):
|
||||||
|
"""Class admin options de l'asso"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MailMessageOptionAdmin(VersionAdmin):
|
class MailMessageOptionAdmin(VersionAdmin):
|
||||||
|
"""Class admin options mail"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(OptionalUser, OptionalUserAdmin)
|
admin.site.register(OptionalUser, OptionalUserAdmin)
|
||||||
admin.site.register(OptionalMachine, OptionalMachineAdmin)
|
admin.site.register(OptionalMachine, OptionalMachineAdmin)
|
||||||
admin.site.register(OptionalTopologie, OptionalTopologieAdmin)
|
admin.site.register(OptionalTopologie, OptionalTopologieAdmin)
|
||||||
|
|
|
@ -19,66 +19,116 @@
|
||||||
# You should have received a copy of the GNU General Public License along
|
# 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.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 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 __future__ import unicode_literals
|
||||||
|
|
||||||
from django.forms import ModelForm, Form, ValidationError
|
from django.forms import ModelForm, Form
|
||||||
from django import forms
|
from django import forms
|
||||||
from .models import OptionalUser, OptionalMachine, OptionalTopologie, GeneralOption, AssoOption, MailMessageOption, Service
|
from .models import OptionalUser, OptionalMachine, OptionalTopologie
|
||||||
from django.db.models import Q
|
from .models import GeneralOption, AssoOption, MailMessageOption, Service
|
||||||
|
|
||||||
|
|
||||||
class EditOptionalUserForm(ModelForm):
|
class EditOptionalUserForm(ModelForm):
|
||||||
|
"""Formulaire d'édition des options de l'user. (solde, telephone..)"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = OptionalUser
|
model = OptionalUser
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(EditOptionalUserForm, self).__init__(*args, **kwargs)
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
self.fields['is_tel_mandatory'].label = 'Exiger un numéro de téléphone'
|
super(EditOptionalUserForm, self).__init__(
|
||||||
self.fields['user_solde'].label = 'Activation du solde pour les utilisateurs'
|
*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):
|
class EditOptionalMachineForm(ModelForm):
|
||||||
|
"""Options machines (max de machines, etc)"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = OptionalMachine
|
model = OptionalMachine
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(EditOptionalMachineForm, self).__init__(*args, **kwargs)
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
self.fields['password_machine'].label = "Possibilité d'attribuer un mot de passe par interface"
|
super(EditOptionalMachineForm, self).__init__(
|
||||||
self.fields['max_lambdauser_interfaces'].label = "Maximum d'interfaces autorisées pour un user normal"
|
*args,
|
||||||
self.fields['max_lambdauser_aliases'].label = "Maximum d'alias dns autorisés pour un user normal"
|
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):
|
class EditOptionalTopologieForm(ModelForm):
|
||||||
|
"""Options de topologie, formulaire d'edition (vlan par default etc)"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = OptionalTopologie
|
model = OptionalTopologie
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(EditOptionalTopologieForm, self).__init__(*args, **kwargs)
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
self.fields['vlan_decision_ok'].label = "Vlan où placer les machines après acceptation RADIUS"
|
super(EditOptionalTopologieForm, self).__init__(
|
||||||
self.fields['vlan_decision_nok'].label = "Vlan où placer les machines après rejet RADIUS"
|
*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):
|
class EditGeneralOptionForm(ModelForm):
|
||||||
|
"""Options générales (affichages de résultats de recherche, etc)"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = GeneralOption
|
model = GeneralOption
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(EditGeneralOptionForm, self).__init__(*args, **kwargs)
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
self.fields['search_display_page'].label = 'Resultats affichés dans une recherche'
|
super(EditGeneralOptionForm, self).__init__(
|
||||||
self.fields['pagination_number'].label = 'Items par page, taille normale (ex users)'
|
*args,
|
||||||
self.fields['pagination_large_number'].label = 'Items par page, taille élevée (machines)'
|
prefix=prefix,
|
||||||
self.fields['req_expire_hrs'].label = 'Temps avant expiration du lien de reinitialisation de mot de passe (en heures)'
|
**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['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):
|
class EditAssoOptionForm(ModelForm):
|
||||||
|
"""Options de l'asso (addresse, telephone, etc)"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AssoOption
|
model = AssoOption
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
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['name'].label = 'Nom de l\'asso'
|
||||||
self.fields['siret'].label = 'SIRET'
|
self.fields['siret'].label = 'SIRET'
|
||||||
self.fields['adresse1'].label = 'Adresse (ligne 1)'
|
self.fields['adresse1'].label = 'Adresse (ligne 1)'
|
||||||
|
@ -86,22 +136,44 @@ class EditAssoOptionForm(ModelForm):
|
||||||
self.fields['contact'].label = 'Email de contact'
|
self.fields['contact'].label = 'Email de contact'
|
||||||
self.fields['telephone'].label = 'Numéro de téléphone'
|
self.fields['telephone'].label = 'Numéro de téléphone'
|
||||||
self.fields['pseudo'].label = 'Pseudo d\'usage'
|
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):
|
class EditMailMessageOptionForm(ModelForm):
|
||||||
|
"""Formulaire d'edition des messages de bienvenue personnalisés"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = MailMessageOption
|
model = MailMessageOption
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(EditMailMessageOptionForm, self).__init__(*args, **kwargs)
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
self.fields['welcome_mail_fr'].label = 'Message dans le mail de bienvenue en français'
|
super(EditMailMessageOptionForm, self).__init__(
|
||||||
self.fields['welcome_mail_en'].label = 'Message dans le mail de bienvenue en anglais'
|
*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):
|
class ServiceForm(ModelForm):
|
||||||
|
"""Edition, ajout de services sur la page d'accueil"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Service
|
model = Service
|
||||||
fields = '__all__'
|
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):
|
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
preferences/migrations/0021_auto_20171015_1741.py
Normal file
20
preferences/migrations/0021_auto_20171015_1741.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-10-15 15:41
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('preferences', '0020_optionalmachine_ipv6'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='optionaltopologie',
|
||||||
|
name='radius_general_policy',
|
||||||
|
field=models.CharField(choices=[('MACHINE', 'Sur le vlan de la plage ip machine'), ('DEFINED', 'Prédéfini dans "Vlan où placer les machines après acceptation RADIUS"')], default='DEFINED', max_length=32),
|
||||||
|
),
|
||||||
|
]
|
20
preferences/migrations/0022_auto_20171015_1758.py
Normal file
20
preferences/migrations/0022_auto_20171015_1758.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-10-15 15:58
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('preferences', '0021_auto_20171015_1741'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='optionaltopologie',
|
||||||
|
name='radius_general_policy',
|
||||||
|
field=models.CharField(choices=[('MACHINE', 'Sur le vlan de la plage ip machine'), ('DEFINED', 'Prédéfini dans "Vlan où placer les machines après acceptation RADIUS"')], default='DEFINED', max_length=32),
|
||||||
|
),
|
||||||
|
]
|
20
preferences/migrations/0023_auto_20171015_2033.py
Normal file
20
preferences/migrations/0023_auto_20171015_2033.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-10-15 18:33
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('preferences', '0022_auto_20171015_1758'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='optionaltopologie',
|
||||||
|
name='radius_general_policy',
|
||||||
|
field=models.CharField(choices=[('MACHINE', 'Sur le vlan de la plage ip machine'), ('DEFINED', 'Prédéfini dans "Vlan où placer les machines après acceptation RADIUS"')], default='DEFINED', max_length=32),
|
||||||
|
),
|
||||||
|
]
|
|
@ -20,26 +20,38 @@
|
||||||
# You should have received a copy of the GNU General Public License along
|
# 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.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 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 __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from cotisations.models import Paiement
|
from cotisations.models import Paiement
|
||||||
from machines.models import Vlan
|
|
||||||
|
|
||||||
class OptionalUser(models.Model):
|
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"
|
PRETTY_NAME = "Options utilisateur"
|
||||||
|
|
||||||
is_tel_mandatory = models.BooleanField(default=True)
|
is_tel_mandatory = models.BooleanField(default=True)
|
||||||
user_solde = models.BooleanField(default=False)
|
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)
|
gpg_fingerprint = models.BooleanField(default=True)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
"""Creation du mode de paiement par solde"""
|
||||||
if self.user_solde:
|
if self.user_solde:
|
||||||
Paiement.objects.get_or_create(moyen="Solde")
|
Paiement.objects.get_or_create(moyen="Solde")
|
||||||
|
|
||||||
|
|
||||||
class OptionalMachine(models.Model):
|
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"
|
PRETTY_NAME = "Options machines"
|
||||||
|
|
||||||
password_machine = models.BooleanField(default=False)
|
password_machine = models.BooleanField(default=False)
|
||||||
|
@ -47,21 +59,43 @@ class OptionalMachine(models.Model):
|
||||||
max_lambdauser_aliases = models.IntegerField(default=10)
|
max_lambdauser_aliases = models.IntegerField(default=10)
|
||||||
ipv6 = models.BooleanField(default=False)
|
ipv6 = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
|
||||||
class OptionalTopologie(models.Model):
|
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"
|
PRETTY_NAME = "Options topologie"
|
||||||
MACHINE = 'MACHINE'
|
MACHINE = 'MACHINE'
|
||||||
DEFINED = 'DEFINED'
|
DEFINED = 'DEFINED'
|
||||||
CHOICE_RADIUS = (
|
CHOICE_RADIUS = (
|
||||||
(MACHINE, 'Sur le vlan de la plage ip machine'),
|
(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):
|
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"
|
PRETTY_NAME = "Options générales"
|
||||||
|
|
||||||
search_display_page = models.IntegerField(default=15)
|
search_display_page = models.IntegerField(default=15)
|
||||||
|
@ -71,7 +105,10 @@ class GeneralOption(models.Model):
|
||||||
site_name = models.CharField(max_length=32, default="Re2o")
|
site_name = models.CharField(max_length=32, default="Re2o")
|
||||||
email_from = models.EmailField(default="www-data@serveur.net")
|
email_from = models.EmailField(default="www-data@serveur.net")
|
||||||
|
|
||||||
|
|
||||||
class Service(models.Model):
|
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)
|
name = models.CharField(max_length=32)
|
||||||
url = models.URLField()
|
url = models.URLField()
|
||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
|
@ -80,21 +117,32 @@ class Service(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.name)
|
return str(self.name)
|
||||||
|
|
||||||
|
|
||||||
class AssoOption(models.Model):
|
class AssoOption(models.Model):
|
||||||
|
"""Options générales de l'asso : siret, addresse, nom, etc"""
|
||||||
PRETTY_NAME = "Options de l'association"
|
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)
|
siret = models.CharField(default="00000000000000", max_length=32)
|
||||||
adresse1 = models.CharField(default="1 Rue de exemple", max_length=128)
|
adresse1 = models.CharField(default="1 Rue de exemple", max_length=128)
|
||||||
adresse2 = models.CharField(default="94230 Cachan", max_length=128)
|
adresse2 = models.CharField(default="94230 Cachan", max_length=128)
|
||||||
contact = models.EmailField(default="contact@example.org")
|
contact = models.EmailField(default="contact@example.org")
|
||||||
telephone = models.CharField(max_length=15, default="0000000000")
|
telephone = models.CharField(max_length=15, default="0000000000")
|
||||||
pseudo = models.CharField(default="Asso", max_length=32)
|
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):
|
class MailMessageOption(models.Model):
|
||||||
|
"""Reglages, mail de bienvenue et autre"""
|
||||||
PRETTY_NAME = "Options de corps de mail"
|
PRETTY_NAME = "Options de corps de mail"
|
||||||
|
|
||||||
welcome_mail_fr = models.TextField(default="")
|
welcome_mail_fr = models.TextField(default="")
|
||||||
welcome_mail_en = 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 %}
|
{% endcomment %}
|
||||||
|
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
|
{% load massive_bootstrap_form %}
|
||||||
|
|
||||||
{% block title %}Création et modification des préférences{% endblock %}
|
{% 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">
|
<form class="form" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% bootstrap_form options %}
|
{% massive_bootstrap_form options 'utilisateur_asso' %}
|
||||||
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
|
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
|
||||||
</form>
|
</form>
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -19,6 +19,9 @@
|
||||||
# You should have received a copy of the GNU General Public License along
|
# 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.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 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
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -28,15 +31,47 @@ from . import views
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^edit_options/(?P<section>OptionalUser)$', views.edit_options, name='edit-options'),
|
url(
|
||||||
url(r'^edit_options/(?P<section>OptionalMachine)$', views.edit_options, name='edit-options'),
|
r'^edit_options/(?P<section>OptionalUser)$',
|
||||||
url(r'^edit_options/(?P<section>OptionalTopologie)$', views.edit_options, name='edit-options'),
|
views.edit_options,
|
||||||
url(r'^edit_options/(?P<section>GeneralOption)$', views.edit_options, name='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>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'^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'^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_name>service)/(?P<object_id>[0-9]+)$',
|
||||||
|
views.history,
|
||||||
|
name='history'
|
||||||
|
),
|
||||||
url(r'^$', views.display_options, name='display-options'),
|
url(r'^$', views.display_options, name='display-options'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -23,48 +23,53 @@
|
||||||
# App de gestion des machines pour re2o
|
# App de gestion des machines pour re2o
|
||||||
# Gabriel Détraz, Augustin Lemesle
|
# Gabriel Détraz, Augustin Lemesle
|
||||||
# Gplv2
|
# Gplv2
|
||||||
|
"""
|
||||||
|
Vue d'affichage, et de modification des réglages (réglages machine,
|
||||||
|
topologie, users, service...)
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render, redirect
|
||||||
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.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||||
from django.template import Context, RequestContext, loader
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required, permission_required
|
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.db import transaction
|
||||||
|
|
||||||
from reversion.models import Version
|
from reversion.models import Version
|
||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
|
|
||||||
|
from re2o.views import form
|
||||||
from .forms import ServiceForm, DelServiceForm
|
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 models
|
||||||
from . import forms
|
from . import forms
|
||||||
|
|
||||||
def form(ctx, template, request):
|
|
||||||
c = ctx
|
|
||||||
c.update(csrf(request))
|
|
||||||
return render(request, template, c)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def display_options(request):
|
def display_options(request):
|
||||||
useroptions, created = OptionalUser.objects.get_or_create()
|
"""Vue pour affichage des options (en vrac) classé selon les models
|
||||||
machineoptions, created = OptionalMachine.objects.get_or_create()
|
correspondants dans un tableau"""
|
||||||
topologieoptions, created = OptionalTopologie.objects.get_or_create()
|
useroptions, _created = OptionalUser.objects.get_or_create()
|
||||||
generaloptions, created = GeneralOption.objects.get_or_create()
|
machineoptions, _created = OptionalMachine.objects.get_or_create()
|
||||||
assooptions, created = AssoOption.objects.get_or_create()
|
topologieoptions, _created = OptionalTopologie.objects.get_or_create()
|
||||||
mailmessageoptions, created = MailMessageOption.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()
|
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
|
@login_required
|
||||||
@permission_required('admin')
|
@permission_required('admin')
|
||||||
|
@ -73,23 +78,36 @@ def edit_options(request, section):
|
||||||
model = getattr(models, section, None)
|
model = getattr(models, section, None)
|
||||||
form_instance = getattr(forms, 'Edit' + section + 'Form', None)
|
form_instance = getattr(forms, 'Edit' + section + 'Form', None)
|
||||||
if model and form:
|
if model and form:
|
||||||
options_instance, created = model.objects.get_or_create()
|
options_instance, _created = model.objects.get_or_create()
|
||||||
options = form_instance(request.POST or None, instance=options_instance)
|
options = form_instance(
|
||||||
|
request.POST or None,
|
||||||
|
instance=options_instance
|
||||||
|
)
|
||||||
if options.is_valid():
|
if options.is_valid():
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
options.save()
|
options.save()
|
||||||
reversion.set_user(request.user)
|
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")
|
messages.success(request, "Préférences modifiées")
|
||||||
return redirect("/preferences/")
|
return redirect("/preferences/")
|
||||||
return form({'options': options}, 'preferences/edit_preferences.html', request)
|
return form(
|
||||||
|
{'options': options},
|
||||||
|
'preferences/edit_preferences.html',
|
||||||
|
request
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
messages.error(request, "Objet inconnu")
|
messages.error(request, "Objet inconnu")
|
||||||
return redirect("/preferences/")
|
return redirect("/preferences/")
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('admin')
|
@permission_required('admin')
|
||||||
def add_services(request):
|
def add_services(request):
|
||||||
|
"""Ajout d'un service de la page d'accueil"""
|
||||||
services = ServiceForm(request.POST or None)
|
services = ServiceForm(request.POST or None)
|
||||||
if services.is_valid():
|
if services.is_valid():
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
|
@ -98,29 +116,45 @@ def add_services(request):
|
||||||
reversion.set_comment("Création")
|
reversion.set_comment("Création")
|
||||||
messages.success(request, "Cet enregistrement ns a été ajouté")
|
messages.success(request, "Cet enregistrement ns a été ajouté")
|
||||||
return redirect("/preferences/")
|
return redirect("/preferences/")
|
||||||
return form({'preferenceform': services}, 'preferences/preferences.html', request)
|
return form(
|
||||||
|
{'preferenceform': services},
|
||||||
|
'preferences/preferences.html',
|
||||||
|
request
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('admin')
|
@permission_required('admin')
|
||||||
def edit_services(request, servicesid):
|
def edit_services(request, servicesid):
|
||||||
|
"""Edition des services affichés sur la page d'accueil"""
|
||||||
try:
|
try:
|
||||||
services_instance = Service.objects.get(pk=servicesid)
|
services_instance = Service.objects.get(pk=servicesid)
|
||||||
except Service.DoesNotExist:
|
except Service.DoesNotExist:
|
||||||
messages.error(request, u"Entrée inexistante" )
|
messages.error(request, u"Entrée inexistante")
|
||||||
return redirect("/preferences/")
|
return redirect("/preferences/")
|
||||||
services = ServiceForm(request.POST or None, instance=services_instance)
|
services = ServiceForm(request.POST or None, instance=services_instance)
|
||||||
if services.is_valid():
|
if services.is_valid():
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
services.save()
|
services.save()
|
||||||
reversion.set_user(request.user)
|
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é")
|
messages.success(request, "Service modifié")
|
||||||
return redirect("/preferences/")
|
return redirect("/preferences/")
|
||||||
return form({'preferenceform': services}, 'preferences/preferences.html', request)
|
return form(
|
||||||
|
{'preferenceform': services},
|
||||||
|
'preferences/preferences.html',
|
||||||
|
request
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('admin')
|
@permission_required('admin')
|
||||||
def del_services(request):
|
def del_services(request):
|
||||||
|
"""Suppression d'un service de la page d'accueil"""
|
||||||
services = DelServiceForm(request.POST or None)
|
services = DelServiceForm(request.POST or None)
|
||||||
if services.is_valid():
|
if services.is_valid():
|
||||||
services_dels = services.cleaned_data['services']
|
services_dels = services.cleaned_data['services']
|
||||||
|
@ -131,20 +165,28 @@ def del_services(request):
|
||||||
reversion.set_user(request.user)
|
reversion.set_user(request.user)
|
||||||
messages.success(request, "Le services a été supprimée")
|
messages.success(request, "Le services a été supprimée")
|
||||||
except ProtectedError:
|
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 redirect("/preferences/")
|
||||||
return form({'preferenceform': services}, 'preferences/preferences.html', request)
|
return form(
|
||||||
|
{'preferenceform': services},
|
||||||
|
'preferences/preferences.html',
|
||||||
|
request
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('cableur')
|
@permission_required('cableur')
|
||||||
def history(request, object, id):
|
def history(request, object_name, object_id):
|
||||||
if object == 'service':
|
"""Historique de creation et de modification d'un service affiché sur
|
||||||
|
la page d'accueil"""
|
||||||
|
if object_name == 'service':
|
||||||
try:
|
try:
|
||||||
object_instance = Service.objects.get(pk=id)
|
object_instance = Service.objects.get(pk=object_id)
|
||||||
except Service.DoesNotExist:
|
except Service.DoesNotExist:
|
||||||
messages.error(request, "Service inexistant")
|
messages.error(request, "Service inexistant")
|
||||||
return redirect("/preferences/")
|
return redirect("/preferences/")
|
||||||
options, created = GeneralOption.objects.get_or_create()
|
options, _created = GeneralOption.objects.get_or_create()
|
||||||
pagination_number = options.pagination_number
|
pagination_number = options.pagination_number
|
||||||
reversions = Version.objects.get_for_object(object_instance)
|
reversions = Version.objects.get_for_object(object_instance)
|
||||||
paginator = Paginator(reversions, pagination_number)
|
paginator = Paginator(reversions, pagination_number)
|
||||||
|
@ -157,4 +199,7 @@ def history(request, object, id):
|
||||||
except EmptyPage:
|
except EmptyPage:
|
||||||
# If page is out of range (e.g. 9999), deliver last page of results.
|
# If page is out of range (e.g. 9999), deliver last page of results.
|
||||||
reversions = paginator.page(paginator.num_pages)
|
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
|
# 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.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 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 __future__ import unicode_literals
|
||||||
|
|
||||||
from machines.models import Interface, Machine
|
|
||||||
from preferences.models import GeneralOption, OptionalMachine
|
from preferences.models import GeneralOption, OptionalMachine
|
||||||
|
|
||||||
|
|
||||||
def context_user(request):
|
def context_user(request):
|
||||||
general_options, created = GeneralOption.objects.get_or_create()
|
"""Fonction de context lorsqu'un user est logué (ou non),
|
||||||
machine_options, created = OptionalMachine.objects.get_or_create()
|
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
|
user = request.user
|
||||||
if user.is_authenticated():
|
if user.is_authenticated():
|
||||||
interfaces = user.user_interfaces()
|
interfaces = user.user_interfaces()
|
||||||
|
@ -52,8 +56,8 @@ def context_user(request):
|
||||||
'is_bofh': is_bofh,
|
'is_bofh': is_bofh,
|
||||||
'is_trez': is_trez,
|
'is_trez': is_trez,
|
||||||
'is_infra': is_infra,
|
'is_infra': is_infra,
|
||||||
'is_admin' : is_admin,
|
'is_admin': is_admin,
|
||||||
'interfaces': interfaces,
|
'interfaces': interfaces,
|
||||||
'site_name': general_options.site_name,
|
'site_name': general_options.site_name,
|
||||||
'ipv6_enabled' : machine_options.ipv6,
|
'ipv6_enabled': machine_options.ipv6,
|
||||||
}
|
}
|
||||||
|
|
809
re2o/templatetags/massive_bootstrap_form.py
Normal file
809
re2o/templatetags/massive_bootstrap_form.py
Normal file
|
@ -0,0 +1,809 @@
|
||||||
|
# -*- 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.
|
||||||
|
|
||||||
|
""" Templatetag used to render massive django form selects into bootstrap
|
||||||
|
forms that can still be manipulating even if there is multiple tens of
|
||||||
|
thousands of elements in the select. It's made possible using JS libaries
|
||||||
|
Twitter Typeahead and Splitree's Tokenfield.
|
||||||
|
See docstring of massive_bootstrap_form for a detailed explaantion on how
|
||||||
|
to use this templatetag.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.forms import TextInput
|
||||||
|
from django.forms.widgets import Select
|
||||||
|
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 :
|
||||||
|
'update_on' : {
|
||||||
|
'field_A' : [ 'id0', 'id1', ... ] ,
|
||||||
|
'field_B' : ... ,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_select (optional)
|
||||||
|
A dict of boolean telling if the form should either generate
|
||||||
|
the normal select (set to true) and then use it to generate
|
||||||
|
the possible choices and then remove it or either (set to
|
||||||
|
false) generate the choices variable in this tag and do not
|
||||||
|
send any select.
|
||||||
|
Sending the select before can be usefull to permit the use
|
||||||
|
without any JS enabled but it will execute more code locally
|
||||||
|
for the client so the loading might be slower.
|
||||||
|
If not specified, this variable is set to true for each field
|
||||||
|
Example :
|
||||||
|
'gen_select' : {
|
||||||
|
'field_A': True ,
|
||||||
|
'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>'
|
||||||
|
[, ... ] ] ]
|
||||||
|
} ],
|
||||||
|
[, 'gen_select': {
|
||||||
|
[ '<field1>': '<gen_select1>'
|
||||||
|
[, '<field2>': '<gen_select2>'
|
||||||
|
[, ... ] ] ]
|
||||||
|
} ]
|
||||||
|
} ]
|
||||||
|
[ <standard boostrap_form parameters> ]
|
||||||
|
%}
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
|
||||||
|
{% massive_bootstrap_form form 'ipv4' choices='[...]' %}
|
||||||
|
"""
|
||||||
|
|
||||||
|
mbf_form = MBFForm(form, mbf_fields.split(','), *args, **kwargs)
|
||||||
|
return mbf_form.render()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MBFForm():
|
||||||
|
""" An object to hold all the information and useful methods needed to
|
||||||
|
create and render a massive django form into an actual HTML and JS
|
||||||
|
code able to handle it correctly.
|
||||||
|
Every field that is not listed is rendered as a normal bootstrap_field.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, form, mbf_fields, *args, **kwargs):
|
||||||
|
# The django form object
|
||||||
|
self.form = form
|
||||||
|
# The fields on which to use JS
|
||||||
|
self.fields = mbf_fields
|
||||||
|
|
||||||
|
# Other bootstrap_form arguments to render the fields
|
||||||
|
self.args = args
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
# Fields to exclude form the form rendering
|
||||||
|
self.exclude = self.kwargs.get('exclude', '').split(',')
|
||||||
|
|
||||||
|
# All the mbf parameters specified byt the user
|
||||||
|
param = kwargs.pop('mbf_param', {})
|
||||||
|
self.choices = param.get('choices', {})
|
||||||
|
self.engine = param.get('engine', {})
|
||||||
|
self.match_func = param.get('match_func', {})
|
||||||
|
self.update_on = param.get('update_on', {})
|
||||||
|
self.gen_select = param.get('gen_select', {})
|
||||||
|
self.hidden_fields = [h.name for h in self.form.hidden_fields()]
|
||||||
|
|
||||||
|
# HTML code to insert inside a template
|
||||||
|
self.html = ""
|
||||||
|
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
""" HTML code for the fully rendered form with all the necessary form
|
||||||
|
"""
|
||||||
|
for name, field in self.form.fields.items():
|
||||||
|
if not name in self.exclude:
|
||||||
|
|
||||||
|
if name in self.fields and not name in self.hidden_fields:
|
||||||
|
mbf_field = MBFField(
|
||||||
|
name,
|
||||||
|
field,
|
||||||
|
field.get_bound_field(self.form, name),
|
||||||
|
self.choices.get(name, None),
|
||||||
|
self.engine.get(name, None),
|
||||||
|
self.match_func.get(name, None),
|
||||||
|
self.update_on.get(name, None),
|
||||||
|
self.gen_select.get(name, True),
|
||||||
|
*self.args,
|
||||||
|
**self.kwargs
|
||||||
|
)
|
||||||
|
self.html += mbf_field.render()
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.html += render_field(
|
||||||
|
field.get_bound_field(self.form, name),
|
||||||
|
*self.args,
|
||||||
|
**self.kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
return mark_safe(self.html)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MBFField():
|
||||||
|
""" An object to hold all the information and useful methods needed to
|
||||||
|
create and render a massive django form field into an actual HTML and JS
|
||||||
|
code able to handle it correctly.
|
||||||
|
Twitter Typeahead is used for the display and the matching of queries and
|
||||||
|
in case of a MultipleSelect, Sliptree's Tokenfield is also used to manage
|
||||||
|
multiple values.
|
||||||
|
A div with only non visible elements is created after the div containing
|
||||||
|
the displayed input. It's used to store the actual data that will be sent
|
||||||
|
to the server """
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, name_, field_, bound_, choices_, engine_, match_func_,
|
||||||
|
update_on_, gen_select_, *args_, **kwargs_):
|
||||||
|
|
||||||
|
# Verify this field is a Select (or MultipleSelect) (only supported)
|
||||||
|
if not isinstance(field_.widget, Select):
|
||||||
|
raise ValueError(
|
||||||
|
('Field named {f_name} is not a Select and'
|
||||||
|
'can\'t be rendered with massive_bootstrap_form.'
|
||||||
|
).format(
|
||||||
|
f_name=name_
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Name of the field
|
||||||
|
self.name = name_
|
||||||
|
# Django field object
|
||||||
|
self.field = field_
|
||||||
|
# Bound Django field associated with field
|
||||||
|
self.bound = bound_
|
||||||
|
|
||||||
|
# Id for the main visible input
|
||||||
|
self.input_id = self.bound.auto_id
|
||||||
|
# Id for a hidden input used to store the value
|
||||||
|
self.hidden_id = self.input_id + '_hidden'
|
||||||
|
# Id for another div containing hidden inputs and script
|
||||||
|
self.div2_id = self.input_id + '_div'
|
||||||
|
|
||||||
|
# Should the standard select should be generated
|
||||||
|
self.gen_select = gen_select_
|
||||||
|
# Is it select with multiple values possible (use of tokenfield)
|
||||||
|
self.multiple = self.field.widget.allow_multiple_selected
|
||||||
|
# JS for the choices variable (user specified or default)
|
||||||
|
self.choices = choices_ or self.default_choices()
|
||||||
|
# JS for the engine variable (typeahead) (user specified or default)
|
||||||
|
self.engine = engine_ or self.default_engine()
|
||||||
|
# JS for the matching function (typeahead) (user specified or default)
|
||||||
|
self.match_func = match_func_ or self.default_match_func()
|
||||||
|
# JS for the datasets variable (typeahead) (user specified or default)
|
||||||
|
self.datasets = self.default_datasets()
|
||||||
|
# Ids of other fields to bind a reset/reload with when changed
|
||||||
|
self.update_on = update_on_ or []
|
||||||
|
|
||||||
|
# Whole HTML code to insert in the template
|
||||||
|
self.html = ""
|
||||||
|
# JS code in the script tag
|
||||||
|
self.js_script = ""
|
||||||
|
# Input tag to display instead of select
|
||||||
|
self.replace_input = None
|
||||||
|
|
||||||
|
# Other bootstrap_form arguments to render the fields
|
||||||
|
self.args = args_
|
||||||
|
self.kwargs = kwargs_
|
||||||
|
|
||||||
|
|
||||||
|
def default_choices(self):
|
||||||
|
""" JS code of the variable choices_<fieldname> """
|
||||||
|
|
||||||
|
if self.gen_select:
|
||||||
|
return (
|
||||||
|
'function plop(o) {{'
|
||||||
|
'var c = [];'
|
||||||
|
'for( let i=0 ; i<o.length ; i++) {{'
|
||||||
|
' c.push( {{ key: o[i].value, value: o[i].text }} );'
|
||||||
|
'}}'
|
||||||
|
'return c;'
|
||||||
|
'}} ($("#{select_id}")[0].options)'
|
||||||
|
).format(
|
||||||
|
select_id=self.input_id
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return '[{objects}]'.format(
|
||||||
|
objects=','.join(
|
||||||
|
['{{key:{k},value:"{v}"}}'.format(
|
||||||
|
k=choice[0] if choice[0] != '' else '""',
|
||||||
|
v=choice[1]
|
||||||
|
) for choice in self.field.choices]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def default_engine(self):
|
||||||
|
""" Default JS code of the variable engine_<field_name> """
|
||||||
|
return (
|
||||||
|
'new Bloodhound({{'
|
||||||
|
' datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"),'
|
||||||
|
' queryTokenizer: Bloodhound.tokenizers.whitespace,'
|
||||||
|
' local: choices_{name},'
|
||||||
|
' identify: function(obj) {{ return obj.key; }}'
|
||||||
|
'}})'
|
||||||
|
).format(
|
||||||
|
name=self.name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def default_datasets(self):
|
||||||
|
""" Default JS script of the datasets to use with typeahead """
|
||||||
|
return (
|
||||||
|
'{{'
|
||||||
|
' hint: true,'
|
||||||
|
' highlight: true,'
|
||||||
|
' minLength: 0'
|
||||||
|
'}},'
|
||||||
|
'{{'
|
||||||
|
' display: "value",'
|
||||||
|
' name: "{name}",'
|
||||||
|
' source: {match_func}'
|
||||||
|
'}}'
|
||||||
|
).format(
|
||||||
|
name=self.name,
|
||||||
|
match_func=self.match_func
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def default_match_func(self):
|
||||||
|
""" Default JS code of the matching function to use with typeahed """
|
||||||
|
return (
|
||||||
|
'function ( q, sync ) {{'
|
||||||
|
' if ( q === "" ) {{'
|
||||||
|
' var first = choices_{name}.slice( 0, 5 ).map('
|
||||||
|
' function ( obj ) {{ return obj.key; }}'
|
||||||
|
' );'
|
||||||
|
' sync( engine_{name}.get( first ) );'
|
||||||
|
' }} else {{'
|
||||||
|
' engine_{name}.search( q, sync );'
|
||||||
|
' }}'
|
||||||
|
'}}'
|
||||||
|
).format(
|
||||||
|
name=self.name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
""" HTML code for the fully rendered field """
|
||||||
|
self.gen_displayed_div()
|
||||||
|
self.gen_hidden_div()
|
||||||
|
return mark_safe(self.html)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_displayed_div(self):
|
||||||
|
""" Generate HTML code for the div that contains displayed tags """
|
||||||
|
if self.gen_select:
|
||||||
|
self.html += render_field(
|
||||||
|
self.bound,
|
||||||
|
*self.args,
|
||||||
|
**self.kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
self.field.widget = TextInput(
|
||||||
|
attrs={
|
||||||
|
'name': 'mbf_'+self.name,
|
||||||
|
'placeholder': self.field.empty_label
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.replace_input = render_field(
|
||||||
|
self.bound,
|
||||||
|
*self.args,
|
||||||
|
**self.kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.gen_select:
|
||||||
|
self.html += self.replace_input
|
||||||
|
|
||||||
|
|
||||||
|
def gen_hidden_div(self):
|
||||||
|
""" Generate HTML code for the div that contains hidden tags """
|
||||||
|
self.gen_full_js()
|
||||||
|
|
||||||
|
content = self.js_script
|
||||||
|
if not self.multiple and not self.gen_select:
|
||||||
|
content += self.hidden_input()
|
||||||
|
|
||||||
|
self.html += render_tag(
|
||||||
|
'div',
|
||||||
|
content=content,
|
||||||
|
attrs={'id': self.div2_id}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def hidden_input(self):
|
||||||
|
""" HTML for the hidden input element """
|
||||||
|
return render_tag(
|
||||||
|
'input',
|
||||||
|
attrs={
|
||||||
|
'id': self.hidden_id,
|
||||||
|
'name': self.bound.html_name,
|
||||||
|
'type': 'hidden',
|
||||||
|
'value': self.bound.value() or ""
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_full_js(self):
|
||||||
|
""" Generate the full script tag containing the JS code """
|
||||||
|
self.create_js()
|
||||||
|
self.fill_js()
|
||||||
|
self.get_script()
|
||||||
|
|
||||||
|
|
||||||
|
def create_js(self):
|
||||||
|
""" Generate a template for the whole script to use depending on
|
||||||
|
gen_select and multiple """
|
||||||
|
if self.gen_select:
|
||||||
|
if self.multiple:
|
||||||
|
self.js_script = (
|
||||||
|
'$( "#{input_id}" ).ready( function() {{'
|
||||||
|
' var choices_{f_name} = {choices};'
|
||||||
|
' {del_select}'
|
||||||
|
' 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", {tok_create} );'
|
||||||
|
' $( "#{input_id}" ).bind( "tokenfield:edittoken", {tok_edit} );'
|
||||||
|
' $( "#{input_id}" ).bind( "tokenfield:removetoken", {tok_remove} );'
|
||||||
|
' {tok_updates}'
|
||||||
|
' setup_{f_name}();'
|
||||||
|
' {tok_init_input}'
|
||||||
|
'}} );'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.js_script = (
|
||||||
|
'$( "#{input_id}" ).ready( function() {{'
|
||||||
|
' var choices_{f_name} = {choices};'
|
||||||
|
' {del_select}'
|
||||||
|
' {gen_hidden}'
|
||||||
|
' 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", {typ_select} );'
|
||||||
|
' $( "#{input_id}" ).bind( "typeahead:change", {typ_change} );'
|
||||||
|
' {typ_updates}'
|
||||||
|
' setup_{f_name}();'
|
||||||
|
' {typ_init_input}'
|
||||||
|
'}} );'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if self.multiple:
|
||||||
|
self.js_script = (
|
||||||
|
'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", {tok_create} );'
|
||||||
|
'$( "#{input_id}" ).bind( "tokenfield:edittoken", {tok_edit} );'
|
||||||
|
'$( "#{input_id}" ).bind( "tokenfield:removetoken", {tok_remove} );'
|
||||||
|
'{tok_updates}'
|
||||||
|
'$( "#{input_id}" ).ready( function() {{'
|
||||||
|
' setup_{f_name}();'
|
||||||
|
' {tok_init_input}'
|
||||||
|
'}} );'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.js_script = (
|
||||||
|
'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", {typ_select} );'
|
||||||
|
'$( "#{input_id}" ).bind( "typeahead:change", {typ_change} );'
|
||||||
|
'{typ_updates}'
|
||||||
|
'$( "#{input_id}" ).ready( function() {{'
|
||||||
|
' setup_{f_name}();'
|
||||||
|
' {typ_init_input}'
|
||||||
|
'}} );'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def fill_js(self):
|
||||||
|
""" Fill the template with the correct values """
|
||||||
|
self.js_script = self.js_script.format(
|
||||||
|
f_name=self.name,
|
||||||
|
choices=self.choices,
|
||||||
|
del_select=self.del_select(),
|
||||||
|
gen_hidden=self.gen_hidden(),
|
||||||
|
engine=self.engine,
|
||||||
|
input_id=self.input_id,
|
||||||
|
datasets=self.datasets,
|
||||||
|
typ_select=self.typeahead_select(),
|
||||||
|
typ_change=self.typeahead_change(),
|
||||||
|
tok_create=self.tokenfield_create(),
|
||||||
|
tok_edit=self.tokenfield_edit(),
|
||||||
|
tok_remove=self.tokenfield_remove(),
|
||||||
|
typ_updates=self.typeahead_updates(),
|
||||||
|
tok_updates=self.tokenfield_updates(),
|
||||||
|
tok_init_input=self.tokenfield_init_input(),
|
||||||
|
typ_init_input=self.typeahead_init_input()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_script(self):
|
||||||
|
""" Insert the JS code inside a script tag """
|
||||||
|
self.js_script = render_tag('script', content=mark_safe(self.js_script))
|
||||||
|
|
||||||
|
|
||||||
|
def del_select(self):
|
||||||
|
""" JS code to delete the select if it has been generated and replace
|
||||||
|
it with an input. """
|
||||||
|
return (
|
||||||
|
'var p = $("#{select_id}").parent()[0];'
|
||||||
|
'var new_input = `{replace_input}`;'
|
||||||
|
'p.innerHTML = new_input;'
|
||||||
|
).format(
|
||||||
|
select_id=self.input_id,
|
||||||
|
replace_input=self.replace_input
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_hidden(self):
|
||||||
|
""" JS code to add a hidden tag to store the value. """
|
||||||
|
return (
|
||||||
|
'var d = $("#{div2_id}")[0];'
|
||||||
|
'var i = document.createElement("input");'
|
||||||
|
'i.id = "{hidden_id}";'
|
||||||
|
'i.name = "{html_name}";'
|
||||||
|
'i.value = "";'
|
||||||
|
'i.type = "hidden";'
|
||||||
|
'd.appendChild(i);'
|
||||||
|
).format(
|
||||||
|
div2_id=self.div2_id,
|
||||||
|
hidden_id=self.hidden_id,
|
||||||
|
html_name=self.bound.html_name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def typeahead_init_input(self):
|
||||||
|
""" JS code to init the fields values """
|
||||||
|
init_key = self.bound.value() or '""'
|
||||||
|
return (
|
||||||
|
'$( "#{input_id}" ).typeahead("val", {init_val});'
|
||||||
|
'$( "#{hidden_id}" ).val( {init_key} );'
|
||||||
|
).format(
|
||||||
|
input_id=self.input_id,
|
||||||
|
init_val='""' if init_key == '""' else
|
||||||
|
'engine_{name}.get( {init_key} )[0].value'.format(
|
||||||
|
name=self.name,
|
||||||
|
init_key=init_key
|
||||||
|
),
|
||||||
|
init_key=init_key,
|
||||||
|
hidden_id=self.hidden_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def typeahead_reset_input(self):
|
||||||
|
""" JS code to reset the fields values """
|
||||||
|
return (
|
||||||
|
'$( "#{input_id}" ).typeahead("val", "");'
|
||||||
|
'$( "#{hidden_id}" ).val( "" );'
|
||||||
|
).format(
|
||||||
|
input_id=self.input_id,
|
||||||
|
hidden_id=self.hidden_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def typeahead_select(self):
|
||||||
|
""" JS code to create 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=self.hidden_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def typeahead_change(self):
|
||||||
|
""" JS code of 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=self.input_id,
|
||||||
|
hidden_id=self.hidden_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def typeahead_updates(self):
|
||||||
|
""" JS code for binding external fields changes with a reset """
|
||||||
|
reset_input = self.typeahead_reset_input()
|
||||||
|
updates = [
|
||||||
|
(
|
||||||
|
'$( "#{u_id}" ).change( function() {{'
|
||||||
|
' setup_{name}();'
|
||||||
|
' {reset_input}'
|
||||||
|
'}} );'
|
||||||
|
).format(
|
||||||
|
u_id=u_id,
|
||||||
|
name=self.name,
|
||||||
|
reset_input=reset_input
|
||||||
|
) for u_id in self.update_on]
|
||||||
|
return ''.join(updates)
|
||||||
|
|
||||||
|
|
||||||
|
def tokenfield_init_input(self):
|
||||||
|
""" JS code to init the fields values """
|
||||||
|
init_key = self.bound.value() or '""'
|
||||||
|
return (
|
||||||
|
'$( "#{input_id}" ).tokenfield("setTokens", {init_val});'
|
||||||
|
).format(
|
||||||
|
input_id=self.input_id,
|
||||||
|
init_val='""' if init_key == '""' else (
|
||||||
|
'engine_{name}.get( {init_key} ).map('
|
||||||
|
' function(o) {{ return o.value; }}'
|
||||||
|
')').format(
|
||||||
|
name=self.name,
|
||||||
|
init_key=init_key
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def tokenfield_reset_input(self):
|
||||||
|
""" JS code to reset the fields values """
|
||||||
|
return (
|
||||||
|
'$( "#{input_id}" ).tokenfield("setTokens", "");'
|
||||||
|
).format(
|
||||||
|
input_id=self.input_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def tokenfield_create(self):
|
||||||
|
""" JS code 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_{name}.length &&'
|
||||||
|
' choices_{name}[i].value !== data ) {{'
|
||||||
|
' i++;'
|
||||||
|
' }}'
|
||||||
|
' if ( i === choices_{name}.length ) {{ return false; }}'
|
||||||
|
' k = choices_{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 = "{html_name}";'
|
||||||
|
' $( "#{div2_id}" ).append(new_input);'
|
||||||
|
'}}'
|
||||||
|
).format(
|
||||||
|
name=self.name,
|
||||||
|
hidden_id=self.hidden_id,
|
||||||
|
html_name=self.bound.html_name,
|
||||||
|
div2_id=self.div2_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def tokenfield_edit(self):
|
||||||
|
""" JS code 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_{name}.length &&'
|
||||||
|
' choices_{name}[i].value !== data ) {{'
|
||||||
|
' i++;'
|
||||||
|
' }}'
|
||||||
|
' if ( i === choices_{name}.length ) {{ return true; }}'
|
||||||
|
' k = choices_{name}[i].key;'
|
||||||
|
' }}'
|
||||||
|
' var old_input = document.getElementById('
|
||||||
|
' "{hidden_id}_"+k.toString()'
|
||||||
|
' );'
|
||||||
|
' old_input.parentNode.removeChild(old_input);'
|
||||||
|
'}}'
|
||||||
|
).format(
|
||||||
|
name=self.name,
|
||||||
|
hidden_id=self.hidden_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def tokenfield_remove(self):
|
||||||
|
""" JS code 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_{name}.length &&'
|
||||||
|
' choices_{name}[i].value !== data ) {{'
|
||||||
|
' i++;'
|
||||||
|
' }}'
|
||||||
|
' if ( i === choices_{name}.length ) {{ return true; }}'
|
||||||
|
' k = choices_{name}[i].key;'
|
||||||
|
' }}'
|
||||||
|
' var old_input = document.getElementById('
|
||||||
|
' "{hidden_id}_"+k.toString()'
|
||||||
|
' );'
|
||||||
|
' old_input.parentNode.removeChild(old_input);'
|
||||||
|
'}}'
|
||||||
|
).format(
|
||||||
|
name=self.name,
|
||||||
|
hidden_id=self.hidden_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def tokenfield_updates(self):
|
||||||
|
""" JS code for binding external fields changes with a reset """
|
||||||
|
reset_input = self.tokenfield_reset_input()
|
||||||
|
updates = [
|
||||||
|
(
|
||||||
|
'$( "#{u_id}" ).change( function() {{'
|
||||||
|
' setup_{name}();'
|
||||||
|
' {reset_input}'
|
||||||
|
'}} );'
|
||||||
|
).format(
|
||||||
|
u_id=u_id,
|
||||||
|
name=self.name,
|
||||||
|
reset_input=reset_input
|
||||||
|
) for u_id in self.update_on]
|
||||||
|
return ''.join(updates)
|
105
re2o/templatetags/url_insert_param.py
Normal file
105
re2o/templatetags/url_insert_param.py
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
# -*- 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Templatetag used to write a URL (specified or current one) and adding
|
||||||
|
or inserting specific parameters into the query part without deleting
|
||||||
|
the other parameters.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def url_insert_param(url="", **kwargs):
|
||||||
|
"""
|
||||||
|
Return the URL with some specific parameters inserted into the query
|
||||||
|
part. If a URL has already some parameters, those requested will be
|
||||||
|
modified if already exisiting or will be added and the other parameters
|
||||||
|
will stay unmodified. If parameters with the same name are already in the
|
||||||
|
URL and a value is specified for this parameter, it will replace all
|
||||||
|
existing parameters.
|
||||||
|
|
||||||
|
**Tag name**::
|
||||||
|
|
||||||
|
url_insert_param
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
|
||||||
|
url (optional)
|
||||||
|
The URL to use as a base. The parameters will be added to this URL.
|
||||||
|
If not specified, it will only return the query part of the URL
|
||||||
|
("?a=foo&b=bar" for example).
|
||||||
|
Example : "https://example.com/bar?foo=0&thing=abc"
|
||||||
|
|
||||||
|
other arguments
|
||||||
|
Any other key-value argument will be used. The key is considered as
|
||||||
|
the name of the parameter to insert/modify and the value is the one
|
||||||
|
used.
|
||||||
|
Example : q="foo" search="bar" name="johnDoe"
|
||||||
|
will return as ?<existing_param>&q=foo&search=bar&name=johnDoe
|
||||||
|
|
||||||
|
**Usage**::
|
||||||
|
|
||||||
|
{% url_insert_param [URL] [param1=val1 [param2=val2 [...]]] %}
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
{% url_insert_param a=0 b="bar" %}
|
||||||
|
return "?a=0&b=bar"
|
||||||
|
|
||||||
|
{% url_insert_param "url.net/foo.html" a=0 b="bar" %}
|
||||||
|
return "url.net/foo.html?a=0&b=bar"
|
||||||
|
|
||||||
|
{% url_insert_param "url.net/foo.html?c=keep" a=0 b="bar" %}
|
||||||
|
return "url.net/foo.html?c=keep&a=0&b=bar"
|
||||||
|
|
||||||
|
{% url_insert_param "url.net/foo.html?a=del" a=0 b="bar" %}
|
||||||
|
return "url.net/foo.html?a=0&b=bar"
|
||||||
|
|
||||||
|
{% url_insert_param "url.net/foo.html?a=del&c=keep" a=0 b="bar" %}
|
||||||
|
return "url.net/foo.hmtl?a=0&c=keep&b=bar"
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Get existing parameters in the url
|
||||||
|
params = {}
|
||||||
|
if '?' in url:
|
||||||
|
url, parameters = url.split('?', maxsplit=1)
|
||||||
|
for parameter in parameters.split('&'):
|
||||||
|
p_name, p_value = parameter.split('=', maxsplit=1)
|
||||||
|
if p_name not in params:
|
||||||
|
params[p_name] = []
|
||||||
|
params[p_name].append(p_value)
|
||||||
|
|
||||||
|
# Add the request parameters to the list of parameters
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
params[key] = [value]
|
||||||
|
|
||||||
|
# Write the url
|
||||||
|
url += '?'
|
||||||
|
for param, value_list in params.items():
|
||||||
|
for value in value_list:
|
||||||
|
url += str(param) + '=' + str(value) + '&'
|
||||||
|
|
||||||
|
# Remove the last '&' (or '?' if no parameters)
|
||||||
|
return url[:-1]
|
10
re2o/urls.py
10
re2o/urls.py
|
@ -49,10 +49,16 @@ urlpatterns = [
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
url(r'^admin/', include(admin.site.urls)),
|
||||||
url(r'^users/', include('users.urls', namespace='users')),
|
url(r'^users/', include('users.urls', namespace='users')),
|
||||||
url(r'^search/', include('search.urls', namespace='search')),
|
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'^machines/', include('machines.urls', namespace='machines')),
|
||||||
url(r'^topologie/', include('topologie.urls', namespace='topologie')),
|
url(r'^topologie/', include('topologie.urls', namespace='topologie')),
|
||||||
url(r'^logs/', include('logs.urls', namespace='logs')),
|
url(r'^logs/', include('logs.urls', namespace='logs')),
|
||||||
url(r'^preferences/', include('preferences.urls', namespace='preferences')),
|
url(
|
||||||
|
r'^preferences/',
|
||||||
|
include('preferences.urls', namespace='preferences')
|
||||||
|
),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
264
re2o/utils.py
Normal file
264
re2o/utils.py
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
# -*- 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 Adherent, 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(
|
||||||
|
Q(type_cotisation='All') | Q(type_cotisation='Adhesion'),
|
||||||
|
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(
|
||||||
|
Q(type_cotisation='All') | Q(type_cotisation='Connexion'),
|
||||||
|
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)
|
||||||
|
|
||||||
|
class SortTable:
|
||||||
|
""" Class gathering uselful stuff to sort the colums of a table, according
|
||||||
|
to the column and order requested. It's used with a dict of possible
|
||||||
|
values and associated model_fields """
|
||||||
|
|
||||||
|
# All the possible possible values
|
||||||
|
# The naming convention is based on the URL or the views function
|
||||||
|
# The syntax to describe the sort to apply is a dict where the keys are
|
||||||
|
# the url value and the values are a list of model field name to use to
|
||||||
|
# order the request. They are applied in the order they are given.
|
||||||
|
# A 'default' might be provided to specify what to do if the requested col
|
||||||
|
# doesn't match any keys.
|
||||||
|
USERS_INDEX = {
|
||||||
|
'user_name': ['name'],
|
||||||
|
'user_surname': ['surname'],
|
||||||
|
'user_pseudo': ['pseudo'],
|
||||||
|
'user_room': ['room'],
|
||||||
|
'default': ['state', 'pseudo']
|
||||||
|
}
|
||||||
|
USERS_INDEX_BAN = {
|
||||||
|
'ban_user': ['user__pseudo'],
|
||||||
|
'ban_start': ['date_start'],
|
||||||
|
'ban_end': ['date_end'],
|
||||||
|
'default': ['-date_end']
|
||||||
|
}
|
||||||
|
USERS_INDEX_WHITE = {
|
||||||
|
'white_user': ['user__pseudo'],
|
||||||
|
'white_start': ['date_start'],
|
||||||
|
'white_end': ['date_end'],
|
||||||
|
'default': ['-date_end']
|
||||||
|
}
|
||||||
|
MACHINES_INDEX = {
|
||||||
|
'machine_name': ['name'],
|
||||||
|
'default': ['pk']
|
||||||
|
}
|
||||||
|
COTISATIONS_INDEX = {
|
||||||
|
'cotis_user': ['user__pseudo'],
|
||||||
|
'cotis_paiement': ['paiement__moyen'],
|
||||||
|
'cotis_date': ['date'],
|
||||||
|
'cotis_id': ['id'],
|
||||||
|
'default': ['-date']
|
||||||
|
}
|
||||||
|
COTISATIONS_CONTROL = {
|
||||||
|
'control_name': ['user__adherent__name'],
|
||||||
|
'control_surname': ['user__surname'],
|
||||||
|
'control_paiement': ['paiement'],
|
||||||
|
'control_date': ['date'],
|
||||||
|
'control_valid': ['valid'],
|
||||||
|
'control_control': ['control'],
|
||||||
|
'control_id': ['id'],
|
||||||
|
'control_user-id': ['user__id'],
|
||||||
|
'default': ['-date']
|
||||||
|
}
|
||||||
|
TOPOLOGIE_INDEX = {
|
||||||
|
'switch_dns': ['switch_interface__domain__name'],
|
||||||
|
'switch_ip': ['switch_interface__ipv4__ipv4'],
|
||||||
|
'switch_loc': ['location'],
|
||||||
|
'switch_ports': ['number'],
|
||||||
|
'switch_stack': ['stack__name'],
|
||||||
|
'default': ['location', 'stack', 'stack_member_id']
|
||||||
|
}
|
||||||
|
TOPOLOGIE_INDEX_PORT = {
|
||||||
|
'port_port': ['port'],
|
||||||
|
'port_room': ['room__name'],
|
||||||
|
'port_interface': ['machine_interface__domain__name'],
|
||||||
|
'port_related': ['related__switch__name'],
|
||||||
|
'port_radius': ['radius'],
|
||||||
|
'port_vlan': ['vlan_force__name'],
|
||||||
|
'default': ['port']
|
||||||
|
}
|
||||||
|
TOPOLOGIE_INDEX_ROOM = {
|
||||||
|
'room_name': ['name'],
|
||||||
|
'default': ['name']
|
||||||
|
}
|
||||||
|
TOPOLOGIE_INDEX_STACK = {
|
||||||
|
'stack_name': ['name'],
|
||||||
|
'stack_id': ['stack_id'],
|
||||||
|
'default': ['stack_id'],
|
||||||
|
}
|
||||||
|
TOPOLOGIE_INDEX_MODEL_SWITCH = {
|
||||||
|
'model_switch_name': ['reference'],
|
||||||
|
'model_switch__contructor' : ['constructor__name'],
|
||||||
|
'default': ['reference'],
|
||||||
|
}
|
||||||
|
TOPOLOGIE_INDEX_CONSTRUCTOR_SWITCH = {
|
||||||
|
'room_name': ['name'],
|
||||||
|
'default': ['name'],
|
||||||
|
}
|
||||||
|
LOGS_INDEX = {
|
||||||
|
'sum_date': ['revision__date_created'],
|
||||||
|
'default': ['-revision__date_created'],
|
||||||
|
}
|
||||||
|
LOGS_STATS_LOGS = {
|
||||||
|
'logs_author': ['user__name'],
|
||||||
|
'logs_date': ['date_created'],
|
||||||
|
'default': ['-date_created']
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sort(request, col, order, values):
|
||||||
|
""" Check if the given values are possible and add .order_by() and
|
||||||
|
a .reverse() as specified according to those values """
|
||||||
|
fields = values.get(col, None)
|
||||||
|
if not fields:
|
||||||
|
fields = values.get('default', [])
|
||||||
|
request = request.order_by(*fields)
|
||||||
|
if values.get(col, None) and order == 'desc':
|
||||||
|
return request.reverse()
|
||||||
|
else:
|
||||||
|
return request
|
||||||
|
|
||||||
|
|
||||||
|
def remove_user_room(room):
|
||||||
|
""" Déménage de force l'ancien locataire de la chambre """
|
||||||
|
try:
|
||||||
|
user = Adherent.objects.get(room=room)
|
||||||
|
except Adherent.DoesNotExist:
|
||||||
|
return
|
||||||
|
user.room = None
|
||||||
|
user.save()
|
|
@ -19,25 +19,28 @@
|
||||||
# You should have received a copy of the GNU General Public License along
|
# 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.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 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 __future__ import unicode_literals
|
||||||
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from django.template.context_processors import csrf
|
from django.template.context_processors import csrf
|
||||||
from django.template import Context, RequestContext, loader
|
|
||||||
from preferences.models import Service
|
from preferences.models import Service
|
||||||
|
|
||||||
|
|
||||||
def form(ctx, template, request):
|
def form(ctx, template, request):
|
||||||
c = ctx
|
"""Form générique, raccourci importé par les fonctions views du site"""
|
||||||
c.update(csrf(request))
|
context = ctx
|
||||||
return render(request, template, c)
|
context.update(csrf(request))
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
i = 0
|
"""Affiche la liste des services sur la page d'accueil de re2o"""
|
||||||
services = [[], [], []]
|
services = [[], [], []]
|
||||||
for indice, serv in enumerate(Service.objects.all()):
|
for indice, serv in enumerate(Service.objects.all()):
|
||||||
services[indice % 3].append(serv)
|
services[indice % 3].append(serv)
|
||||||
|
|
||||||
return form({'services_urls': services}, 're2o/index.html', request)
|
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
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from django.core.wsgi import get_wsgi_application
|
|
||||||
from os.path import dirname
|
|
||||||
import sys
|
import sys
|
||||||
|
from os.path import dirname
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
|
||||||
sys.path.append(dirname(dirname(__file__)))
|
sys.path.append(dirname(dirname(__file__)))
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "re2o.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "re2o.settings")
|
||||||
|
|
|
@ -21,8 +21,8 @@
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
"""The field used in the admin view for the search app"""
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
# Register your models here.
|
# Register your models here.
|
||||||
|
|
|
@ -20,21 +20,72 @@
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
"""The forms used by the search app"""
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db.models import Q
|
from django import forms
|
||||||
from simple_search import BaseSearchForm
|
from django.forms import Form
|
||||||
|
|
||||||
from users.models import User, School
|
CHOICES_USER = (
|
||||||
|
('0', 'Actifs'),
|
||||||
|
('1', 'Désactivés'),
|
||||||
|
('2', 'Archivés'),
|
||||||
|
)
|
||||||
|
|
||||||
class UserSearchForm(BaseSearchForm):
|
CHOICES_AFF = (
|
||||||
class Meta:
|
('0', 'Utilisateurs'),
|
||||||
base_qs = User.objects
|
('1', 'Machines'),
|
||||||
search_fields = ('^name', 'description', 'specifications', '=id')
|
('2', 'Factures'),
|
||||||
|
('3', 'Bannissements'),
|
||||||
|
('4', 'Accès à titre gracieux'),
|
||||||
|
('5', 'Chambres'),
|
||||||
|
('6', 'Ports'),
|
||||||
|
('7', 'Switchs'),
|
||||||
|
)
|
||||||
|
|
||||||
# assumes a fulltext index has been defined on the fields
|
|
||||||
# 'name,description,specifications,id'
|
def initial_choices(c):
|
||||||
fulltext_indexes = (
|
"""Return the choices that should be activated by default for a
|
||||||
('name', 2), # name matches are weighted higher
|
given set of choices"""
|
||||||
('name,description,specifications,id', 1),
|
return [i[0] for i in c]
|
||||||
)
|
|
||||||
|
|
||||||
|
class SearchForm(Form):
|
||||||
|
"""The form for a simple search"""
|
||||||
|
q = forms.CharField(label='Search', max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
class SearchFormPlus(Form):
|
||||||
|
"""The form for an advanced search (with filters)"""
|
||||||
|
q = forms.CharField(
|
||||||
|
label='Search',
|
||||||
|
max_length=100,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
u = forms.MultipleChoiceField(
|
||||||
|
label="Filtre utilisateurs",
|
||||||
|
required=False,
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
choices=CHOICES_USER,
|
||||||
|
initial=initial_choices(CHOICES_USER)
|
||||||
|
)
|
||||||
|
a = forms.MultipleChoiceField(
|
||||||
|
label="Filtre affichage",
|
||||||
|
required=False,
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
choices=CHOICES_AFF,
|
||||||
|
initial=initial_choices(CHOICES_AFF)
|
||||||
|
)
|
||||||
|
s = forms.DateField(
|
||||||
|
required=False,
|
||||||
|
label="Date de début",
|
||||||
|
help_text='DD/MM/YYYY',
|
||||||
|
input_formats=['%d/%m/%Y']
|
||||||
|
)
|
||||||
|
e = forms.DateField(
|
||||||
|
required=False,
|
||||||
|
help_text='DD/MM/YYYY',
|
||||||
|
input_formats=['%d/%m/%Y'],
|
||||||
|
label="Date de fin"
|
||||||
|
)
|
||||||
|
|
|
@ -1,62 +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 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.
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django import forms
|
|
||||||
from django.forms import Form
|
|
||||||
from django.forms import ModelForm
|
|
||||||
|
|
||||||
CHOICES = (
|
|
||||||
('0', 'Actifs'),
|
|
||||||
('1', 'Désactivés'),
|
|
||||||
('2', 'Archivés'),
|
|
||||||
)
|
|
||||||
|
|
||||||
CHOICES2 = (
|
|
||||||
(1, 'Active'),
|
|
||||||
("", 'Désactivée'),
|
|
||||||
)
|
|
||||||
|
|
||||||
CHOICES3 = (
|
|
||||||
('0', 'Utilisateurs'),
|
|
||||||
('1', 'Machines'),
|
|
||||||
('2', 'Factures'),
|
|
||||||
('3', 'Bannissements'),
|
|
||||||
('4', 'Accès à titre gracieux'),
|
|
||||||
('6', 'Switchs'),
|
|
||||||
('5', 'Ports'),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SearchForm(Form):
|
|
||||||
search_field = forms.CharField(label = 'Search', max_length = 100)
|
|
||||||
|
|
||||||
class SearchFormPlus(Form):
|
|
||||||
search_field = forms.CharField(label = 'Search', max_length = 100, required=False)
|
|
||||||
filtre = forms.MultipleChoiceField(label="Filtre utilisateurs", required=False, widget =forms.CheckboxSelectMultiple,choices=CHOICES)
|
|
||||||
connexion = forms.MultipleChoiceField(label="Filtre connexion", required=False, widget =forms.CheckboxSelectMultiple,choices=CHOICES2)
|
|
||||||
affichage = forms.MultipleChoiceField(label="Filtre affichage", required=False, widget =forms.CheckboxSelectMultiple,choices=CHOICES3)
|
|
||||||
date_deb = forms.DateField(required=False, label="Date de début", help_text='DD/MM/YYYY', input_formats=['%d/%m/%Y'])
|
|
||||||
date_fin = forms.DateField(required=False, help_text='DD/MM/YYYY', input_formats=['%d/%m/%Y'], label="Date de fin")
|
|
|
@ -36,30 +36,35 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<h2>Résultats dans les machines : </h2>
|
<h2>Résultats dans les machines : </h2>
|
||||||
{% include "machines/aff_machines.html" with machines_list=machines_list %}
|
{% include "machines/aff_machines.html" with machines_list=machines_list %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if facture_list %}
|
{% if factures_list %}
|
||||||
<h2>Résultats dans les factures : </h2>
|
<h2>Résultats dans les factures : </h2>
|
||||||
{% include "cotisations/aff_cotisations.html" with facture_list=facture_list %}
|
{% include "cotisations/aff_cotisations.html" with facture_list=factures_list %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if white_list %}
|
{% if whitelists_list %}
|
||||||
<h2>Résultats dans les accès à titre gracieux : </h2>
|
<h2>Résultats dans les accès à titre gracieux : </h2>
|
||||||
{% include "users/aff_whitelists.html" with white_list=white_list %}
|
{% include "users/aff_whitelists.html" with white_list=whitelists_list %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if ban_list %}
|
{% if bans_list %}
|
||||||
<h2>Résultats dans les banissements : </h2>
|
<h2>Résultats dans les banissements : </h2>
|
||||||
{% include "users/aff_bans.html" with ban_list=ban_list %}
|
{% include "users/aff_bans.html" with ban_list=bans_list %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if switch_list %}
|
{% if rooms_list %}
|
||||||
<h2>Résultats dans les switchs : </h2>
|
<h2>Résultats dans les chambres : </h2>
|
||||||
{% include "topologie/aff_switch.html" with switch_list=switch_list %}
|
{% include "topologie/aff_chambres.html" with room_list=rooms_list %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if port_list %}
|
{% if switch_ports_list %}
|
||||||
<h2>Résultats dans les ports : </h2>
|
<h2>Résultats dans les ports : </h2>
|
||||||
{% include "topologie/aff_port.html" with port_list=port_list %}
|
{% include "topologie/aff_port.html" with port_list=switch_ports_list %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not ban_list and not interfaces_list and not users_list and not facture_list and not white_list and not port_list and not switch_list%}
|
{% if switches_list %}
|
||||||
|
<h2>Résultats dans les switchs : </h2>
|
||||||
|
{% include "topologie/aff_switch.html" with switch_list=switches_list %}
|
||||||
|
{% endif %}
|
||||||
|
{% if not users_list and not machines_list and not factures_list and not whitelists_list and not bans_list and not rooms_list and not switch_ports_list and not switches_list %}
|
||||||
<h3>Aucun résultat</h3>
|
<h3>Aucun résultat</h3>
|
||||||
|
{% else %}
|
||||||
|
<h6>(Seulement les {{ max_result }} premiers résultats sont affichés dans chaque catégorie)</h6>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h6>(Seulement les {{ max_result }} premiers résultats sont affichés dans chaque catégorie)</h6>
|
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -28,11 +28,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% block title %}Recherche{% endblock %}
|
{% block title %}Recherche{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% bootstrap_form_errors searchform %}
|
{% bootstrap_form_errors search_form %}
|
||||||
|
|
||||||
<form class="form" method="post">
|
<form class="form">
|
||||||
{% csrf_token %}
|
{% bootstrap_field search_form.q %}
|
||||||
{% bootstrap_form searchform %}
|
{% include "buttons/multiple_checkbox_alt.html" with field=search_form.u %}
|
||||||
|
{% include "buttons/multiple_checkbox_alt.html" with field=search_form.a %}
|
||||||
|
{% bootstrap_field search_form.s %}
|
||||||
|
{% bootstrap_field search_form.e %}
|
||||||
{% bootstrap_button "Search" button_type="submit" icon="search" %}
|
{% bootstrap_button "Search" button_type="submit" icon="search" %}
|
||||||
</form>
|
</form>
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
"""The urls used by the search app"""
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
@ -28,5 +30,5 @@ from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', views.search, name='search'),
|
url(r'^$', views.search, name='search'),
|
||||||
url(r'^avance/$', views.searchp, name='searchp'),
|
url(r'^advanced/$', views.searchp, name='searchp'),
|
||||||
]
|
]
|
||||||
|
|
385
search/views.py
385
search/views.py
|
@ -20,115 +20,326 @@
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
# App de recherche pour re2o
|
"""The views for the search app, responsible for finding the matches
|
||||||
# Augustin lemesle, Gabriel Détraz, Goulven Kermarec
|
Augustin lemesle, Gabriel Détraz, Goulven Kermarec, Maël Kervella
|
||||||
# Gplv2
|
Gplv2"""
|
||||||
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.shortcuts import render
|
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 django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from users.models import User, Ban, Whitelist
|
from users.models import User, Ban, Whitelist
|
||||||
from machines.models import Machine, Interface
|
from machines.models import Machine
|
||||||
from topologie.models import Port, Switch
|
from topologie.models import Port, Switch, Room
|
||||||
from cotisations.models import Facture
|
from cotisations.models import Facture
|
||||||
from search.models import SearchForm, SearchFormPlus
|
|
||||||
from preferences.models import GeneralOption
|
from preferences.models import GeneralOption
|
||||||
|
from search.forms import (
|
||||||
|
SearchForm,
|
||||||
|
SearchFormPlus,
|
||||||
|
CHOICES_USER,
|
||||||
|
CHOICES_AFF,
|
||||||
|
initial_choices
|
||||||
|
)
|
||||||
|
from re2o.utils import SortTable
|
||||||
|
|
||||||
def form(ctx, template, request):
|
|
||||||
c = ctx
|
|
||||||
c.update(csrf(request))
|
|
||||||
return render(request, template, c)
|
|
||||||
|
|
||||||
def search_result(search, type, request):
|
def is_int(variable):
|
||||||
date_deb = None
|
""" Check if the variable can be casted to an integer """
|
||||||
date_fin = None
|
|
||||||
states=[]
|
|
||||||
co=[]
|
|
||||||
aff=[]
|
|
||||||
if(type):
|
|
||||||
aff = search.cleaned_data['affichage']
|
|
||||||
co = search.cleaned_data['connexion']
|
|
||||||
states = search.cleaned_data['filtre']
|
|
||||||
date_deb = search.cleaned_data['date_deb']
|
|
||||||
date_fin = search.cleaned_data['date_fin']
|
|
||||||
date_query = Q()
|
|
||||||
if aff==[]:
|
|
||||||
aff = ['0','1','2','3','4','5','6']
|
|
||||||
if date_deb != None:
|
|
||||||
date_query = date_query & Q(date__gte=date_deb)
|
|
||||||
if date_fin != None:
|
|
||||||
date_query = date_query & Q(date__lte=date_fin)
|
|
||||||
search = search.cleaned_data['search_field']
|
|
||||||
query1 = Q()
|
|
||||||
for s in states:
|
|
||||||
query1 = query1 | Q(state = s)
|
|
||||||
|
|
||||||
connexion = []
|
try:
|
||||||
|
int(variable)
|
||||||
recherche = {'users_list': None, 'machines_list' : [], 'facture_list' : None, 'ban_list' : None, 'white_list': None, 'port_list': None, 'switch_list': None}
|
except ValueError:
|
||||||
|
return False
|
||||||
if request.user.has_perms(('cableur',)):
|
|
||||||
query = Q(user__pseudo__icontains = search) | Q(user__name__icontains = search) | Q(user__surname__icontains = search)
|
|
||||||
else:
|
else:
|
||||||
query = (Q(user__pseudo__icontains = search) | Q(user__name__icontains = search) | Q(user__surname__icontains = search)) & Q(user = request.user)
|
return True
|
||||||
|
|
||||||
|
|
||||||
for i in aff:
|
def get_results(query, request, filters={}):
|
||||||
if i == '0':
|
""" Construct the correct filters to match differents fields of some models
|
||||||
query_user_list = Q(room__name__icontains = search) | Q(pseudo__icontains = search) | Q(name__icontains = search) | Q(surname__icontains = search) & query1
|
with the given query according to the given filters.
|
||||||
if request.user.has_perms(('cableur',)):
|
The match field are either CharField or IntegerField that will be displayed
|
||||||
recherche['users_list'] = User.objects.filter(query_user_list).order_by('state', 'surname').distinct()
|
on the results page (else, one might not see why a result has matched the
|
||||||
else :
|
query). IntegerField are matched against the query only if it can be casted
|
||||||
recherche['users_list'] = User.objects.filter(query_user_list & Q(id=request.user.id)).order_by('state', 'surname').distinct()
|
to an int."""
|
||||||
if i == '1':
|
|
||||||
query_machine_list = Q(machine__user__pseudo__icontains = search) | Q(machine__user__name__icontains = search) | Q(machine__user__surname__icontains = search) | Q(mac_address__icontains = search) | Q(ipv4__ipv4__icontains = search) | Q(domain__name__icontains = search) | Q(domain__related_domain__name__icontains = search)
|
|
||||||
if request.user.has_perms(('cableur',)):
|
|
||||||
data = Interface.objects.filter(query_machine_list).distinct()
|
|
||||||
else:
|
|
||||||
data = Interface.objects.filter(query_machine_list & Q(machine__user__id = request.user.id)).distinct()
|
|
||||||
for d in data:
|
|
||||||
recherche['machines_list'].append(d.machine)
|
|
||||||
if i == '2':
|
|
||||||
recherche['facture_list'] = Facture.objects.filter(query & date_query).distinct()
|
|
||||||
if i == '3':
|
|
||||||
recherche['ban_list'] = Ban.objects.filter(query).distinct()
|
|
||||||
if i == '4':
|
|
||||||
recherche['white_list'] = Whitelist.objects.filter(query).distinct()
|
|
||||||
if i == '5':
|
|
||||||
recherche['port_list'] = Port.objects.filter(details__icontains = search).distinct()
|
|
||||||
if not request.user.has_perms(('cableur',)):
|
|
||||||
recherche['port_list'] = None
|
|
||||||
if i == '6':
|
|
||||||
recherche['switch_list'] = Switch.objects.filter(details__icontains = search).distinct()
|
|
||||||
if not request.user.has_perms(('cableur',)):
|
|
||||||
recherche['switch_list'] = None
|
|
||||||
options, created = GeneralOption.objects.get_or_create()
|
|
||||||
search_display_page = options.search_display_page
|
|
||||||
|
|
||||||
for r in recherche:
|
start = filters.get('s', None)
|
||||||
if recherche[r] != None:
|
end = filters.get('e', None)
|
||||||
recherche[r] = recherche[r][:search_display_page]
|
user_state = filters.get('u', initial_choices(CHOICES_USER))
|
||||||
|
aff = filters.get('a', initial_choices(CHOICES_AFF))
|
||||||
|
|
||||||
recherche.update({'max_result': search_display_page})
|
options, _ = GeneralOption.objects.get_or_create()
|
||||||
|
max_result = options.search_display_page
|
||||||
|
|
||||||
|
results = {
|
||||||
|
'users_list': User.objects.none(),
|
||||||
|
'machines_list': Machine.objects.none(),
|
||||||
|
'factures_list': Facture.objects.none(),
|
||||||
|
'bans_list': Ban.objects.none(),
|
||||||
|
'whitelists_list': Whitelist.objects.none(),
|
||||||
|
'rooms_list': Room.objects.none(),
|
||||||
|
'switch_ports_list': Port.objects.none(),
|
||||||
|
'switches_list': Switch.objects.none()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Users
|
||||||
|
if '0' in aff:
|
||||||
|
filter_user_list = (
|
||||||
|
Q(
|
||||||
|
surname__icontains=query
|
||||||
|
) | Q(
|
||||||
|
adherent__name__icontains=query
|
||||||
|
) | Q(
|
||||||
|
pseudo__icontains=query
|
||||||
|
) | Q(
|
||||||
|
club__room__name__icontains=query
|
||||||
|
) | Q(
|
||||||
|
adherent__room__name__icontains=query
|
||||||
|
)
|
||||||
|
) & Q(state__in=user_state)
|
||||||
|
if not request.user.has_perms(('cableur',)):
|
||||||
|
filter_user_list &= Q(id=request.user.id)
|
||||||
|
results['users_list'] = User.objects.filter(filter_user_list)
|
||||||
|
results['users_list'] = SortTable.sort(
|
||||||
|
results['users_list'],
|
||||||
|
request.GET.get('col'),
|
||||||
|
request.GET.get('order'),
|
||||||
|
SortTable.USERS_INDEX
|
||||||
|
)
|
||||||
|
|
||||||
|
# Machines
|
||||||
|
if '1' in aff:
|
||||||
|
filter_machine_list = Q(
|
||||||
|
name__icontains=query
|
||||||
|
) | (
|
||||||
|
Q(
|
||||||
|
user__pseudo__icontains=query
|
||||||
|
) & Q(
|
||||||
|
user__state__in=user_state
|
||||||
|
)
|
||||||
|
) | Q(
|
||||||
|
interface__domain__name__icontains=query
|
||||||
|
) | Q(
|
||||||
|
interface__domain__related_domain__name__icontains=query
|
||||||
|
) | Q(
|
||||||
|
interface__mac_address__icontains=query
|
||||||
|
) | Q(
|
||||||
|
interface__ipv4__ipv4__icontains=query
|
||||||
|
)
|
||||||
|
if not request.user.has_perms(('cableur',)):
|
||||||
|
filter_machine_list &= Q(user__id=request.user.id)
|
||||||
|
results['machines_list'] = Machine.objects.filter(filter_machine_list)
|
||||||
|
results['machines_list'] = SortTable.sort(
|
||||||
|
results['machines_list'],
|
||||||
|
request.GET.get('col'),
|
||||||
|
request.GET.get('order'),
|
||||||
|
SortTable.MACHINES_INDEX
|
||||||
|
)
|
||||||
|
|
||||||
|
# Factures
|
||||||
|
if '2' in aff:
|
||||||
|
filter_facture_list = Q(
|
||||||
|
user__pseudo__icontains=query
|
||||||
|
) & Q(
|
||||||
|
user__state__in=user_state
|
||||||
|
)
|
||||||
|
if start is not None:
|
||||||
|
filter_facture_list &= Q(date__gte=start)
|
||||||
|
if end is not None:
|
||||||
|
filter_facture_list &= Q(date__lte=end)
|
||||||
|
results['factures_list'] = Facture.objects.filter(filter_facture_list)
|
||||||
|
results['factures_list'] = SortTable.sort(
|
||||||
|
results['factures_list'],
|
||||||
|
request.GET.get('col'),
|
||||||
|
request.GET.get('order'),
|
||||||
|
SortTable.COTISATIONS_INDEX
|
||||||
|
)
|
||||||
|
|
||||||
|
# Bans
|
||||||
|
if '3' in aff:
|
||||||
|
date_filter = (
|
||||||
|
Q(
|
||||||
|
user__pseudo__icontains=query
|
||||||
|
) & Q(
|
||||||
|
user__state__in=user_state
|
||||||
|
)
|
||||||
|
) | Q(
|
||||||
|
raison__icontains=query
|
||||||
|
)
|
||||||
|
if start is not None:
|
||||||
|
date_filter &= (
|
||||||
|
Q(date_start__gte=start) & Q(date_end__gte=start)
|
||||||
|
) | (
|
||||||
|
Q(date_start__lte=start) & Q(date_end__gte=start)
|
||||||
|
) | (
|
||||||
|
Q(date_start__gte=start) & Q(date_end__lte=start)
|
||||||
|
)
|
||||||
|
if end is not None:
|
||||||
|
date_filter &= (
|
||||||
|
Q(date_start__lte=end) & Q(date_end__lte=end)
|
||||||
|
) | (
|
||||||
|
Q(date_start__lte=end) & Q(date_end__gte=end)
|
||||||
|
) | (
|
||||||
|
Q(date_start__gte=end) & Q(date_end__lte=end)
|
||||||
|
)
|
||||||
|
results['bans_list'] = Ban.objects.filter(date_filter)
|
||||||
|
results['bans_list'] = SortTable.sort(
|
||||||
|
results['bans_list'],
|
||||||
|
request.GET.get('col'),
|
||||||
|
request.GET.get('order'),
|
||||||
|
SortTable.USERS_INDEX_BAN
|
||||||
|
)
|
||||||
|
|
||||||
|
# Whitelists
|
||||||
|
if '4' in aff:
|
||||||
|
date_filter = (
|
||||||
|
Q(
|
||||||
|
user__pseudo__icontains=query
|
||||||
|
) & Q(
|
||||||
|
user__state__in=user_state
|
||||||
|
)
|
||||||
|
) | Q(
|
||||||
|
raison__icontains=query
|
||||||
|
)
|
||||||
|
if start is not None:
|
||||||
|
date_filter &= (
|
||||||
|
Q(date_start__gte=start) & Q(date_end__gte=start)
|
||||||
|
) | (
|
||||||
|
Q(date_start__lte=start) & Q(date_end__gte=start)
|
||||||
|
) | (
|
||||||
|
Q(date_start__gte=start) & Q(date_end__lte=start)
|
||||||
|
)
|
||||||
|
if end is not None:
|
||||||
|
date_filter &= (
|
||||||
|
Q(date_start__lte=end) & Q(date_end__lte=end)
|
||||||
|
) | (
|
||||||
|
Q(date_start__lte=end) & Q(date_end__gte=end)
|
||||||
|
) | (
|
||||||
|
Q(date_start__gte=end) & Q(date_end__lte=end)
|
||||||
|
)
|
||||||
|
results['whitelists_list'] = Whitelist.objects.filter(date_filter)
|
||||||
|
results['whitelists_list'] = SortTable.sort(
|
||||||
|
results['whitelists_list'],
|
||||||
|
request.GET.get('col'),
|
||||||
|
request.GET.get('order'),
|
||||||
|
SortTable.USERS_INDEX_WHITE
|
||||||
|
)
|
||||||
|
|
||||||
|
# Rooms
|
||||||
|
if '5' in aff and request.user.has_perms(('cableur',)):
|
||||||
|
filter_rooms_list = Q(
|
||||||
|
details__icontains=query
|
||||||
|
) | Q(
|
||||||
|
name__icontains=query
|
||||||
|
) | Q(
|
||||||
|
port__details=query
|
||||||
|
)
|
||||||
|
results['rooms_list'] = Room.objects.filter(filter_rooms_list)
|
||||||
|
results['rooms_list'] = SortTable.sort(
|
||||||
|
results['rooms_list'],
|
||||||
|
request.GET.get('col'),
|
||||||
|
request.GET.get('order'),
|
||||||
|
SortTable.TOPOLOGIE_INDEX_ROOM
|
||||||
|
)
|
||||||
|
|
||||||
|
# Switch ports
|
||||||
|
if '6' in aff and request.user.has_perms(('cableur',)):
|
||||||
|
filter_ports_list = Q(
|
||||||
|
room__name__icontains=query
|
||||||
|
) | Q(
|
||||||
|
machine_interface__domain__name__icontains=query
|
||||||
|
) | Q(
|
||||||
|
related__switch__switch_interface__domain__name__icontains=query
|
||||||
|
) | Q(
|
||||||
|
radius__icontains=query
|
||||||
|
) | Q(
|
||||||
|
vlan_force__name__icontains=query
|
||||||
|
) | Q(
|
||||||
|
details__icontains=query
|
||||||
|
)
|
||||||
|
if is_int(query):
|
||||||
|
filter_ports_list |= Q(
|
||||||
|
port=query
|
||||||
|
)
|
||||||
|
results['switch_ports_list'] = Port.objects.filter(filter_ports_list)
|
||||||
|
results['switch_ports_list'] = SortTable.sort(
|
||||||
|
results['switch_ports_list'],
|
||||||
|
request.GET.get('col'),
|
||||||
|
request.GET.get('order'),
|
||||||
|
SortTable.TOPOLOGIE_INDEX_PORT
|
||||||
|
)
|
||||||
|
|
||||||
|
# Switches
|
||||||
|
if '7' in aff and request.user.has_perms(('cableur',)):
|
||||||
|
filter_switches_list = Q(
|
||||||
|
switch_interface__domain__name__icontains=query
|
||||||
|
) | Q(
|
||||||
|
switch_interface__ipv4__ipv4__icontains=query
|
||||||
|
) | Q(
|
||||||
|
location__icontains=query
|
||||||
|
) | Q(
|
||||||
|
stack__name__icontains=query
|
||||||
|
) | Q(
|
||||||
|
model__reference__icontains=query
|
||||||
|
) | Q(
|
||||||
|
model__constructor__name__icontains=query
|
||||||
|
) | Q(
|
||||||
|
details__icontains=query
|
||||||
|
)
|
||||||
|
if is_int(query):
|
||||||
|
filter_switches_list |= Q(
|
||||||
|
number=query
|
||||||
|
) | Q(
|
||||||
|
stack_member_id=query
|
||||||
|
)
|
||||||
|
results['switches_list'] = Switch.objects.filter(filter_switches_list)
|
||||||
|
results['switches_list'] = SortTable.sort(
|
||||||
|
results['switches_list'],
|
||||||
|
request.GET.get('col'),
|
||||||
|
request.GET.get('order'),
|
||||||
|
SortTable.TOPOLOGIE_INDEX
|
||||||
|
)
|
||||||
|
|
||||||
|
for name, val in results.items():
|
||||||
|
results[name] = val.distinct()[:max_result]
|
||||||
|
|
||||||
|
results.update({'max_result': max_result})
|
||||||
|
results.update({'search_term': query})
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
return recherche
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def search(request):
|
def search(request):
|
||||||
search = SearchForm(request.POST or None)
|
""" La page de recherche standard """
|
||||||
if search.is_valid():
|
search_form = SearchForm(request.GET or None)
|
||||||
return form(search_result(search, False, request), 'search/index.html',request)
|
if search_form.is_valid():
|
||||||
return form({'searchform' : search}, 'search/search.html', request)
|
return render(
|
||||||
|
request,
|
||||||
|
'search/index.html',
|
||||||
|
get_results(
|
||||||
|
search_form.cleaned_data.get('q', ''),
|
||||||
|
request,
|
||||||
|
search_form.cleaned_data
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return render(request, 'search/search.html', {'search_form': search_form})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def searchp(request):
|
def searchp(request):
|
||||||
search = SearchFormPlus(request.POST or None)
|
""" La page de recherche avancée """
|
||||||
if search.is_valid():
|
search_form = SearchFormPlus(request.GET or None)
|
||||||
return form(search_result(search, True, request), 'search/index.html',request)
|
if search_form.is_valid():
|
||||||
return form({'searchform' : search}, 'search/search.html', request)
|
return render(
|
||||||
|
request,
|
||||||
|
'search/index.html',
|
||||||
|
get_results(
|
||||||
|
search_form.cleaned_data.get('q', ''),
|
||||||
|
request,
|
||||||
|
search_form.cleaned_data
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return render(request, 'search/search.html', {'search_form': search_form})
|
||||||
|
|
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,16 +33,23 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{# Load CSS and JavaScript #}
|
{# Load CSS and JavaScript #}
|
||||||
{% bootstrap_css %}
|
{% bootstrap_css %}
|
||||||
<link href="/static/css/typeaheadjs.css" rel="stylesheet">
|
<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 %}
|
{% bootstrap_javascript %}
|
||||||
<script src="/static/js/typeahead.js"></script>
|
<script src="/static/js/typeahead/typeahead.js"></script>
|
||||||
<script src="/static/js/handlebars.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" %}">
|
<link rel="stylesheet" href="{% static "/css/base.css" %}">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{{ site_name }} : {% block title %}Accueil{% endblock %}</title>
|
<title>{{ site_name }} : {% block title %}Accueil{% endblock %}</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
{% include "cookie_banner.html" %}
|
||||||
<div id="wrap">
|
<div id="wrap">
|
||||||
<nav class="navbar navbar-inverse">
|
<nav class="navbar navbar-inverse">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
|
@ -66,10 +73,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
<div class="col-sm-3 col-md-3 navbar-right">
|
<div class="col-sm-3 col-md-3 navbar-right">
|
||||||
<form action="{% url "search:search"%}" method="POST" class="navbar-form" role="search">
|
<form action="{% url "search:search"%}" class="navbar-form" role="search">
|
||||||
{% csrf_token %}
|
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control" placeholder="Search" name="search_field" id="search-term">
|
<input type="text" class="form-control" placeholder="Search" name="q" id="search-term" {% if search_term %}value="{{ search_term }}"{% endif %}>
|
||||||
<div class="input-group-btn">
|
<div class="input-group-btn">
|
||||||
<button class="btn btn-default" type="submit"><i class="glyphicon glyphicon-search"></i></button>
|
<button class="btn btn-default" type="submit"><i class="glyphicon glyphicon-search"></i></button>
|
||||||
<a href="{% url "search:searchp" %}" class="btn btn-default" role="button"><i class="glyphicon glyphicon-plus"></i></a>
|
<a href="{% url "search:searchp" %}" class="btn btn-default" role="button"><i class="glyphicon glyphicon-plus"></i></a>
|
||||||
|
@ -140,7 +146,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<th scope="row">Connexion</th>
|
<th scope="row">Connexion</th>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{% if request_user.has_access %}
|
{% if request_user.has_access %}
|
||||||
<font color="green">Active</font>
|
<font color="green">jusqu'au {{ request.user.end_access|date:"d b Y" }}</font>
|
||||||
{% else %}
|
{% else %}
|
||||||
<font color="red">Désactivée</font>
|
<font color="red">Désactivée</font>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -149,8 +155,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Adhésion</th>
|
<th scope="row">Adhésion</th>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{% if request_user.end_adhesion != None %}
|
{% if request_user.is_adherent %}
|
||||||
<font color="green">{{ request_user.end_adhesion }}</font>
|
<font color="green">jusqu'au {{ request_user.end_adhesion|date:"d b Y" }}</font>
|
||||||
{% else %}
|
{% else %}
|
||||||
<font color="red">Non adhérent</font>
|
<font color="red">Non adhérent</font>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
40
templates/buttons/multiple_checkbox_alt.html
Normal file
40
templates/buttons/multiple_checkbox_alt.html
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
{% comment %}
|
||||||
|
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
||||||
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
quelques clics.
|
||||||
|
|
||||||
|
Copyright © 2017 Gabriel Détraz
|
||||||
|
Copyright © 2017 Goulven Kermarec
|
||||||
|
Copyright © 2017 Augustin Lemesle
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
<div class="form-group {% if field.form.errors %}{% if field.errors %}has-error{% else %}has-success{% endif %}{% endif %}">
|
||||||
|
<label class="control-label" for="{{ field.id_for_label }}">
|
||||||
|
{{ field.label }}
|
||||||
|
</label>
|
||||||
|
<div id="{{ field.auto_id }}" data-toggle="buttons">
|
||||||
|
{% for val in field.field.choices %}
|
||||||
|
<label for="id_u_{{ val.0 }}" class="btn btn-default{% if val.0 in field.initial %} active{% endif %}">
|
||||||
|
<input {% if val.0 in field.initial %}checked="checked" {% endif %}class="" id="id_u_{{ val.0 }}" name="{{ field.name }}" title="" type="checkbox" value="{{ val.0 }}" /> {{ val.1 }}
|
||||||
|
</label>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<div class="help-block">{{ error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
<div class="help-block">{{ field.help_text }}</div>
|
||||||
|
</div>
|
50
templates/buttons/sort.html
Normal file
50
templates/buttons/sort.html
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
{% comment %}
|
||||||
|
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
||||||
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
quelques clics.
|
||||||
|
|
||||||
|
Copyright © 2017 Gabriel Détraz
|
||||||
|
Copyright © 2017 Goulven Kermarec
|
||||||
|
Copyright © 2017 Augustin Lemesle
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% load url_insert_param %}
|
||||||
|
|
||||||
|
{% spaceless %}
|
||||||
|
<div style="display: flex; padding: 0;">
|
||||||
|
{{ text }}
|
||||||
|
<div style="display: grid; font-size: 9px; line-height: 1; margin: auto 0;">
|
||||||
|
{% if prefix %}
|
||||||
|
{% with prefix|add:'_'|add:col as colname %}
|
||||||
|
<a role="button" href="{% url_insert_param request.get_full_path col=colname order='asc' %}" title="{{ desc|default:"Tri croissant" }}">
|
||||||
|
<span class="glyphicon glyphicon-triangle-top"></span>
|
||||||
|
</a>
|
||||||
|
<a role="button" href="{% url_insert_param request.get_full_path col=colname order='desc' %}" title="{{ desc|default:"Tri décroissant" }}">
|
||||||
|
<span class="glyphicon glyphicon-triangle-bottom"></span>
|
||||||
|
</a>
|
||||||
|
{% endwith %}
|
||||||
|
{% else %}
|
||||||
|
<a role="button" href="{% url_insert_param request.get_full_path col=col order='asc' %}" title="{{ desc|default:"Tri croissant" }}">
|
||||||
|
<span class="glyphicon glyphicon-triangle-top"></span>
|
||||||
|
</a>
|
||||||
|
<a role="button" href="{% url_insert_param request.get_full_path col=col order='desc' %}" title="{{ desc|default:"Tri décroissant" }}">
|
||||||
|
<span class="glyphicon glyphicon-triangle-bottom"></span>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endspaceless %}
|
19
templates/cookie_banner.html
Normal file
19
templates/cookie_banner.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{% if not 'accept_cookies' in request.COOKIES%}
|
||||||
|
<script>
|
||||||
|
function accept_cookie() {
|
||||||
|
var d = new Date();
|
||||||
|
var expiration_time = 7 * 24 * 60 * 60 * 1000; // Accepte les cookies pendant 7 jours.
|
||||||
|
d.setTime(d.getTime() + expiration_time);
|
||||||
|
var expires = "expires="+ d.toUTCString();
|
||||||
|
document.cookie = "accept_cookies=1;" + expires + ";path=/";
|
||||||
|
var banner = document.getElementById("cookie_banner");
|
||||||
|
banner.parentNode.removeChild(banner);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div class="navbar text-center" id="cookie_banner">
|
||||||
|
<p>Ce site utilise des cookies. En poursuivant sur ce site j'accepte l'utilisation des cookies sur ce site.</p>
|
||||||
|
<a class="btn btn-primary btn-sm" role="button" onclick="accept_cookie();" title="Accepter">
|
||||||
|
J'ai compris !
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
|
@ -22,20 +22,22 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% load url_insert_param %}
|
||||||
|
|
||||||
<ul class="pagination nav navbar-nav">
|
<ul class="pagination nav navbar-nav">
|
||||||
{% if list.has_previous %}
|
{% if list.has_previous %}
|
||||||
<li><a href="?page=1"> << </a></li>
|
<li><a href="{% url_insert_param request.get_full_path page=1 %}"> << </a></li>
|
||||||
<li><a href="?page={{ list.previous_page_number }}"> < </a></li>
|
<li><a href="{% url_insert_param request.get_full_path page=list.previous_page_number %}"> < </a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for page in list.paginator.page_range %}
|
{% for page in list.paginator.page_range %}
|
||||||
{% if list.number <= page|add:"3" and list.number >= page|add:"-3" %}
|
{% if list.number <= page|add:"3" and list.number >= page|add:"-3" %}
|
||||||
<li class="{% if list.number == page %}active{% endif %}"><a href="?page={{page }}">{{ page }}</a></li>
|
<li class="{% if list.number == page %}active{% endif %}"><a href="{% url_insert_param request.get_full_path page=page %}">{{ page }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if list.has_next %}
|
{% if list.has_next %}
|
||||||
<li><a href="?page={{ list.next_page_number }}"> > </a></li>
|
<li><a href="{% url_insert_param request.get_full_path page=list.next_page_number %}"> > </a></li>
|
||||||
<li><a href="?page={{ list.paginator.page_range|length }}"> >> </a></li>
|
<li><a href="{% url_insert_param request.get_full_path page=list.paginator.page_range|length %}"> >> </a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
|
@ -20,27 +20,51 @@
|
||||||
# You should have received a copy of the GNU General Public License along
|
# 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.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 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
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from reversion.admin import VersionAdmin
|
from reversion.admin import VersionAdmin
|
||||||
|
|
||||||
from .models import Port, Room, Switch, Stack
|
from .models import Port, Room, Switch, Stack, ModelSwitch, ConstructorSwitch
|
||||||
|
|
||||||
|
|
||||||
class StackAdmin(VersionAdmin):
|
class StackAdmin(VersionAdmin):
|
||||||
|
"""Administration d'une stack de switches (inclus des switches)"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SwitchAdmin(VersionAdmin):
|
class SwitchAdmin(VersionAdmin):
|
||||||
|
"""Administration d'un switch"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PortAdmin(VersionAdmin):
|
class PortAdmin(VersionAdmin):
|
||||||
|
"""Administration d'un port de switches"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RoomAdmin(VersionAdmin):
|
class RoomAdmin(VersionAdmin):
|
||||||
|
"""Administration d'un chambre"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ModelSwitchAdmin(VersionAdmin):
|
||||||
|
"""Administration d'un modèle de switch"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ConstructorSwitchAdmin(VersionAdmin):
|
||||||
|
"""Administration d'un constructeur d'un switch"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Port, PortAdmin)
|
admin.site.register(Port, PortAdmin)
|
||||||
admin.site.register(Room, RoomAdmin)
|
admin.site.register(Room, RoomAdmin)
|
||||||
admin.site.register(Switch, SwitchAdmin)
|
admin.site.register(Switch, SwitchAdmin)
|
||||||
admin.site.register(Stack, StackAdmin)
|
admin.site.register(Stack, StackAdmin)
|
||||||
|
admin.site.register(ModelSwitch, ModelSwitchAdmin)
|
||||||
|
admin.site.register(ConstructorSwitch, ConstructorSwitchAdmin)
|
||||||
|
|
|
@ -19,52 +19,150 @@
|
||||||
# You should have received a copy of the GNU General Public License along
|
# 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.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 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 __future__ import unicode_literals
|
||||||
|
|
||||||
from .models import Port, Switch, Room, Stack
|
|
||||||
from django.forms import ModelForm, Form
|
|
||||||
from machines.models import Interface
|
from machines.models import Interface
|
||||||
|
from django import forms
|
||||||
|
from django.forms import ModelForm
|
||||||
|
from .models import Port, Switch, Room, Stack, ModelSwitch, ConstructorSwitch
|
||||||
|
|
||||||
|
|
||||||
class PortForm(ModelForm):
|
class PortForm(ModelForm):
|
||||||
|
"""Formulaire pour la création d'un port d'un switch
|
||||||
|
Relié directement au modèle port"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Port
|
model = Port
|
||||||
fields = '__all__'
|
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):
|
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):
|
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):
|
def __init__(self, *args, **kwargs):
|
||||||
super(EditPortForm, self).__init__(*args, **kwargs)
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
self.fields['machine_interface'].queryset = Interface.objects.all().select_related('domain__extension')
|
super(EditPortForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
self.fields['related'].queryset = Port.objects.all().select_related('switch__switch_interface__domain__extension').order_by('switch', 'port')
|
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):
|
class AddPortForm(ModelForm):
|
||||||
|
"""Permet d'ajouter un port de switch. Voir EditPortForm pour plus
|
||||||
|
d'informations"""
|
||||||
class Meta(PortForm.Meta):
|
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)
|
||||||
|
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 StackForm(ModelForm):
|
class StackForm(ModelForm):
|
||||||
|
"""Permet d'edition d'une stack : stack_id, et switches membres
|
||||||
|
de la stack"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Stack
|
model = Stack
|
||||||
fields = '__all__'
|
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):
|
class EditSwitchForm(ModelForm):
|
||||||
|
"""Permet d'éditer un switch : nom et nombre de ports"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Switch
|
model = Switch
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
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['switch_interface'].queryset = Interface.objects.all()\
|
||||||
|
.select_related('domain__extension')
|
||||||
self.fields['location'].label = 'Localisation'
|
self.fields['location'].label = 'Localisation'
|
||||||
self.fields['number'].label = 'Nombre de ports'
|
self.fields['number'].label = 'Nombre de ports'
|
||||||
|
|
||||||
|
|
||||||
class NewSwitchForm(ModelForm):
|
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):
|
class Meta(EditSwitchForm.Meta):
|
||||||
fields = ['location', 'number', 'details', 'stack', 'stack_member_id']
|
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)
|
||||||
|
self.fields['location'].label = 'Localisation'
|
||||||
|
self.fields['number'].label = 'Nombre de ports'
|
||||||
|
|
||||||
class EditRoomForm(ModelForm):
|
class EditRoomForm(ModelForm):
|
||||||
|
"""Permet d'éediter le nom et commentaire d'une prise murale"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Room
|
model = Room
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
|
super(EditRoomForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePortsForm(forms.Form):
|
||||||
|
"""Permet de créer une liste de ports pour un switch."""
|
||||||
|
begin = forms.IntegerField(label="Début :", min_value=0)
|
||||||
|
end = forms.IntegerField(label="Fin :", min_value=0)
|
||||||
|
|
||||||
|
|
||||||
|
class EditModelSwitchForm(ModelForm):
|
||||||
|
"""Permet d'éediter un modèle de switch : nom et constructeur"""
|
||||||
|
class Meta:
|
||||||
|
model = ModelSwitch
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
|
super(EditModelSwitchForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class EditConstructorSwitchForm(ModelForm):
|
||||||
|
"""Permet d'éediter le nom d'un constructeur"""
|
||||||
|
class Meta:
|
||||||
|
model = ConstructorSwitch
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
|
super(EditConstructorSwitchForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
|
|
40
topologie/migrations/0031_auto_20171015_2033.py
Normal file
40
topologie/migrations/0031_auto_20171015_2033.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-10-15 18:33
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('topologie', '0030_auto_20171004_0235'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='port',
|
||||||
|
name='port',
|
||||||
|
field=models.PositiveIntegerField(),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='stack',
|
||||||
|
name='member_id_max',
|
||||||
|
field=models.PositiveIntegerField(),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='stack',
|
||||||
|
name='member_id_min',
|
||||||
|
field=models.PositiveIntegerField(),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='switch',
|
||||||
|
name='number',
|
||||||
|
field=models.PositiveIntegerField(),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='switch',
|
||||||
|
name='stack_member_id',
|
||||||
|
field=models.PositiveIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
36
topologie/migrations/0032_auto_20171026_0338.py
Normal file
36
topologie/migrations/0032_auto_20171026_0338.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-10-26 01:38
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('topologie', '0031_auto_20171015_2033'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ConstructorSwitch',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ModelSwitch',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('reference', models.CharField(max_length=255)),
|
||||||
|
('constructor', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='topologie.ConstructorSwitch')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='switch',
|
||||||
|
name='model',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='topologie.ModelSwitch'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -20,32 +20,40 @@
|
||||||
# You should have received a copy of the GNU General Public License along
|
# 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.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 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 __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.signals import post_delete
|
from django.db.models.signals import post_delete
|
||||||
from django.dispatch import receiver
|
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
|
from django.core.exceptions import ValidationError
|
||||||
import reversion
|
|
||||||
|
|
||||||
from machines.models import Vlan
|
|
||||||
|
|
||||||
|
|
||||||
class Stack(models.Model):
|
class Stack(models.Model):
|
||||||
""" Un objet stack. Regrouppe des switchs en foreign key
|
"""Un objet stack. Regrouppe des switchs en foreign key
|
||||||
, contient une id de stack, un switch id min et max dans
|
,contient une id de stack, un switch id min et max dans
|
||||||
le stack"""
|
le stack"""
|
||||||
PRETTY_NAME = "Stack de switchs"
|
PRETTY_NAME = "Stack de switchs"
|
||||||
|
|
||||||
name = models.CharField(max_length=32, blank=True, null=True)
|
name = models.CharField(max_length=32, blank=True, null=True)
|
||||||
stack_id = models.CharField(max_length=32, unique=True)
|
stack_id = models.CharField(max_length=32, unique=True)
|
||||||
details = models.CharField(max_length=255, blank=True, null=True)
|
details = models.CharField(max_length=255, blank=True, null=True)
|
||||||
member_id_min = models.IntegerField()
|
member_id_min = models.PositiveIntegerField()
|
||||||
member_id_max = models.IntegerField()
|
member_id_max = models.PositiveIntegerField()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return " ".join([self.name, self.stack_id])
|
return " ".join([self.name, self.stack_id])
|
||||||
|
@ -59,7 +67,9 @@ class Stack(models.Model):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
""" Verification que l'id_max < id_min"""
|
""" Verification que l'id_max < id_min"""
|
||||||
if self.member_id_max < self.member_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):
|
class Switch(models.Model):
|
||||||
""" Definition d'un switch. Contient un nombre de ports (number),
|
""" Definition d'un switch. Contient un nombre de ports (number),
|
||||||
|
@ -69,18 +79,35 @@ class Switch(models.Model):
|
||||||
Pourquoi ne pas avoir fait hériter switch de interface ?
|
Pourquoi ne pas avoir fait hériter switch de interface ?
|
||||||
Principalement par méconnaissance de la puissance de cette façon de faire.
|
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
|
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"
|
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)
|
location = models.CharField(max_length=255)
|
||||||
number = models.IntegerField()
|
number = models.PositiveIntegerField()
|
||||||
details = models.CharField(max_length=255, blank=True)
|
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_member_id = models.IntegerField(blank=True, null=True)
|
'topologie.Stack',
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=models.SET_NULL
|
||||||
|
)
|
||||||
|
stack_member_id = models.PositiveIntegerField(blank=True, null=True)
|
||||||
|
model = models.ForeignKey(
|
||||||
|
'topologie.ModelSwitch',
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=models.SET_NULL
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('stack','stack_member_id')
|
unique_together = ('stack', 'stack_member_id')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.location) + ' ' + str(self.switch_interface)
|
return str(self.location) + ' ' + str(self.switch_interface)
|
||||||
|
@ -89,10 +116,38 @@ class Switch(models.Model):
|
||||||
""" Verifie que l'id stack est dans le bon range"""
|
""" Verifie que l'id stack est dans le bon range"""
|
||||||
if self.stack is not None:
|
if self.stack is not None:
|
||||||
if self.stack_member_id 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):
|
if (self.stack_member_id > self.stack.member_id_max) or\
|
||||||
raise ValidationError({'stack_member_id': "L'id de ce switch est en dehors des bornes permises pas la stack"})
|
(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:
|
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 ModelSwitch(models.Model):
|
||||||
|
"""Un modèle (au sens constructeur) de switch"""
|
||||||
|
PRETTY_NAME = "Modèle de switch"
|
||||||
|
reference = models.CharField(max_length=255)
|
||||||
|
constructor = models.ForeignKey(
|
||||||
|
'topologie.ConstructorSwitch',
|
||||||
|
on_delete=models.PROTECT
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.constructor) + ' ' + str(self.reference)
|
||||||
|
|
||||||
|
|
||||||
|
class ConstructorSwitch(models.Model):
|
||||||
|
"""Un constructeur de switch"""
|
||||||
|
PRETTY_NAME = "Constructeur de switch"
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.name)
|
||||||
|
|
||||||
|
|
||||||
class Port(models.Model):
|
class Port(models.Model):
|
||||||
""" Definition d'un port. Relié à un switch(foreign_key),
|
""" Definition d'un port. Relié à un switch(foreign_key),
|
||||||
|
@ -102,7 +157,8 @@ class Port(models.Model):
|
||||||
- un autre port (uplink) (related)
|
- un autre port (uplink) (related)
|
||||||
Champs supplémentaires :
|
Champs supplémentaires :
|
||||||
- RADIUS (mode STRICT : connexion sur port uniquement si machine
|
- 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 COMMON : vérification uniquement du statut de la machine
|
||||||
mode NO : accepte toute demande venant du port et place sur le vlan normal
|
mode NO : accepte toute demande venant du port et place sur le vlan normal
|
||||||
mode BLOQ : rejet de toute authentification
|
mode BLOQ : rejet de toute authentification
|
||||||
|
@ -111,19 +167,43 @@ class Port(models.Model):
|
||||||
RADIUS"""
|
RADIUS"""
|
||||||
PRETTY_NAME = "Port de switch"
|
PRETTY_NAME = "Port de switch"
|
||||||
STATES = (
|
STATES = (
|
||||||
('NO', 'NO'),
|
('NO', 'NO'),
|
||||||
('STRICT', 'STRICT'),
|
('STRICT', 'STRICT'),
|
||||||
('BLOQ', 'BLOQ'),
|
('BLOQ', 'BLOQ'),
|
||||||
('COMMON', 'COMMON'),
|
('COMMON', 'COMMON'),
|
||||||
)
|
)
|
||||||
|
|
||||||
switch = models.ForeignKey('Switch', related_name="ports")
|
switch = models.ForeignKey(
|
||||||
port = models.IntegerField()
|
'Switch',
|
||||||
room = models.ForeignKey('Room', on_delete=models.PROTECT, blank=True, null=True)
|
related_name="ports",
|
||||||
machine_interface = models.ForeignKey('machines.Interface', on_delete=models.SET_NULL, blank=True, null=True)
|
on_delete=models.CASCADE
|
||||||
related = models.OneToOneField('self', null=True, blank=True, related_name='related_port')
|
)
|
||||||
|
port = models.PositiveIntegerField()
|
||||||
|
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')
|
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)
|
details = models.CharField(max_length=255, blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -142,23 +222,28 @@ class Port(models.Model):
|
||||||
related_port.save()
|
related_port.save()
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
""" Verifie que un seul de chambre, interface_parent et related_port est rempli.
|
""" Verifie que un seul de chambre, interface_parent et related_port
|
||||||
Verifie que le related n'est pas le port lui-même....
|
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
|
Verifie que le related n'est pas déjà occupé par une machine ou une
|
||||||
ce n'est pas le cas, applique la relation related
|
chambre. Si ce n'est pas le cas, applique la relation related
|
||||||
Si un port related point vers self, on nettoie la relation
|
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
|
A priori pas d'autre solution que de faire ça à la main. A priori
|
||||||
un bloc transaction, donc pas de problème de cohérence"""
|
tout cela est dans un bloc transaction, donc pas de problème de
|
||||||
|
cohérence"""
|
||||||
if hasattr(self, 'switch'):
|
if hasattr(self, 'switch'):
|
||||||
if self.port > self.switch.number:
|
if self.port > self.switch.number:
|
||||||
raise ValidationError("Ce port ne peut exister, numero trop élevé")
|
raise ValidationError("Ce port ne peut exister,\
|
||||||
if self.room and self.machine_interface or self.room and self.related or self.machine_interface and self.related:
|
numero trop élevé")
|
||||||
raise ValidationError("Chambre, interface et related_port sont mutuellement exclusifs")
|
if self.room and self.machine_interface or self.room and\
|
||||||
if self.related==self:
|
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")
|
raise ValidationError("On ne peut relier un port à lui même")
|
||||||
if self.related and not self.related.related:
|
if self.related and not self.related.related:
|
||||||
if self.related.machine_interface or self.related.room:
|
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:
|
else:
|
||||||
self.make_port_related()
|
self.make_port_related()
|
||||||
elif hasattr(self, 'related_port'):
|
elif hasattr(self, 'related_port'):
|
||||||
|
@ -167,8 +252,9 @@ class Port(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.switch) + " - " + str(self.port)
|
return str(self.switch) + " - " + str(self.port)
|
||||||
|
|
||||||
|
|
||||||
class Room(models.Model):
|
class Room(models.Model):
|
||||||
""" Une chambre/local contenant une prise murale"""
|
"""Une chambre/local contenant une prise murale"""
|
||||||
PRETTY_NAME = "Chambre/ Prise murale"
|
PRETTY_NAME = "Chambre/ Prise murale"
|
||||||
|
|
||||||
name = models.CharField(max_length=255, unique=True)
|
name = models.CharField(max_length=255, unique=True)
|
||||||
|
@ -180,6 +266,8 @@ class Room(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.name)
|
return str(self.name)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Stack)
|
@receiver(post_delete, sender=Stack)
|
||||||
def stack_post_delete(sender, **kwargs):
|
def stack_post_delete(sender, **kwargs):
|
||||||
Switch.objects.filter(stack=None).update(stack_member_id = None)
|
"""Vide les id des switches membres d'une stack supprimée"""
|
||||||
|
Switch.objects.filter(stack=None).update(stack_member_id=None)
|
||||||
|
|
|
@ -29,7 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Chambre</th>
|
<th>{% include "buttons/sort.html" with prefix='room' col='name' text='Chambre' %}</th>
|
||||||
<th>Commentaire</th>
|
<th>Commentaire</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
54
topologie/templates/topologie/aff_constructor_switch.html
Normal file
54
topologie/templates/topologie/aff_constructor_switch.html
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
{% comment %}
|
||||||
|
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
||||||
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
quelques clics.
|
||||||
|
|
||||||
|
Copyright © 2017 Gabriel Détraz
|
||||||
|
Copyright © 2017 Goulven Kermarec
|
||||||
|
Copyright © 2017 Augustin Lemesle
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% if constructor_switch_list.paginator %}
|
||||||
|
{% include "pagination.html" with list=constructor_switch_list %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% include "buttons/sort.html" with prefix='constructor-switch' col='name' text='Constructeur' %}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% for constructor_switch in constructor_switch_list %}
|
||||||
|
<tr>
|
||||||
|
<td>{{constructor_switch}}</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'constructor_switch' constructor_switch.pk %}">
|
||||||
|
<i class="glyphicon glyphicon-time"></i>
|
||||||
|
</a>
|
||||||
|
{% if is_infra %}
|
||||||
|
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-constructor-switch' constructor_switch.id %}">
|
||||||
|
<i class="glyphicon glyphicon-edit"></i>
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-constructor-switch' constructor_switch.id %}">
|
||||||
|
<i class="glyphicon glyphicon-trash"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
56
topologie/templates/topologie/aff_model_switch.html
Normal file
56
topologie/templates/topologie/aff_model_switch.html
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
{% comment %}
|
||||||
|
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
||||||
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
quelques clics.
|
||||||
|
|
||||||
|
Copyright © 2017 Gabriel Détraz
|
||||||
|
Copyright © 2017 Goulven Kermarec
|
||||||
|
Copyright © 2017 Augustin Lemesle
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% if model_switch_list.paginator %}
|
||||||
|
{% include "pagination.html" with list=model_switch_list %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% include "buttons/sort.html" with prefix='model-switch' col='reference' text='Référence' %}</th>
|
||||||
|
<th>{% include "buttons/sort.html" with prefix='model-switch' col='constructor' text='Constructeur' %}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% for model_switch in model_switch_list %}
|
||||||
|
<tr>
|
||||||
|
<td>{{model_switch.reference}}</td>
|
||||||
|
<td>{{model_switch.constructor}}</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'model_switch' model_switch.pk %}">
|
||||||
|
<i class="glyphicon glyphicon-time"></i>
|
||||||
|
</a>
|
||||||
|
{% if is_infra %}
|
||||||
|
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-model-switch' model_switch.id %}">
|
||||||
|
<i class="glyphicon glyphicon-edit"></i>
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-model-switch' model_switch.id %}">
|
||||||
|
<i class="glyphicon glyphicon-trash"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
|
@ -25,12 +25,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Port</th>
|
<th>{% include "buttons/sort.html" with prefix='port' col='port' text='Port' %}</th>
|
||||||
<th>Room</th>
|
<th>{% include "buttons/sort.html" with prefix='port' col='room' text='Room' %}</th>
|
||||||
<th>Interface machine</th>
|
<th>{% include "buttons/sort.html" with prefix='port' col='interface' text='Interface machine' %}</th>
|
||||||
<th>Related</th>
|
<th>{% include "buttons/sort.html" with prefix='port' col='related' text='Related' %}</th>
|
||||||
<th>Radius</th>
|
<th>{% include "buttons/sort.html" with prefix='port' col='radius' text='Radius' %}</th>
|
||||||
<th>Vlan forcé</th>
|
<th>{% include "buttons/sort.html" with prefix='port' col='vlan' text='Vlan forcé' %}</th>
|
||||||
<th>Détails</th>
|
<th>Détails</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -25,37 +25,58 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Stack</th>
|
<th>{% include "buttons/sort.html" with prefix='stack' col='name' text='Stack' %}</th>
|
||||||
<th>ID</th>
|
<th>{% include "buttons/sort.html" with prefix='stack' col='id' text='ID' %}</th>
|
||||||
<th>Details</th>
|
<th>Détails</th>
|
||||||
<th>Membres</th>
|
<th>Membres</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
{% for stack in stack_list %}
|
{% for stack in stack_list %}
|
||||||
{% for switch in stack.switch_set.all %}
|
{% for switch in stack.switch_set.all %}
|
||||||
<tr class="active">
|
<tbody>
|
||||||
{% if forloop.first %}
|
<tr class="active">
|
||||||
<td rowspan="{{ stack.switch_set.all|length }}">{{stack.name}}</td>
|
{% if forloop.first %}
|
||||||
<td rowspan="{{ stack.switch_set.all|length }}">{{stack.stack_id}}</td>
|
<td rowspan="{{ stack.switch_set.all|length }}">{{stack.name}}</td>
|
||||||
<td rowspan="{{ stack.switch_set.all|length }}" >{{stack.details}}</td>
|
<td rowspan="{{ stack.switch_set.all|length }}">{{stack.stack_id}}</td>
|
||||||
{% endif %}
|
<td rowspan="{{ stack.switch_set.all|length }}" >{{stack.details}}</td>
|
||||||
<td><a href="{% url 'topologie:index-port' switch.pk %}">{{switch}}</a></td>
|
{% endif %}
|
||||||
{% if forloop.first %}
|
<td><a href="{% url 'topologie:index-port' switch.pk %}">{{switch}}</a></td>
|
||||||
<td rowspan="{{ stack.switch_set.all|length }}">
|
{% if forloop.first %}
|
||||||
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'stack' stack.pk %}">
|
<td rowspan="{{ stack.switch_set.all|length }}">
|
||||||
<i class="glyphicon glyphicon-time"></i>
|
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'stack' stack.pk %}">
|
||||||
</a>
|
<i class="glyphicon glyphicon-time"></i>
|
||||||
{% if is_infra %}
|
</a>
|
||||||
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-stack' stack.id %}">
|
{% if is_infra %}
|
||||||
<i class="glyphicon glyphicon-edit"></i>
|
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-stack' stack.id %}">
|
||||||
</a>
|
<i class="glyphicon glyphicon-edit"></i>
|
||||||
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-stack' stack.pk %}">
|
</a>
|
||||||
<i class="glyphicon glyphicon-trash"></i>
|
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-stack' stack.pk %}">
|
||||||
</a>
|
<i class="glyphicon glyphicon-trash"></i>
|
||||||
{% endif %}
|
</a>
|
||||||
</td>
|
{% endif %}
|
||||||
{% endif %}
|
</td>
|
||||||
</tr>
|
{% endif %}
|
||||||
{% endfor %}
|
</tr>
|
||||||
{% endfor %}
|
{% 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>
|
</table>
|
||||||
|
|
|
@ -22,15 +22,20 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% if switch_list.paginator %}
|
||||||
|
{% include "pagination.html" with list=switch_list %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Dns</th>
|
<th>{% include "buttons/sort.html" with prefix='switch' col='dns' text='Dns' %}</th>
|
||||||
<th>Ipv4</th>
|
<th>{% include "buttons/sort.html" with prefix='switch' col='ip' text='Ipv4' %}</th>
|
||||||
<th>Localisation</th>
|
<th>{% include "buttons/sort.html" with prefix='switch' col='loc' text='Localisation' %}</th>
|
||||||
<th>Ports</th>
|
<th>{% include "buttons/sort.html" with prefix='switch' col='ports' text='Ports' %}</th>
|
||||||
<th>Stack</th>
|
<th>{% include "buttons/sort.html" with prefix='switch' col='stack' text='Stack' %}</th>
|
||||||
<th>id interne Stack</th>
|
<th>Id stack</th>
|
||||||
|
<th>Modèle</th>
|
||||||
<th>Détails</th>
|
<th>Détails</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -47,11 +52,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<td>{{switch.number}}</td>
|
<td>{{switch.number}}</td>
|
||||||
<td>{{switch.stack.name}}</td>
|
<td>{{switch.stack.name}}</td>
|
||||||
<td>{{switch.stack_member_id}}</td>
|
<td>{{switch.stack_member_id}}</td>
|
||||||
|
<td>{{switch.model}}</td>
|
||||||
<td>{{switch.details}}</td>
|
<td>{{switch.details}}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'switch' switch.pk %}">
|
{% include 'buttons/history.html' with href='topologie:history' name='switch' id=switch.pk%}
|
||||||
<i class="glyphicon glyphicon-time"></i>
|
{% if is_infra %}
|
||||||
</a>
|
{% include 'buttons/edit.html' with href='topologie:edit-switch' id=switch.pk %}
|
||||||
|
{% include 'buttons/suppr.html' with href='machines:del-interface' id=switch.switch_interface.id %}
|
||||||
|
{% include 'buttons/add.html' with href='topologie:create-ports' id=switch.pk desc='Création de ports'%}
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -31,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<h2>Switchs</h2>
|
<h2>Switchs</h2>
|
||||||
{% if is_infra %}
|
{% if is_infra %}
|
||||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:new-switch' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un switch</a>
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:new-switch' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un switch</a>
|
||||||
|
<hr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include "topologie/aff_switch.html" with switch_list=switch_list %}
|
{% include "topologie/aff_switch.html" with switch_list=switch_list %}
|
||||||
<br />
|
<br />
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue