8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-26 22:52:26 +00:00

Merge branch 'master' into 'massive_use_bft_tag'

# Conflicts:
#   topologie/views.py
This commit is contained in:
Gabriel Detraz 2017-10-14 01:47:06 +02:00
commit 210fa28d74
21 changed files with 985 additions and 327 deletions

1
.gitignore vendored
View file

@ -4,3 +4,4 @@ settings_local.py
re2o.png re2o.png
__pycache__/* __pycache__/*
static_files/* static_files/*
static/logo/*

View file

@ -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)

View file

@ -19,16 +19,33 @@
# 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.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):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(NewFactureForm, self).__init__(*args, prefix=prefix, **kwargs) super(NewFactureForm, self).__init__(*args, prefix=prefix, **kwargs)
@ -36,58 +53,91 @@ class NewFactureForm(ModelForm):
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): class SelectArticleForm(Form):
article = forms.ModelChoiceField(queryset=Article.objects.all(), label="Article", required=True) """Selection d'un article lors de la creation d'une facture"""
quantity = forms.IntegerField(label="Quantité", validators=[MinValueValidator(1)], required=True) article = forms.ModelChoiceField(
queryset=Article.objects.all(),
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__'
@ -98,6 +148,7 @@ 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__'
@ -107,10 +158,20 @@ class ArticleForm(ModelForm):
super(ArticleForm, self).__init__(*args, prefix=prefix, **kwargs) 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']
@ -121,10 +182,19 @@ class PaiementForm(ModelForm):
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']
@ -134,5 +204,11 @@ class BanqueForm(ModelForm):
super(BanqueForm, self).__init__(*args, prefix=prefix, **kwargs) 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
)

View file

@ -20,59 +20,111 @@
# 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.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"
facture = models.ForeignKey('Facture', on_delete=models.CASCADE) facture = models.ForeignKey('Facture', on_delete=models.CASCADE)
@ -80,44 +132,67 @@ class Vente(models.Model):
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() iscotisation = models.BooleanField()
duration = models.IntegerField(help_text="Durée exprimée en mois entiers", blank=True, null=True) duration = models.IntegerField(
help_text="Durée exprimée en mois entiers",
blank=True,
null=True)
def prix_total(self): 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
en argument l'user, la facture pour la quantitéi, et l'article pour
la durée"""
if not hasattr(self, 'cotisation'): if not hasattr(self, 'cotisation'):
cotisation=Cotisation(vente=self) cotisation = Cotisation(vente=self)
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_adhesion = Cotisation.objects.filter(
vente__in=Vente.objects.filter(
facture__in=Facture.objects.filter(
user=self.facture.user
).exclude(valid=False))
).filter(
date_start__lt=date_start
).aggregate(Max('date_end'))['date_end__max']
else: else:
end_adhesion = self.facture.user.end_adhesion() end_adhesion = self.facture.user.end_adhesion()
date_start = date_start or timezone.now() date_start = date_start or timezone.now()
end_adhesion = end_adhesion or date_start end_adhesion = end_adhesion or date_start
date_max = max(end_adhesion, date_start) date_max = max(end_adhesion, 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.iscotisation and not self.duration:
raise ValidationError("Cotisation et durée doivent être présents ensembles") raise ValidationError("Cotisation et durée doivent être présents\
ensembles")
self.update_cotisation() 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
@ -128,14 +203,20 @@ def vente_post_save(sender, **kwargs):
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.iscotisation:
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) name = models.CharField(max_length=255, unique=True)
@ -154,7 +235,9 @@ class Article(models.Model):
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 +245,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,11 +264,15 @@ 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"
vente = models.OneToOneField('Vente', on_delete=models.CASCADE, null=True) vente = models.OneToOneField('Vente', on_delete=models.CASCADE, null=True)
@ -193,15 +282,19 @@ class Cotisation(models.Model):
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')

View file

@ -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>facture)/(?P<id>[0-9]+)$',
views.history,
name='history'
),
url(r'^history/(?P<object>article)/(?P<id>[0-9]+)$',
views.history,
name='history'
),
url(r'^history/(?P<object>paiement)/(?P<id>[0-9]+)$',
views.history,
name='history'),
url(r'^history/(?P<object>banque)/(?P<id>[0-9]+)$',
views.history,
name='history'
),
url(r'^control/$',
views.control,
name='control'
),
url(r'^$', views.index, name='index'), url(r'^$', views.index, name='index'),
] ]

View file

@ -24,44 +24,45 @@
# 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.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 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
from .forms import ArticleForm, DelArticleForm, PaiementForm, DelPaiementForm
from .forms import BanqueForm, DelBanqueForm, NewFactureFormPdf
from .forms import SelectArticleForm, CreditSoldeForm
from .tex import render_tex
from dateutil.relativedelta import relativedelta
from django.utils import timezone
def form(ctx, template, request):
c = ctx
c.update(csrf(request))
return render(request, template, c)
@login_required @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
@ -70,50 +71,80 @@ def new_facture(request, userid):
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) article_formset = formset_factory(SelectArticleForm)(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,
iscotisation=article.iscotisation,
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'].iscotisation
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 +152,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 +284,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 +297,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 +307,14 @@ 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'],
iscotisation=False,
duration=0,
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 +327,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 +344,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 +384,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 +400,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 +438,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 +466,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 +506,25 @@ 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.order_by('date').reverse()
controlform_set = modelformset_factory(Facture, fields=('control','valid'), extra=0) controlform_set = modelformset_factory(
Facture,
fields=('control', 'valid'),
extra=0
)
paginator = Paginator(facture_list, pagination_number) paginator = Paginator(facture_list, pagination_number)
page = request.GET.get('page') page = request.GET.get('page')
try: try:
@ -379,7 +533,9 @@ 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]) page_query = Facture.objects.order_by('date').reverse().filter(
id__in=[facture.id for facture in facture_list]
)
controlform = controlform_set(request.POST or None, queryset=page_query) 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():
@ -387,32 +543,50 @@ def control(request):
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.order_by('date').select_related('user')\
.select_related('paiement').prefetch_related('vente_set').reverse()
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 +597,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, object_id):
"""Affiche l'historique de chaque objet"""
if object == 'facture': if object == '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\
d'une facture d'un autre user que vous sans droit cableur")
return redirect("/users/profil/" + str(request.user.id))
elif object == 'paiement' and request.user.has_perms(('cableur',)): elif object == '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 == '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 == '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 +650,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
})

View file

@ -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")

View file

@ -29,9 +29,9 @@ 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 context = ctx
c.update(csrf(request)) context.update(csrf(request))
return render(request, template, c) return render(request, template, context)
def index(request): def index(request):

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

View file

View file

@ -20,6 +20,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.
"""
Fichier définissant les administration des models dans l'interface admin
"""
from __future__ import unicode_literals from __future__ import unicode_literals
@ -28,18 +31,27 @@ from reversion.admin import VersionAdmin
from .models import Port, Room, Switch, Stack from .models import Port, Room, Switch, Stack
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
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)

View file

@ -19,14 +19,27 @@
# 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.forms import ModelForm
from .models import Port, Switch, Room, Stack
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__'
@ -35,25 +48,45 @@ class PortForm(ModelForm):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(PortForm, self).__init__(*args, prefix=prefix, **kwargs) 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):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(EditPortForm, self).__init__(*args, prefix=prefix, **kwargs) super(EditPortForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['machine_interface'].queryset = Interface.objects.all().select_related('domain__extension') self.fields['machine_interface'].queryset = Interface.objects.all()\
self.fields['related'].queryset = Port.objects.all().select_related('switch__switch_interface__domain__extension').order_by('switch', 'port') .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): def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(AddPortForm, self).__init__(*args, prefix=prefix, **kwargs) super(AddPortForm, self).__init__(*args, prefix=prefix, **kwargs)
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__'
@ -62,7 +95,9 @@ class StackForm(ModelForm):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(StackForm, self).__init__(*args, prefix=prefix, **kwargs) 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__'
@ -73,7 +108,10 @@ class EditSwitchForm(ModelForm):
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']
@ -81,7 +119,9 @@ class NewSwitchForm(ModelForm):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(NewSwitchForm, self).__init__(*args, prefix=prefix, **kwargs) super(NewSwitchForm, self).__init__(*args, prefix=prefix, **kwargs)
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__'
@ -89,4 +129,3 @@ class EditRoomForm(ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(EditRoomForm, self).__init__(*args, prefix=prefix, **kwargs) super(EditRoomForm, self).__init__(*args, prefix=prefix, **kwargs)

View file

@ -20,24 +20,32 @@
# 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"
@ -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,29 @@ 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.IntegerField()
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,
blank=True,
null=True,
on_delete=models.SET_NULL
)
stack_member_id = models.IntegerField(blank=True, null=True) stack_member_id = models.IntegerField(blank=True, null=True)
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 +110,16 @@ 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 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 +129,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 +139,39 @@ 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('Switch', related_name="ports")
port = models.IntegerField() port = models.IntegerField()
room = models.ForeignKey('Room', on_delete=models.PROTECT, blank=True, null=True) room = models.ForeignKey(
machine_interface = models.ForeignKey('machines.Interface', on_delete=models.SET_NULL, blank=True, null=True) 'Room',
related = models.OneToOneField('self', null=True, blank=True, related_name='related_port') 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 +190,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 +220,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 +234,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)

View file

@ -19,6 +19,12 @@
# 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 urls de l'application topologie.
Inclu dans urls de re2o.
Fait référence aux fonctions du views
"""
from __future__ import unicode_literals from __future__ import unicode_literals
@ -33,18 +39,33 @@ urlpatterns = [
url(r'^new_room/$', views.new_room, name='new-room'), url(r'^new_room/$', views.new_room, name='new-room'),
url(r'^edit_room/(?P<room_id>[0-9]+)$', views.edit_room, name='edit-room'), url(r'^edit_room/(?P<room_id>[0-9]+)$', views.edit_room, name='edit-room'),
url(r'^del_room/(?P<room_id>[0-9]+)$', views.del_room, name='del-room'), url(r'^del_room/(?P<room_id>[0-9]+)$', views.del_room, name='del-room'),
url(r'^switch/(?P<switch_id>[0-9]+)$', views.index_port, name='index-port'), url(r'^switch/(?P<switch_id>[0-9]+)$',
url(r'^history/(?P<object>switch)/(?P<id>[0-9]+)$', views.history, name='history'), views.index_port,
url(r'^history/(?P<object>port)/(?P<id>[0-9]+)$', views.history, name='history'), name='index-port'),
url(r'^history/(?P<object>room)/(?P<id>[0-9]+)$', views.history, name='history'), url(r'^history/(?P<object>switch)/(?P<id>[0-9]+)$',
url(r'^history/(?P<object>stack)/(?P<id>[0-9]+)$', views.history, name='history'), views.history,
name='history'),
url(r'^history/(?P<object>port)/(?P<id>[0-9]+)$',
views.history,
name='history'),
url(r'^history/(?P<object>room)/(?P<id>[0-9]+)$',
views.history,
name='history'),
url(r'^history/(?P<object>stack)/(?P<id>[0-9]+)$',
views.history,
name='history'),
url(r'^edit_port/(?P<port_id>[0-9]+)$', views.edit_port, name='edit-port'), url(r'^edit_port/(?P<port_id>[0-9]+)$', views.edit_port, name='edit-port'),
url(r'^new_port/(?P<switch_id>[0-9]+)$', views.new_port, name='new-port'), url(r'^new_port/(?P<switch_id>[0-9]+)$', views.new_port, name='new-port'),
url(r'^del_port/(?P<port_id>[0-9]+)$', views.del_port, name='del-port'), url(r'^del_port/(?P<port_id>[0-9]+)$', views.del_port, name='del-port'),
url(r'^edit_switch/(?P<switch_id>[0-9]+)$', views.edit_switch, name='edit-switch'), url(r'^edit_switch/(?P<switch_id>[0-9]+)$',
views.edit_switch,
name='edit-switch'),
url(r'^new_stack/$', views.new_stack, name='new-stack'), url(r'^new_stack/$', views.new_stack, name='new-stack'),
url(r'^index_stack/$', views.index_stack, name='index-stack'), url(r'^index_stack/$', views.index_stack, name='index-stack'),
url(r'^edit_stack/(?P<stack_id>[0-9]+)$', views.edit_stack, name='edit-stack'), url(r'^edit_stack/(?P<stack_id>[0-9]+)$',
url(r'^del_stack/(?P<stack_id>[0-9]+)$', views.del_stack, name='del-stack'), views.edit_stack,
name='edit-stack'),
url(r'^del_stack/(?P<stack_id>[0-9]+)$',
views.del_stack,
name='del-stack'),
] ]

View file

@ -19,7 +19,20 @@
# 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.
"""
Page des vues de l'application topologie
Permet de créer, modifier et supprimer :
- un port (add_port, edit_port, del_port)
- un switch : les vues d'ajout et d'édition font appel aux forms de creation
de switch, mais aussi aux forms de machines.forms (domain, interface et
machine). Le views les envoie et les save en même temps. TODO : rationaliser
et faire que la creation de machines (interfaces, domain etc) soit gérée
coté models et forms de topologie
- une chambre (new_room, edit_room, del_room)
- une stack
- l'historique de tous les objets cités
"""
from __future__ import unicode_literals from __future__ import unicode_literals
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
@ -33,9 +46,9 @@ from reversion import revisions as reversion
from reversion.models import Version from reversion.models import Version
from topologie.models import Switch, Port, Room, Stack from topologie.models import Switch, Port, Room, Stack
from topologie.forms import EditPortForm, NewSwitchForm, EditSwitchForm, AddPortForm, EditRoomForm, StackForm from topologie.forms import EditPortForm, NewSwitchForm, EditSwitchForm
from topologie.forms import AddPortForm, EditRoomForm, StackForm
from users.views import form from users.views import form
from users.models import User
from machines.forms import AliasForm, NewMachineForm, EditMachineForm, EditInterfaceForm, AddInterfaceForm from machines.forms import AliasForm, NewMachineForm, EditMachineForm, EditInterfaceForm, AddInterfaceForm
from machines.views import generate_ipv4_bft_param from machines.views import generate_ipv4_bft_param
@ -46,41 +59,52 @@ from preferences.models import AssoOption, GeneralOption
@permission_required('cableur') @permission_required('cableur')
def index(request): def index(request):
""" Vue d'affichage de tous les swicthes""" """ Vue d'affichage de tous les swicthes"""
switch_list = Switch.objects.order_by('stack','stack_member_id','location').select_related('switch_interface__domain__extension').select_related('switch_interface__ipv4').select_related('switch_interface__domain').select_related('stack') switch_list = Switch.objects.order_by(
return render(request, 'topologie/index.html', {'switch_list': switch_list}) 'stack',
'stack_member_id',
'location'
)\
.select_related('switch_interface__domain__extension')\
.select_related('switch_interface__ipv4')\
.select_related('switch_interface__domain')\
.select_related('stack')
return render(request, 'topologie/index.html', {
'switch_list': switch_list
})
@login_required @login_required
@permission_required('cableur') @permission_required('cableur')
def history(request, object, id): def history(request, object_name, object_id):
""" Vue générique pour afficher l'historique complet d'un objet""" """ Vue générique pour afficher l'historique complet d'un objet"""
if object == 'switch': if object_name == 'switch':
try: try:
object_instance = Switch.objects.get(pk=id) object_instance = Switch.objects.get(pk=object_id)
except Switch.DoesNotExist: except Switch.DoesNotExist:
messages.error(request, "Switch inexistant") messages.error(request, "Switch inexistant")
return redirect("/topologie/") return redirect("/topologie/")
elif object == 'port': elif object_name == 'port':
try: try:
object_instance = Port.objects.get(pk=id) object_instance = Port.objects.get(pk=object_id)
except Port.DoesNotExist: except Port.DoesNotExist:
messages.error(request, "Port inexistant") messages.error(request, "Port inexistant")
return redirect("/topologie/") return redirect("/topologie/")
elif object == 'room': elif object_name == 'room':
try: try:
object_instance = Room.objects.get(pk=id) object_instance = Room.objects.get(pk=object_id)
except Room.DoesNotExist: except Room.DoesNotExist:
messages.error(request, "Chambre inexistante") messages.error(request, "Chambre inexistante")
return redirect("/topologie/") return redirect("/topologie/")
elif object == 'stack': elif object_name == 'stack':
try: try:
object_instance = Stack.objects.get(pk=id) object_instance = Stack.objects.get(pk=object_id)
except Room.DoesNotExist: except Room.DoesNotExist:
messages.error(request, "Stack inexistante") messages.error(request, "Stack inexistante")
return redirect("/topologie/") return redirect("/topologie/")
else: else:
messages.error(request, "Objet inconnu") messages.error(request, "Objet inconnu")
return redirect("/topologie/") return redirect("/topologie/")
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)
@ -93,7 +117,11 @@ 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
})
@login_required @login_required
@permission_required('cableur') @permission_required('cableur')
@ -104,15 +132,25 @@ def index_port(request, switch_id):
except Switch.DoesNotExist: except Switch.DoesNotExist:
messages.error(request, u"Switch inexistant") messages.error(request, u"Switch inexistant")
return redirect("/topologie/") return redirect("/topologie/")
port_list = Port.objects.filter(switch = switch).select_related('room').select_related('machine_interface__domain__extension').select_related('related').select_related('switch').order_by('port') port_list = Port.objects.filter(switch=switch)\
return render(request, 'topologie/index_p.html', {'port_list':port_list, 'id_switch':switch_id, 'nom_switch':switch}) .select_related('room')\
.select_related('machine_interface__domain__extension')\
.select_related('related')\
.select_related('switch')\
.order_by('port')
return render(request, 'topologie/index_p.html', {
'port_list': port_list,
'id_switch': switch_id,
'nom_switch': switch
})
@login_required @login_required
@permission_required('cableur') @permission_required('cableur')
def index_room(request): def index_room(request):
""" Affichage de l'ensemble des chambres""" """ Affichage de l'ensemble des chambres"""
room_list = Room.objects.order_by('name') room_list = Room.objects.order_by('name')
options, created = GeneralOption.objects.get_or_create() options, _created = GeneralOption.objects.get_or_create()
pagination_number = options.pagination_number pagination_number = options.pagination_number
paginator = Paginator(room_list, pagination_number) paginator = Paginator(room_list, pagination_number)
page = request.GET.get('page') page = request.GET.get('page')
@ -124,13 +162,20 @@ def index_room(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.
room_list = paginator.page(paginator.num_pages) room_list = paginator.page(paginator.num_pages)
return render(request, 'topologie/index_room.html', {'room_list': room_list}) return render(request, 'topologie/index_room.html', {
'room_list': room_list
})
@login_required @login_required
@permission_required('infra') @permission_required('infra')
def index_stack(request): def index_stack(request):
stack_list = Stack.objects.order_by('name').prefetch_related('switch_set__switch_interface__domain__extension') """Affichage de la liste des stacks (affiche l'ensemble des switches)"""
return render(request, 'topologie/index_stack.html', {'stack_list': stack_list}) stack_list = Stack.objects.order_by('name')\
.prefetch_related('switch_set__switch_interface__domain__extension')
return render(request, 'topologie/index_stack.html', {
'stack_list': stack_list
})
@login_required @login_required
@ -153,16 +198,24 @@ def new_port(request, switch_id):
reversion.set_comment("Création") reversion.set_comment("Création")
messages.success(request, "Port ajouté") messages.success(request, "Port ajouté")
except IntegrityError: except IntegrityError:
messages.error(request,"Ce port existe déjà" ) messages.error(request, "Ce port existe déjà")
return redirect("/topologie/switch/" + switch_id) return redirect("/topologie/switch/" + switch_id)
return form({'topoform':port}, 'topologie/topo.html', request) return form({'topoform': port}, 'topologie/topo.html', request)
@login_required @login_required
@permission_required('infra') @permission_required('infra')
def edit_port(request, port_id): def edit_port(request, port_id):
""" Edition d'un port. Permet de changer le switch parent et l'affectation du port""" """ Edition d'un port. Permet de changer le switch parent et
l'affectation du port"""
try: try:
port_object = Port.objects.select_related('switch__switch_interface__domain__extension').select_related('machine_interface__domain__extension').select_related('machine_interface__switch').select_related('room').select_related('related').get(pk=port_id) port_object = Port.objects\
.select_related('switch__switch_interface__domain__extension')\
.select_related('machine_interface__domain__extension')\
.select_related('machine_interface__switch')\
.select_related('room')\
.select_related('related')\
.get(pk=port_id)
except Port.DoesNotExist: except Port.DoesNotExist:
messages.error(request, u"Port inexistant") messages.error(request, u"Port inexistant")
return redirect("/topologie/") return redirect("/topologie/")
@ -171,14 +224,17 @@ def edit_port(request, port_id):
with transaction.atomic(), reversion.create_revision(): with transaction.atomic(), reversion.create_revision():
port.save() port.save()
reversion.set_user(request.user) reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in port.changed_data)) reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in port.changed_data
))
messages.success(request, "Le port a bien été modifié") messages.success(request, "Le port a bien été modifié")
return redirect("/topologie/switch/" + str(port_object.switch.id)) return redirect("/topologie/switch/" + str(port_object.switch.id))
return form({'topoform':port}, 'topologie/topo.html', request) return form({'topoform': port}, 'topologie/topo.html', request)
@login_required @login_required
@permission_required('infra') @permission_required('infra')
def del_port(request,port_id): def del_port(request, port_id):
""" Supprime le port""" """ Supprime le port"""
try: try:
port = Port.objects.get(pk=port_id) port = Port.objects.get(pk=port_id)
@ -193,32 +249,30 @@ def del_port(request,port_id):
reversion.set_comment("Destruction") reversion.set_comment("Destruction")
messages.success(request, "Le port a eté détruit") messages.success(request, "Le port a eté détruit")
except ProtectedError: except ProtectedError:
messages.error(request, "Le port %s est affecté à un autre objet, impossible de le supprimer" % port) messages.error(request, "Le port %s est affecté à un autre objet,\
impossible de le supprimer" % port)
return redirect('/topologie/switch/' + str(port.switch.id)) return redirect('/topologie/switch/' + str(port.switch.id))
return form({'objet':port}, 'topologie/delete.html', request) return form({'objet': port}, 'topologie/delete.html', request)
@login_required @login_required
@permission_required('infra') @permission_required('infra')
def new_stack(request): def new_stack(request):
"""Ajoute un nouveau stack : stack_id_min, max, et nombre de switches"""
stack = StackForm(request.POST or None) stack = StackForm(request.POST or None)
#if stack.is_valid(): if stack.is_valid():
if request.POST: with transaction.atomic(), reversion.create_revision():
try: stack.save()
with transaction.atomic(), reversion.create_revision(): reversion.set_user(request.user)
stack.save() reversion.set_comment("Création")
reversion.set_user(request.user) messages.success(request, "Stack crée")
reversion.set_comment("Création") return form({'topoform': stack}, 'topologie/topo.html', request)
messages.success(request, "Stack crée")
except:
messages.error(request, "Cette stack existe déjà")
else:
return redirect('/topologie/index_stack')
return form({'topoform':stack}, 'topologie/topo.html', request)
@login_required @login_required
@permission_required('infra') @permission_required('infra')
def edit_stack(request,stack_id): def edit_stack(request, stack_id):
"""Edition d'un stack (nombre de switches, nom...)"""
try: try:
stack = Stack.objects.get(pk=stack_id) stack = Stack.objects.get(pk=stack_id)
except Stack.DoesNotExist: except Stack.DoesNotExist:
@ -229,13 +283,19 @@ def edit_stack(request,stack_id):
with transaction.atomic(), reversion.create_revision(): with transaction.atomic(), reversion.create_revision():
stack.save() stack.save()
reversion.set_user(request.user) reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in stack.changed_data)) reversion.set_comment(
"Champs modifié(s) : %s" % ', '.join(
field for field in stack.changed_data
)
)
return redirect('/topologie/index_stack') return redirect('/topologie/index_stack')
return form({'topoform':stack}, 'topologie/topo.html', request) return form({'topoform': stack}, 'topologie/topo.html', request)
@login_required @login_required
@permission_required('infra') @permission_required('infra')
def del_stack(request,stack_id): def del_stack(request, stack_id):
"""Supprime un stack"""
try: try:
stack = Stack.objects.get(pk=stack_id) stack = Stack.objects.get(pk=stack_id)
except Stack.DoesNotExist: except Stack.DoesNotExist:
@ -249,13 +309,16 @@ def del_stack(request,stack_id):
reversion.set_comment("Destruction") reversion.set_comment("Destruction")
messages.success(request, "La stack a eté détruite") messages.success(request, "La stack a eté détruite")
except ProtectedError: except ProtectedError:
messages.error(request, "La stack %s est affectée à un autre objet, impossible de la supprimer" % stack) messages.error(request, "La stack %s est affectée à un autre\
objet, impossible de la supprimer" % stack)
return redirect('/topologie/index_stack') return redirect('/topologie/index_stack')
return form({'objet':stack}, 'topologie/delete.html', request) return form({'objet': stack}, 'topologie/delete.html', request)
@login_required @login_required
@permission_required('infra') @permission_required('infra')
def edit_switchs_stack(request,stack_id): def edit_switchs_stack(request, stack_id):
"""Permet d'éditer la liste des switches dans une stack et l'ajouter"""
try: try:
stack = Stack.objects.get(pk=stack_id) stack = Stack.objects.get(pk=stack_id)
except Stack.DoesNotExist: except Stack.DoesNotExist:
@ -267,30 +330,36 @@ def edit_switchs_stack(request,stack_id):
context = {'stack': stack} context = {'stack': stack}
context['switchs_stack'] = stack.switchs_set.all() context['switchs_stack'] = stack.switchs_set.all()
context['switchs_autres'] = Switch.object.filter(stack=None) context['switchs_autres'] = Switch.object.filter(stack=None)
pass
@login_required @login_required
@permission_required('infra') @permission_required('infra')
def new_switch(request): def new_switch(request):
""" Creation d'un switch. Cree en meme temps l'interface et la machine associée. """ Creation d'un switch. Cree en meme temps l'interface et la machine
Vue complexe. Appelle successivement les 4 models forms adaptés : machine, associée. Vue complexe. Appelle successivement les 4 models forms
interface, domain et switch""" adaptés : machine, interface, domain et switch"""
switch = NewSwitchForm(request.POST or None) switch = NewSwitchForm(request.POST or None)
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(
domain = AliasForm(request.POST or None, infra=request.user.has_perms(('infra',))) request.POST or None,
infra=request.user.has_perms(('infra',))
)
domain = AliasForm(
request.POST or None,
infra=request.user.has_perms(('infra',))
)
if switch.is_valid() and machine.is_valid() and interface.is_valid(): if switch.is_valid() and machine.is_valid() and interface.is_valid():
options, created = AssoOption.objects.get_or_create() options, _created = AssoOption.objects.get_or_create()
user = options.utilisateur_asso user = options.utilisateur_asso
if not user: if not user:
messages.error(request, "L'user association n'existe pas encore, veuillez le créer ou le linker dans preferences") messages.error(request, "L'user association n'existe pas encore,\
veuillez le créer ou le linker dans preferences")
return redirect("/topologie/") return redirect("/topologie/")
new_machine = machine.save(commit=False) new_machine = machine.save(commit=False)
new_machine.user = user new_machine.user = user
new_interface = interface.save(commit=False) new_interface = interface.save(commit=False)
new_switch = switch.save(commit=False) new_switch_instance = switch.save(commit=False)
new_domain = domain.save(commit=False) new_domain_instance = domain.save(commit=False)
with transaction.atomic(), reversion.create_revision(): with transaction.atomic(), reversion.create_revision():
new_machine.save() new_machine.save()
reversion.set_user(request.user) reversion.set_user(request.user)
@ -300,14 +369,14 @@ def new_switch(request):
new_interface.save() new_interface.save()
reversion.set_user(request.user) reversion.set_user(request.user)
reversion.set_comment("Création") reversion.set_comment("Création")
new_domain.interface_parent = new_interface new_domain_instance.interface_parent = new_interface
with transaction.atomic(), reversion.create_revision(): with transaction.atomic(), reversion.create_revision():
new_domain.save() new_domain_instance.save()
reversion.set_user(request.user) reversion.set_user(request.user)
reversion.set_comment("Création") reversion.set_comment("Création")
new_switch.switch_interface = new_interface new_switch_instance.switch_interface = new_interface
with transaction.atomic(), reversion.create_revision(): with transaction.atomic(), reversion.create_revision():
new_switch.save() new_switch_instance.save()
reversion.set_user(request.user) reversion.set_user(request.user)
reversion.set_comment("Création") reversion.set_comment("Création")
messages.success(request, "Le switch a été créé") messages.success(request, "Le switch a été créé")
@ -318,38 +387,59 @@ def new_switch(request):
@login_required @login_required
@permission_required('infra') @permission_required('infra')
def edit_switch(request, switch_id): def edit_switch(request, switch_id):
""" Edition d'un switch. Permet de chambre nombre de ports, place dans le stack, """ Edition d'un switch. Permet de chambre nombre de ports,
interface et machine associée""" place dans le stack, interface et machine associée"""
try: try:
switch = Switch.objects.get(pk=switch_id) switch = Switch.objects.get(pk=switch_id)
except Switch.DoesNotExist: except Switch.DoesNotExist:
messages.error(request, u"Switch inexistant") messages.error(request, u"Switch inexistant")
return redirect("/topologie/") return redirect("/topologie/")
switch_form = EditSwitchForm(request.POST or None, instance=switch) switch_form = EditSwitchForm(request.POST or None, instance=switch)
machine_form = EditMachineForm(request.POST or None, instance=switch.switch_interface.machine) machine_form = EditMachineForm(
interface_form = EditInterfaceForm(request.POST or None, instance=switch.switch_interface) request.POST or None,
domain_form = AliasForm(request.POST or None, infra=request.user.has_perms(('infra',)), instance=switch.switch_interface.domain) instance=switch.switch_interface.machine
if switch_form.is_valid() and machine_form.is_valid() and interface_form.is_valid(): )
interface_form = EditInterfaceForm(
request.POST or None,
instance=switch.switch_interface
)
domain_form = AliasForm(
request.POST or None,
infra=request.user.has_perms(('infra',)),
instance=switch.switch_interface.domain
)
if switch_form.is_valid() and machine_form.is_valid()\
and interface_form.is_valid():
new_interface = interface_form.save(commit=False) new_interface = interface_form.save(commit=False)
new_machine = machine_form.save(commit=False) new_machine = machine_form.save(commit=False)
new_switch = switch_form.save(commit=False) new_switch_instance = switch_form.save(commit=False)
new_domain = domain_form.save(commit=False) new_domain = domain_form.save(commit=False)
with transaction.atomic(), reversion.create_revision(): with transaction.atomic(), reversion.create_revision():
new_machine.save() new_machine.save()
reversion.set_user(request.user) reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in machine_form.changed_data)) reversion.set_comment(
"Champs modifié(s) : %s" % ', '.join(
field for field in machine_form.changed_data
)
)
with transaction.atomic(), reversion.create_revision(): with transaction.atomic(), reversion.create_revision():
new_interface.save() new_interface.save()
reversion.set_user(request.user) reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in interface_form.changed_data)) reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in interface_form.changed_data)
)
with transaction.atomic(), reversion.create_revision(): with transaction.atomic(), reversion.create_revision():
new_domain.save() new_domain.save()
reversion.set_user(request.user) reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in domain_form.changed_data)) reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in domain_form.changed_data)
)
with transaction.atomic(), reversion.create_revision(): with transaction.atomic(), reversion.create_revision():
new_switch.save() new_switch_instance.save()
reversion.set_user(request.user) reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in switch_form.changed_data)) reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in switch_form.changed_data)
)
messages.success(request, "Le switch a bien été modifié") messages.success(request, "Le switch a bien été modifié")
return redirect("/topologie/") return redirect("/topologie/")
i_bft_param = generate_ipv4_bft_param( interface_form, False ) i_bft_param = generate_ipv4_bft_param( interface_form, False )
@ -367,7 +457,8 @@ def new_room(request):
reversion.set_comment("Création") reversion.set_comment("Création")
messages.success(request, "La chambre a été créé") messages.success(request, "La chambre a été créé")
return redirect("/topologie/index_room/") return redirect("/topologie/index_room/")
return form({'topoform':room}, 'topologie/topo.html', request) return form({'topoform': room}, 'topologie/topo.html', request)
@login_required @login_required
@permission_required('infra') @permission_required('infra')
@ -383,10 +474,13 @@ def edit_room(request, room_id):
with transaction.atomic(), reversion.create_revision(): with transaction.atomic(), reversion.create_revision():
room.save() room.save()
reversion.set_user(request.user) reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in room.changed_data)) reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
field for field in room.changed_data)
)
messages.success(request, "La chambre a bien été modifiée") messages.success(request, "La chambre a bien été modifiée")
return redirect("/topologie/index_room/") return redirect("/topologie/index_room/")
return form({'topoform':room}, 'topologie/topo.html', request) return form({'topoform': room}, 'topologie/topo.html', request)
@login_required @login_required
@permission_required('infra') @permission_required('infra')
@ -395,7 +489,7 @@ def del_room(request, room_id):
try: try:
room = Room.objects.get(pk=room_id) room = Room.objects.get(pk=room_id)
except Room.DoesNotExist: except Room.DoesNotExist:
messages.error(request, u"Chambre inexistante" ) messages.error(request, u"Chambre inexistante")
return redirect("/topologie/index_room/") return redirect("/topologie/index_room/")
if request.method == "POST": if request.method == "POST":
try: try:
@ -405,6 +499,10 @@ def del_room(request, room_id):
reversion.set_comment("Destruction") reversion.set_comment("Destruction")
messages.success(request, "La chambre/prise a été détruite") messages.success(request, "La chambre/prise a été détruite")
except ProtectedError: except ProtectedError:
messages.error(request, "La chambre %s est affectée à un autre objet, impossible de la supprimer (switch ou user)" % room) messages.error(request, "La chambre %s est affectée à un autre objet,\
impossible de la supprimer (switch ou user)" % room)
return redirect("/topologie/index_room/") return redirect("/topologie/index_room/")
return form({'objet': room, 'objet_name': 'Chambre'}, 'topologie/delete.html', request) return form({
'objet': room,
'objet_name': 'Chambre'
}, 'topologie/delete.html', request)

View file

@ -651,10 +651,10 @@ def profil(request, userid):
if not request.user.has_perms(('cableur',)) and users != request.user: if not request.user.has_perms(('cableur',)) and users != request.user:
messages.error(request, "Vous ne pouvez pas afficher un autre user que vous sans droit cableur") messages.error(request, "Vous ne pouvez pas afficher un autre user que vous sans droit cableur")
return redirect("/users/profil/" + str(request.user.id)) return redirect("/users/profil/" + str(request.user.id))
machines = Machine.objects.filter(user__pseudo=users).select_related('user').prefetch_related('interface_set__domain__extension').prefetch_related('interface_set__ipv4__ip_type__extension').prefetch_related('interface_set__type').prefetch_related('interface_set__domain__related_domain__extension') machines = Machine.objects.filter(user=users).select_related('user').prefetch_related('interface_set__domain__extension').prefetch_related('interface_set__ipv4__ip_type__extension').prefetch_related('interface_set__type').prefetch_related('interface_set__domain__related_domain__extension')
factures = Facture.objects.filter(user__pseudo=users) factures = Facture.objects.filter(user=users)
bans = Ban.objects.filter(user__pseudo=users) bans = Ban.objects.filter(user=users)
whitelists = Whitelist.objects.filter(user__pseudo=users) whitelists = Whitelist.objects.filter(user=users)
list_droits = Right.objects.filter(user=users) list_droits = Right.objects.filter(user=users)
options, created = OptionalUser.objects.get_or_create() options, created = OptionalUser.objects.get_or_create()
user_solde = options.user_solde user_solde = options.user_solde