mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-27 15:12:25 +00:00
Merge branch 'master' into reverse_url
This commit is contained in:
commit
5dc44a7136
19 changed files with 458 additions and 74 deletions
|
@ -38,6 +38,7 @@ ArticleForm, BanqueForm, PaiementForm permettent aux admin d'ajouter,
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.db.models import Q
|
||||||
from django.forms import ModelForm, Form
|
from django.forms import ModelForm, Form
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
from .models import Article, Paiement, Facture, Banque
|
from .models import Article, Paiement, Facture, Banque
|
||||||
|
@ -90,10 +91,24 @@ class CreditSoldeForm(NewFactureForm):
|
||||||
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 SelectUserArticleForm(Form):
|
||||||
"""Selection d'un article lors de la creation d'une facture"""
|
"""Selection d'un article lors de la creation d'une facture"""
|
||||||
article = forms.ModelChoiceField(
|
article = forms.ModelChoiceField(
|
||||||
queryset=Article.objects.all(),
|
queryset=Article.objects.filter(Q(type_user='All') | Q(type_user='Adherent')),
|
||||||
|
label="Article",
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
quantity = forms.IntegerField(
|
||||||
|
label="Quantité",
|
||||||
|
validators=[MinValueValidator(1)],
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SelectClubArticleForm(Form):
|
||||||
|
"""Selection d'un article lors de la creation d'une facture"""
|
||||||
|
article = forms.ModelChoiceField(
|
||||||
|
queryset=Article.objects.filter(Q(type_user='All') | Q(type_user='Club')),
|
||||||
label="Article",
|
label="Article",
|
||||||
required=True
|
required=True
|
||||||
)
|
)
|
||||||
|
|
20
cotisations/migrations/0025_article_type_user.py
Normal file
20
cotisations/migrations/0025_article_type_user.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-10-27 03:02
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cotisations', '0024_auto_20171015_2033'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='article',
|
||||||
|
name='type_user',
|
||||||
|
field=models.CharField(choices=[('Adherent', 'Adherent'), ('Club', 'Club'), ('All', 'All')], default='All', max_length=255),
|
||||||
|
),
|
||||||
|
]
|
78
cotisations/migrations/0026_auto_20171028_0126.py
Normal file
78
cotisations/migrations/0026_auto_20171028_0126.py
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-10-27 23:26
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def create_type(apps, schema_editor):
|
||||||
|
Cotisation = apps.get_model('cotisations', 'Cotisation')
|
||||||
|
Vente = apps.get_model('cotisations', 'Vente')
|
||||||
|
Article = apps.get_model('cotisations', 'Article')
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
articles = Article.objects.using(db_alias).all()
|
||||||
|
ventes = Vente.objects.using(db_alias).all()
|
||||||
|
cotisations = Cotisation.objects.using(db_alias).all()
|
||||||
|
for article in articles:
|
||||||
|
if article.iscotisation:
|
||||||
|
article.type_cotisation='All'
|
||||||
|
article.save(using=db_alias)
|
||||||
|
for vente in ventes:
|
||||||
|
if vente.iscotisation:
|
||||||
|
vente.type_cotisation='All'
|
||||||
|
vente.save(using=db_alias)
|
||||||
|
for cotisation in cotisations:
|
||||||
|
cotisation.type_cotisation='All'
|
||||||
|
cotisation.save(using=db_alias)
|
||||||
|
|
||||||
|
def delete_type(apps, schema_editor):
|
||||||
|
Vente = apps.get_model('cotisations', 'Vente')
|
||||||
|
Article = apps.get_model('cotisations', 'Article')
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
articles = Articles.objects.using(db_alias).all()
|
||||||
|
ventes = Vente.objects.using(db_alias).all()
|
||||||
|
for article in articles:
|
||||||
|
if article.type_cotisation:
|
||||||
|
article.iscotisation=True
|
||||||
|
else:
|
||||||
|
article.iscotisation=False
|
||||||
|
article.save(using=db_alias)
|
||||||
|
for vente in ventes:
|
||||||
|
if vente.iscotisation:
|
||||||
|
vente.iscotisation=True
|
||||||
|
else:
|
||||||
|
vente.iscotisation=False
|
||||||
|
vente.save(using=db_alias)
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cotisations', '0025_article_type_user'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='article',
|
||||||
|
name='type_cotisation',
|
||||||
|
field=models.CharField(blank=True, choices=[('Connexion', 'Connexion'), ('Adhesion', 'Adhesion'), ('All', 'All')], default=None, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='cotisation',
|
||||||
|
name='type_cotisation',
|
||||||
|
field=models.CharField(choices=[('Connexion', 'Connexion'), ('Adhesion', 'Adhesion'), ('All', 'All')], max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vente',
|
||||||
|
name='type_cotisation',
|
||||||
|
field=models.CharField(blank=True, choices=[('Connexion', 'Connexion'), ('Adhesion', 'Adhesion'), ('All', 'All')], max_length=255, null=True),
|
||||||
|
),
|
||||||
|
migrations.RunPython(create_type, delete_type),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='article',
|
||||||
|
name='iscotisation',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='vente',
|
||||||
|
name='iscotisation',
|
||||||
|
),
|
||||||
|
]
|
20
cotisations/migrations/0027_auto_20171029_1156.py
Normal file
20
cotisations/migrations/0027_auto_20171029_1156.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-10-29 10:56
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cotisations', '0026_auto_20171028_0126'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=255),
|
||||||
|
),
|
||||||
|
]
|
|
@ -47,6 +47,7 @@ from __future__ import unicode_literals
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
from django.db.models.signals import post_save, post_delete
|
from django.db.models.signals import post_save, post_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.forms import ValidationError
|
from django.forms import ValidationError
|
||||||
|
@ -127,15 +128,26 @@ class Vente(models.Model):
|
||||||
iscotisation"""
|
iscotisation"""
|
||||||
PRETTY_NAME = "Ventes effectuées"
|
PRETTY_NAME = "Ventes effectuées"
|
||||||
|
|
||||||
|
COTISATION_TYPE = (
|
||||||
|
('Connexion', 'Connexion'),
|
||||||
|
('Adhesion', 'Adhesion'),
|
||||||
|
('All', 'All'),
|
||||||
|
)
|
||||||
|
|
||||||
facture = models.ForeignKey('Facture', on_delete=models.CASCADE)
|
facture = models.ForeignKey('Facture', on_delete=models.CASCADE)
|
||||||
number = models.IntegerField(validators=[MinValueValidator(1)])
|
number = models.IntegerField(validators=[MinValueValidator(1)])
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
prix = models.DecimalField(max_digits=5, decimal_places=2)
|
prix = models.DecimalField(max_digits=5, decimal_places=2)
|
||||||
iscotisation = models.BooleanField()
|
|
||||||
duration = models.PositiveIntegerField(
|
duration = models.PositiveIntegerField(
|
||||||
help_text="Durée exprimée en mois entiers",
|
help_text="Durée exprimée en mois entiers",
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True)
|
null=True)
|
||||||
|
type_cotisation = models.CharField(
|
||||||
|
choices=COTISATION_TYPE,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
max_length=255
|
||||||
|
)
|
||||||
|
|
||||||
def prix_total(self):
|
def prix_total(self):
|
||||||
"""Renvoie le prix_total de self (nombre*prix)"""
|
"""Renvoie le prix_total de self (nombre*prix)"""
|
||||||
|
@ -155,22 +167,26 @@ class Vente(models.Model):
|
||||||
"""Update et crée l'objet cotisation associé à une facture, prend
|
"""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
|
en argument l'user, la facture pour la quantitéi, et l'article pour
|
||||||
la durée"""
|
la durée"""
|
||||||
if not hasattr(self, 'cotisation'):
|
if not hasattr(self, 'cotisation') and self.type_cotisation:
|
||||||
cotisation = Cotisation(vente=self)
|
cotisation = Cotisation(vente=self)
|
||||||
|
cotisation.type_cotisation = self.type_cotisation
|
||||||
if date_start:
|
if date_start:
|
||||||
end_adhesion = Cotisation.objects.filter(
|
end_cotisation = Cotisation.objects.filter(
|
||||||
vente__in=Vente.objects.filter(
|
vente__in=Vente.objects.filter(
|
||||||
facture__in=Facture.objects.filter(
|
facture__in=Facture.objects.filter(
|
||||||
user=self.facture.user
|
user=self.facture.user
|
||||||
).exclude(valid=False))
|
).exclude(valid=False))
|
||||||
|
).filter(Q(type_cotisation='All') | Q(type_cotisation=self.type_cotisation)
|
||||||
).filter(
|
).filter(
|
||||||
date_start__lt=date_start
|
date_start__lt=date_start
|
||||||
).aggregate(Max('date_end'))['date_end__max']
|
).aggregate(Max('date_end'))['date_end__max']
|
||||||
|
elif self.type_cotisation=="Adhesion":
|
||||||
|
end_cotisation = self.facture.user.end_adhesion()
|
||||||
else:
|
else:
|
||||||
end_adhesion = self.facture.user.end_adhesion()
|
end_cotisation = self.facture.user.end_connexion()
|
||||||
date_start = date_start or timezone.now()
|
date_start = date_start or timezone.now()
|
||||||
end_adhesion = end_adhesion or date_start
|
end_cotisation = end_cotisation or date_start
|
||||||
date_max = max(end_adhesion, date_start)
|
date_max = max(end_cotisation, date_start)
|
||||||
cotisation.date_start = date_max
|
cotisation.date_start = date_max
|
||||||
cotisation.date_end = cotisation.date_start + relativedelta(
|
cotisation.date_end = cotisation.date_start + relativedelta(
|
||||||
months=self.duration*self.number
|
months=self.duration*self.number
|
||||||
|
@ -179,7 +195,7 @@ class Vente(models.Model):
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
# On verifie que si iscotisation, duration est présent
|
# On verifie que si iscotisation, duration est présent
|
||||||
if self.iscotisation and not self.duration:
|
if self.type_cotisation and not self.duration:
|
||||||
raise ValidationError("Cotisation et durée doivent être présents\
|
raise ValidationError("Cotisation et durée doivent être présents\
|
||||||
ensembles")
|
ensembles")
|
||||||
self.update_cotisation()
|
self.update_cotisation()
|
||||||
|
@ -197,7 +213,7 @@ def vente_post_save(sender, **kwargs):
|
||||||
if hasattr(vente, 'cotisation'):
|
if hasattr(vente, 'cotisation'):
|
||||||
vente.cotisation.vente = vente
|
vente.cotisation.vente = vente
|
||||||
vente.cotisation.save()
|
vente.cotisation.save()
|
||||||
if vente.iscotisation:
|
if vente.type_cotisation:
|
||||||
vente.create_cotis()
|
vente.create_cotis()
|
||||||
vente.cotisation.save()
|
vente.cotisation.save()
|
||||||
user = vente.facture.user
|
user = vente.facture.user
|
||||||
|
@ -209,7 +225,7 @@ def vente_post_delete(sender, **kwargs):
|
||||||
"""Après suppression d'une vente, on synchronise l'user ldap (ex
|
"""Après suppression d'une vente, on synchronise l'user ldap (ex
|
||||||
suppression d'une cotisation"""
|
suppression d'une cotisation"""
|
||||||
vente = kwargs['instance']
|
vente = kwargs['instance']
|
||||||
if vente.iscotisation:
|
if vente.type_cotisation:
|
||||||
user = vente.facture.user
|
user = vente.facture.user
|
||||||
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
||||||
|
|
||||||
|
@ -219,18 +235,47 @@ class Article(models.Model):
|
||||||
et duree si c'est une cotisation"""
|
et duree si c'est une cotisation"""
|
||||||
PRETTY_NAME = "Articles en vente"
|
PRETTY_NAME = "Articles en vente"
|
||||||
|
|
||||||
name = models.CharField(max_length=255, unique=True)
|
USER_TYPES = (
|
||||||
|
('Adherent', 'Adherent'),
|
||||||
|
('Club', 'Club'),
|
||||||
|
('All', 'All'),
|
||||||
|
)
|
||||||
|
|
||||||
|
COTISATION_TYPE = (
|
||||||
|
('Connexion', 'Connexion'),
|
||||||
|
('Adhesion', 'Adhesion'),
|
||||||
|
('All', 'All'),
|
||||||
|
)
|
||||||
|
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
prix = models.DecimalField(max_digits=5, decimal_places=2)
|
prix = models.DecimalField(max_digits=5, decimal_places=2)
|
||||||
iscotisation = models.BooleanField()
|
|
||||||
duration = models.PositiveIntegerField(
|
duration = models.PositiveIntegerField(
|
||||||
help_text="Durée exprimée en mois entiers",
|
help_text="Durée exprimée en mois entiers",
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[MinValueValidator(0)])
|
validators=[MinValueValidator(0)])
|
||||||
|
type_user = models.CharField(
|
||||||
|
choices=USER_TYPES,
|
||||||
|
default='All',
|
||||||
|
max_length=255
|
||||||
|
)
|
||||||
|
type_cotisation = models.CharField(
|
||||||
|
choices=COTISATION_TYPE,
|
||||||
|
default=None,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
max_length=255
|
||||||
|
)
|
||||||
|
|
||||||
|
unique_together = ('name', 'type_user')
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.name.lower() == "solde":
|
if self.name.lower() == "solde":
|
||||||
raise ValidationError("Solde est un nom d'article invalide")
|
raise ValidationError("Solde est un nom d'article invalide")
|
||||||
|
if self.type_cotisation and not self.duration:
|
||||||
|
raise ValidationError(
|
||||||
|
"La durée est obligatoire si il s'agit d'une cotisation"
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -275,7 +320,17 @@ class Cotisation(models.Model):
|
||||||
"""Objet cotisation, debut et fin, relié en onetoone à une vente"""
|
"""Objet cotisation, debut et fin, relié en onetoone à une vente"""
|
||||||
PRETTY_NAME = "Cotisations"
|
PRETTY_NAME = "Cotisations"
|
||||||
|
|
||||||
|
COTISATION_TYPE = (
|
||||||
|
('Connexion', 'Connexion'),
|
||||||
|
('Adhesion', 'Adhesion'),
|
||||||
|
('All', 'All'),
|
||||||
|
)
|
||||||
|
|
||||||
vente = models.OneToOneField('Vente', on_delete=models.CASCADE, null=True)
|
vente = models.OneToOneField('Vente', on_delete=models.CASCADE, null=True)
|
||||||
|
type_cotisation = models.CharField(
|
||||||
|
choices=COTISATION_TYPE,
|
||||||
|
max_length=255,
|
||||||
|
)
|
||||||
date_start = models.DateTimeField()
|
date_start = models.DateTimeField()
|
||||||
date_end = models.DateTimeField()
|
date_end = models.DateTimeField()
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<th>Article</th>
|
<th>Article</th>
|
||||||
<th>Prix</th>
|
<th>Prix</th>
|
||||||
<th>Cotisation</th>
|
<th>Type Cotisation</th>
|
||||||
<th>Durée (mois)</th>
|
<th>Durée (mois)</th>
|
||||||
|
<th>Article pour</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -36,8 +37,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ article.name }}</td>
|
<td>{{ article.name }}</td>
|
||||||
<td>{{ article.prix }}</td>
|
<td>{{ article.prix }}</td>
|
||||||
<td>{{ article.iscotisation }}</td>
|
<td>{{ article.type_cotisation }}</td>
|
||||||
<td>{{ article.duration }}</td>
|
<td>{{ article.duration }}</td>
|
||||||
|
<td>{{ article.type_user }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{% if is_trez %}
|
{% if is_trez %}
|
||||||
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'cotisations:edit-article' article.id %}">
|
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'cotisations:edit-article' article.id %}">
|
||||||
|
|
|
@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<th>Prix total</th>
|
<th>Prix total</th>
|
||||||
<th>{% include "buttons/sort.html" with prefix='cotis' col='paiement' text='Moyen de paiement' %}</th>
|
<th>{% include "buttons/sort.html" with prefix='cotis' col='paiement' text='Moyen de paiement' %}</th>
|
||||||
<th>{% include "buttons/sort.html" with prefix='cotis' col='date' text='Date' %}</th>
|
<th>{% include "buttons/sort.html" with prefix='cotis' col='date' text='Date' %}</th>
|
||||||
<th></th>
|
<th>{% include "buttons/sort.html" with prefix='cotis' col='id' text='Id facture' %}</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -46,17 +46,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<td>{{ facture.prix_total }}</td>
|
<td>{{ facture.prix_total }}</td>
|
||||||
<td>{{ facture.paiement }}</td>
|
<td>{{ facture.paiement }}</td>
|
||||||
<td>{{ facture.date }}</td>
|
<td>{{ facture.date }}</td>
|
||||||
|
<td>{{ facture.id }}</td>
|
||||||
{% if is_cableur %}
|
{% if is_cableur %}
|
||||||
<td>
|
<td>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button class="btn btn-default dropdown-toggle" type="button" id="editionfacture" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
<button class="btn btn-default dropdown-toggle" type="button" id="editionfacture" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||||
Modifier
|
Edition
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" aria-labelledby="editionfacture">
|
<ul class="dropdown-menu" aria-labelledby="editionfacture">
|
||||||
{% if facture.valid and not facture.control or is_trez %}
|
{% if facture.valid and not facture.control or is_trez %}
|
||||||
<li><a href="{% url 'cotisations:edit-facture' facture.id %}"><i class="glyphicon glyphicon-bitcoin"></i> Editer</a></li>
|
<li><a href="{% url 'cotisations:edit-facture' facture.id %}"><i class="glyphicon glyphicon-bitcoin"></i> Modifier</a></li>
|
||||||
<li><a href="{% url 'cotisations:del-facture' facture.id %}"><i class="glyphicon glyphicon-trash"></i> Supprimer</a></li>
|
<li><a href="{% url 'cotisations:del-facture' facture.id %}"><i class="glyphicon glyphicon-trash"></i> Supprimer</a></li>
|
||||||
|
<li><a href="{% url 'cotisations:history' 'facture' facture.id %}"><i class="glyphicon glyphicon-time"></i> Historique</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li>Facture controlée</li>
|
<li>Facture controlée</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -74,11 +76,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<font color="red">Facture invalide</font>
|
<font color="red">Facture invalide</font>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
|
||||||
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'cotisations:history' 'facture' facture.id %}">
|
|
||||||
<i class="glyphicon glyphicon-time"></i>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -42,6 +42,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<th>Profil</th>
|
<th>Profil</th>
|
||||||
<th>{% include "buttons/sort.html" with prefix='control' col='name' text='Nom' %}</th>
|
<th>{% include "buttons/sort.html" with prefix='control' col='name' text='Nom' %}</th>
|
||||||
<th>{% include "buttons/sort.html" with prefix='control' col='surname' text='Prénom' %}</th>
|
<th>{% include "buttons/sort.html" with prefix='control' col='surname' text='Prénom' %}</th>
|
||||||
|
<th>{% include "buttons/sort.html" with prefix='control' col='id' text='Id facture' %}</th>
|
||||||
|
<th>{% include "buttons/sort.html" with prefix='control' col='user-id' text='Id user' %}</th>
|
||||||
<th>Designation</th>
|
<th>Designation</th>
|
||||||
<th>Prix total</th>
|
<th>Prix total</th>
|
||||||
<th>{% include "buttons/sort.html" with prefix='control' col='paiement' text='Moyen de paiement' %}</th>
|
<th>{% include "buttons/sort.html" with prefix='control' col='paiement' text='Moyen de paiement' %}</th>
|
||||||
|
@ -58,6 +60,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</td>
|
</td>
|
||||||
<td>{{ form.instance.user.name }}</td>
|
<td>{{ form.instance.user.name }}</td>
|
||||||
<td>{{ form.instance.user.surname }}</td>
|
<td>{{ form.instance.user.surname }}</td>
|
||||||
|
<td>{{ form.instance.id }}</td>
|
||||||
|
<td>{{ form.instance.user.id }}</td>
|
||||||
<td>{{ form.instance.name }}</td>
|
<td>{{ form.instance.name }}</td>
|
||||||
<td>{{ form.instance.prix_total }}</td>
|
<td>{{ form.instance.prix_total }}</td>
|
||||||
<td>{{ form.instance.paiement }}</td>
|
<td>{{ form.instance.paiement }}</td>
|
||||||
|
|
|
@ -33,6 +33,7 @@ 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 ProtectedError
|
from django.db.models import ProtectedError
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from django.db.models import Q
|
||||||
from django.forms import modelformset_factory, formset_factory
|
from django.forms import modelformset_factory, formset_factory
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
|
@ -45,10 +46,21 @@ from re2o.views import form
|
||||||
from re2o.utils import SortTable
|
from re2o.utils import SortTable
|
||||||
from preferences.models import OptionalUser, AssoOption, GeneralOption
|
from preferences.models import OptionalUser, AssoOption, GeneralOption
|
||||||
from .models import Facture, Article, Vente, Paiement, Banque
|
from .models import Facture, Article, Vente, Paiement, Banque
|
||||||
from .forms import NewFactureForm, TrezEditFactureForm, EditFactureForm
|
from .forms import (
|
||||||
from .forms import ArticleForm, DelArticleForm, PaiementForm, DelPaiementForm
|
NewFactureForm,
|
||||||
from .forms import BanqueForm, DelBanqueForm, NewFactureFormPdf
|
TrezEditFactureForm,
|
||||||
from .forms import SelectArticleForm, CreditSoldeForm
|
EditFactureForm,
|
||||||
|
ArticleForm,
|
||||||
|
DelArticleForm,
|
||||||
|
PaiementForm,
|
||||||
|
DelPaiementForm,
|
||||||
|
BanqueForm,
|
||||||
|
DelBanqueForm,
|
||||||
|
NewFactureFormPdf,
|
||||||
|
SelectUserArticleForm,
|
||||||
|
SelectClubArticleForm,
|
||||||
|
CreditSoldeForm
|
||||||
|
)
|
||||||
from .tex import render_tex
|
from .tex import render_tex
|
||||||
|
|
||||||
|
|
||||||
|
@ -69,10 +81,15 @@ def new_facture(request, userid):
|
||||||
return redirect(reverse('cotisations:index'))
|
return redirect(reverse('cotisations:index'))
|
||||||
facture = Facture(user=user)
|
facture = Facture(user=user)
|
||||||
# Le template a besoin de connaitre les articles pour le js
|
# Le template a besoin de connaitre les articles pour le js
|
||||||
article_list = Article.objects.all()
|
article_list = Article.objects.filter(
|
||||||
|
Q(type_user='All') | Q(type_user=request.user.class_name)
|
||||||
|
)
|
||||||
# On envoie la form fature et un formset d'articles
|
# On envoie la form fature et un formset d'articles
|
||||||
facture_form = NewFactureForm(request.POST or None, instance=facture)
|
facture_form = NewFactureForm(request.POST or None, instance=facture)
|
||||||
article_formset = formset_factory(SelectArticleForm)(request.POST or None)
|
if request.user.is_class_club:
|
||||||
|
article_formset = formset_factory(SelectClubArticleForm)(request.POST or None)
|
||||||
|
else:
|
||||||
|
article_formset = formset_factory(SelectUserArticleForm)(request.POST or None)
|
||||||
if facture_form.is_valid() and article_formset.is_valid():
|
if facture_form.is_valid() and article_formset.is_valid():
|
||||||
new_facture_instance = facture_form.save(commit=False)
|
new_facture_instance = facture_form.save(commit=False)
|
||||||
articles = article_formset
|
articles = article_formset
|
||||||
|
@ -111,7 +128,7 @@ def new_facture(request, userid):
|
||||||
facture=new_facture_instance,
|
facture=new_facture_instance,
|
||||||
name=article.name,
|
name=article.name,
|
||||||
prix=article.prix,
|
prix=article.prix,
|
||||||
iscotisation=article.iscotisation,
|
type_cotisation=article.type_cotisation,
|
||||||
duration=article.duration,
|
duration=article.duration,
|
||||||
number=quantity
|
number=quantity
|
||||||
)
|
)
|
||||||
|
@ -119,7 +136,7 @@ def new_facture(request, userid):
|
||||||
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
|
if any(art_item.cleaned_data['article'].type_cotisation
|
||||||
for art_item in articles if art_item.cleaned_data):
|
for art_item in articles if art_item.cleaned_data):
|
||||||
messages.success(
|
messages.success(
|
||||||
request,
|
request,
|
||||||
|
@ -326,8 +343,6 @@ def credit_solde(request, userid):
|
||||||
facture=facture_instance,
|
facture=facture_instance,
|
||||||
name="solde",
|
name="solde",
|
||||||
prix=facture.cleaned_data['montant'],
|
prix=facture.cleaned_data['montant'],
|
||||||
iscotisation=False,
|
|
||||||
duration=0,
|
|
||||||
number=1
|
number=1
|
||||||
)
|
)
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
|
|
|
@ -247,6 +247,9 @@ def check_user_machine_and_register(nas_type, username, mac_address):
|
||||||
return (False, u"Machine enregistrée sur le compte d'un autre user...", '')
|
return (False, u"Machine enregistrée sur le compte d'un autre user...", '')
|
||||||
elif not interface.is_active:
|
elif not interface.is_active:
|
||||||
return (False, u"Machine desactivée", '')
|
return (False, u"Machine desactivée", '')
|
||||||
|
elif not interface.ipv4:
|
||||||
|
interface.assign_ipv4()
|
||||||
|
return (True, u"Ok, Reassignation de l'ipv4", user.pwd_ntlm)
|
||||||
else:
|
else:
|
||||||
return (True, u"Access ok", user.pwd_ntlm)
|
return (True, u"Access ok", user.pwd_ntlm)
|
||||||
elif nas_type:
|
elif nas_type:
|
||||||
|
@ -324,8 +327,13 @@ def decide_vlan_and_register_switch(nas, nas_type, port_number, mac_address):
|
||||||
return (sw_name, u'Access Ok, Capture de la mac...' + extra_log, DECISION_VLAN)
|
return (sw_name, u'Access Ok, Capture de la mac...' + extra_log, DECISION_VLAN)
|
||||||
else:
|
else:
|
||||||
return (sw_name, u'Erreur dans le register mac %s' % reason + unicode(mac_address), VLAN_NOK)
|
return (sw_name, u'Erreur dans le register mac %s' % reason + unicode(mac_address), VLAN_NOK)
|
||||||
elif not interface.first().is_active:
|
else:
|
||||||
|
interface = interface.first()
|
||||||
|
if not interface.is_active:
|
||||||
return (sw_name, u'Machine non active / adherent non cotisant', VLAN_NOK)
|
return (sw_name, u'Machine non active / adherent non cotisant', VLAN_NOK)
|
||||||
|
elif not interface.ipv4:
|
||||||
|
interface.assign_ipv4()
|
||||||
|
return (sw_name, u"Ok, Reassignation de l'ipv4" + extra_log, DECISION_VLAN)
|
||||||
else:
|
else:
|
||||||
return (sw_name, u'Machine OK' + extra_log, DECISION_VLAN)
|
return (sw_name, u'Machine OK' + extra_log, DECISION_VLAN)
|
||||||
|
|
||||||
|
|
132
logs/views.py
132
logs/views.py
|
@ -47,13 +47,50 @@ from django.db.models import Count
|
||||||
from reversion.models import Revision
|
from reversion.models import Revision
|
||||||
from reversion.models import Version, ContentType
|
from reversion.models import Version, ContentType
|
||||||
|
|
||||||
from users.models import User, ServiceUser, Right, School, ListRight, ListShell
|
from users.models import (
|
||||||
from users.models import Ban, Whitelist
|
User,
|
||||||
from cotisations.models import Facture, Vente, Article, Banque, Paiement
|
ServiceUser,
|
||||||
from cotisations.models import Cotisation
|
Right,
|
||||||
from machines.models import Machine, MachineType, IpType, Extension, Interface
|
School,
|
||||||
from machines.models import Domain, IpList
|
ListRight,
|
||||||
from topologie.models import Switch, Port, Room
|
ListShell,
|
||||||
|
Ban,
|
||||||
|
Whitelist,
|
||||||
|
Adherent,
|
||||||
|
Club
|
||||||
|
)
|
||||||
|
from cotisations.models import (
|
||||||
|
Facture,
|
||||||
|
Vente,
|
||||||
|
Article,
|
||||||
|
Banque,
|
||||||
|
Paiement,
|
||||||
|
Cotisation
|
||||||
|
)
|
||||||
|
from machines.models import (
|
||||||
|
Machine,
|
||||||
|
MachineType,
|
||||||
|
IpType,
|
||||||
|
Extension,
|
||||||
|
Interface,
|
||||||
|
Domain,
|
||||||
|
IpList,
|
||||||
|
OuverturePortList,
|
||||||
|
Service,
|
||||||
|
Vlan,
|
||||||
|
Nas,
|
||||||
|
SOA,
|
||||||
|
Mx,
|
||||||
|
Ns
|
||||||
|
)
|
||||||
|
from topologie.models import (
|
||||||
|
Switch,
|
||||||
|
Port,
|
||||||
|
Room,
|
||||||
|
Stack,
|
||||||
|
ModelSwitch,
|
||||||
|
ConstructorSwitch
|
||||||
|
)
|
||||||
from preferences.models import GeneralOption
|
from preferences.models import GeneralOption
|
||||||
from re2o.views import form
|
from re2o.views import form
|
||||||
from re2o.utils import all_whitelisted, all_baned, all_has_access, all_adherent
|
from re2o.utils import all_whitelisted, all_baned, all_has_access, all_adherent
|
||||||
|
@ -184,45 +221,77 @@ def stats_general(request):
|
||||||
range, et les statistiques générales sur les users : users actifs,
|
range, et les statistiques générales sur les users : users actifs,
|
||||||
cotisants, activés, archivés, etc"""
|
cotisants, activés, archivés, etc"""
|
||||||
ip_dict = dict()
|
ip_dict = dict()
|
||||||
for ip_range in IpType.objects.all():
|
for ip_range in IpType.objects.select_related('vlan').all():
|
||||||
all_ip = IpList.objects.filter(ip_type=ip_range)
|
all_ip = IpList.objects.filter(ip_type=ip_range)
|
||||||
used_ip = Interface.objects.filter(ipv4__in=all_ip).count()
|
used_ip = Interface.objects.filter(ipv4__in=all_ip).count()
|
||||||
active_ip = all_active_assigned_interfaces_count().filter(
|
active_ip = all_active_assigned_interfaces_count().filter(
|
||||||
ipv4__in=IpList.objects.filter(ip_type=ip_range)
|
ipv4__in=IpList.objects.filter(ip_type=ip_range)
|
||||||
).count()
|
).count()
|
||||||
ip_dict[ip_range] = [ip_range, all_ip.count(),
|
ip_dict[ip_range] = [ip_range, ip_range.vlan, all_ip.count(),
|
||||||
used_ip, active_ip, all_ip.count()-used_ip]
|
used_ip, active_ip, all_ip.count()-used_ip]
|
||||||
|
_all_adherent = all_adherent()
|
||||||
|
_all_has_access = all_has_access()
|
||||||
|
_all_baned = all_baned()
|
||||||
|
_all_whitelisted = all_whitelisted()
|
||||||
|
_all_active_interfaces_count = all_active_interfaces_count()
|
||||||
|
_all_active_assigned_interfaces_count = all_active_assigned_interfaces_count()
|
||||||
stats = [
|
stats = [
|
||||||
[["Categorie", "Nombre d'utilisateurs"], {
|
[["Categorie", "Nombre d'utilisateurs (total club et adhérents)", "Nombre d'adhérents", "Nombre de clubs"], {
|
||||||
'active_users': [
|
'active_users': [
|
||||||
"Users actifs",
|
"Users actifs",
|
||||||
User.objects.filter(state=User.STATE_ACTIVE).count()],
|
User.objects.filter(state=User.STATE_ACTIVE).count(),
|
||||||
|
Adherent.objects.filter(state=Adherent.STATE_ACTIVE).count(),
|
||||||
|
Club.objects.filter(state=Club.STATE_ACTIVE).count()],
|
||||||
'inactive_users': [
|
'inactive_users': [
|
||||||
"Users désactivés",
|
"Users désactivés",
|
||||||
User.objects.filter(state=User.STATE_DISABLED).count()],
|
User.objects.filter(state=User.STATE_DISABLED).count(),
|
||||||
|
Adherent.objects.filter(state=Adherent.STATE_DISABLED).count(),
|
||||||
|
Club.objects.filter(state=Club.STATE_DISABLED).count()],
|
||||||
'archive_users': [
|
'archive_users': [
|
||||||
"Users archivés",
|
"Users archivés",
|
||||||
User.objects.filter(state=User.STATE_ARCHIVE).count()],
|
User.objects.filter(state=User.STATE_ARCHIVE).count(),
|
||||||
|
Adherent.objects.filter(state=Adherent.STATE_ARCHIVE).count(),
|
||||||
|
Club.objects.filter(state=Club.STATE_ARCHIVE).count()],
|
||||||
'adherent_users': [
|
'adherent_users': [
|
||||||
"Adhérents à l'association",
|
"Cotisant à l'association",
|
||||||
all_adherent().count()],
|
_all_adherent.count(),
|
||||||
|
_all_adherent.exclude(adherent__isnull=True).count(),
|
||||||
|
_all_adherent.exclude(club__isnull=True).count()],
|
||||||
'connexion_users': [
|
'connexion_users': [
|
||||||
"Utilisateurs bénéficiant d'une connexion",
|
"Utilisateurs bénéficiant d'une connexion",
|
||||||
all_has_access().count()],
|
_all_has_access.count(),
|
||||||
|
_all_has_access.exclude(adherent__isnull=True).count(),
|
||||||
|
_all_has_access.exclude(club__isnull=True).count()],
|
||||||
'ban_users': [
|
'ban_users': [
|
||||||
"Utilisateurs bannis",
|
"Utilisateurs bannis",
|
||||||
all_baned().count()],
|
_all_baned.count(),
|
||||||
|
_all_baned.exclude(adherent__isnull=True).count(),
|
||||||
|
_all_baned.exclude(club__isnull=True).count()],
|
||||||
'whitelisted_user': [
|
'whitelisted_user': [
|
||||||
"Utilisateurs bénéficiant d'une connexion gracieuse",
|
"Utilisateurs bénéficiant d'une connexion gracieuse",
|
||||||
all_whitelisted().count()],
|
_all_whitelisted.count(),
|
||||||
|
_all_whitelisted.exclude(adherent__isnull=True).count(),
|
||||||
|
_all_whitelisted.exclude(club__isnull=True).count()],
|
||||||
'actives_interfaces': [
|
'actives_interfaces': [
|
||||||
"Interfaces actives (ayant accès au reseau)",
|
"Interfaces actives (ayant accès au reseau)",
|
||||||
all_active_interfaces_count().count()],
|
_all_active_interfaces_count.count(),
|
||||||
|
_all_active_interfaces_count.exclude(
|
||||||
|
machine__user__adherent__isnull=True
|
||||||
|
).count(),
|
||||||
|
_all_active_interfaces_count.exclude(
|
||||||
|
machine__user__club__isnull=True
|
||||||
|
).count()],
|
||||||
'actives_assigned_interfaces': [
|
'actives_assigned_interfaces': [
|
||||||
"Interfaces actives et assignées ipv4",
|
"Interfaces actives et assignées ipv4",
|
||||||
all_active_assigned_interfaces_count().count()]
|
_all_active_assigned_interfaces_count.count(),
|
||||||
|
_all_active_assigned_interfaces_count.exclude(
|
||||||
|
machine__user__adherent__isnull=True
|
||||||
|
).count(),
|
||||||
|
_all_active_assigned_interfaces_count.exclude(
|
||||||
|
machine__user__club__isnull=True
|
||||||
|
).count()]
|
||||||
}],
|
}],
|
||||||
[["Range d'ip", "Nombre d'ip totales", "Ip assignées",
|
[["Range d'ip", "Vlan", "Nombre d'ip totales", "Ip assignées",
|
||||||
"Ip assignées à une machine active", "Ip non assignées"], ip_dict]
|
"Ip assignées à une machine active", "Ip non assignées"], ip_dict]
|
||||||
]
|
]
|
||||||
return render(request, 'logs/stats_general.html', {'stats_list': stats})
|
return render(request, 'logs/stats_general.html', {'stats_list': stats})
|
||||||
|
@ -237,6 +306,8 @@ def stats_models(request):
|
||||||
stats = {
|
stats = {
|
||||||
'Users': {
|
'Users': {
|
||||||
'users': [User.PRETTY_NAME, User.objects.count()],
|
'users': [User.PRETTY_NAME, User.objects.count()],
|
||||||
|
'adherents': [Adherent.PRETTY_NAME, Adherent.objects.count()],
|
||||||
|
'clubs': [Club.PRETTY_NAME, Club.objects.count()],
|
||||||
'serviceuser': [ServiceUser.PRETTY_NAME,
|
'serviceuser': [ServiceUser.PRETTY_NAME,
|
||||||
ServiceUser.objects.count()],
|
ServiceUser.objects.count()],
|
||||||
'right': [Right.PRETTY_NAME, Right.objects.count()],
|
'right': [Right.PRETTY_NAME, Right.objects.count()],
|
||||||
|
@ -263,11 +334,30 @@ def stats_models(request):
|
||||||
'alias': [Domain.PRETTY_NAME,
|
'alias': [Domain.PRETTY_NAME,
|
||||||
Domain.objects.exclude(cname=None).count()],
|
Domain.objects.exclude(cname=None).count()],
|
||||||
'iplist': [IpList.PRETTY_NAME, IpList.objects.count()],
|
'iplist': [IpList.PRETTY_NAME, IpList.objects.count()],
|
||||||
|
'service': [Service.PRETTY_NAME, Service.objects.count()],
|
||||||
|
'ouvertureportlist': [
|
||||||
|
OuverturePortList.PRETTY_NAME,
|
||||||
|
OuverturePortList.objects.count()
|
||||||
|
],
|
||||||
|
'vlan': [Vlan.PRETTY_NAME, Vlan.objects.count()],
|
||||||
|
'SOA': [Mx.PRETTY_NAME, Mx.objects.count()],
|
||||||
|
'Mx': [Mx.PRETTY_NAME, Mx.objects.count()],
|
||||||
|
'Ns': [Ns.PRETTY_NAME, Ns.objects.count()],
|
||||||
|
'nas': [Nas.PRETTY_NAME, Nas.objects.count()],
|
||||||
},
|
},
|
||||||
'Topologie': {
|
'Topologie': {
|
||||||
'switch': [Switch.PRETTY_NAME, Switch.objects.count()],
|
'switch': [Switch.PRETTY_NAME, Switch.objects.count()],
|
||||||
'port': [Port.PRETTY_NAME, Port.objects.count()],
|
'port': [Port.PRETTY_NAME, Port.objects.count()],
|
||||||
'chambre': [Room.PRETTY_NAME, Room.objects.count()],
|
'chambre': [Room.PRETTY_NAME, Room.objects.count()],
|
||||||
|
'stack': [Stack.PRETTY_NAME, Stack.objects.count()],
|
||||||
|
'modelswitch': [
|
||||||
|
ModelSwitch.PRETTY_NAME,
|
||||||
|
ModelSwitch.objects.count()
|
||||||
|
],
|
||||||
|
'constructorswitch': [
|
||||||
|
ConstructorSwitch.PRETTY_NAME,
|
||||||
|
ConstructorSwitch.objects.count()
|
||||||
|
],
|
||||||
},
|
},
|
||||||
'Actions effectuées sur la base':
|
'Actions effectuées sur la base':
|
||||||
{
|
{
|
||||||
|
|
|
@ -460,6 +460,15 @@ class Interface(models.Model):
|
||||||
def clean(self, *args, **kwargs):
|
def clean(self, *args, **kwargs):
|
||||||
""" Formate l'addresse mac en mac_bare (fonction filter_mac)
|
""" Formate l'addresse mac en mac_bare (fonction filter_mac)
|
||||||
et assigne une ipv4 dans le bon range si inexistante ou incohérente"""
|
et assigne une ipv4 dans le bon range si inexistante ou incohérente"""
|
||||||
|
# If type was an invalid value, django won't create an attribute type
|
||||||
|
# but try clean() as we may be able to create it from another value
|
||||||
|
# so even if the error as yet been detected at this point, django
|
||||||
|
# continues because the error might not prevent us from creating the
|
||||||
|
# instance.
|
||||||
|
# But in our case, it's impossible to create a type value so we raise
|
||||||
|
# the error.
|
||||||
|
if not hasattr(self, 'type') :
|
||||||
|
raise ValidationError("Le type d'ip choisi n'est pas valide")
|
||||||
self.filter_macaddress()
|
self.filter_macaddress()
|
||||||
self.mac_address = str(EUI(self.mac_address)) or None
|
self.mac_address = str(EUI(self.mac_address)) or None
|
||||||
if not self.ipv4 or self.type.ip_type != self.ipv4.ip_type:
|
if not self.ipv4 or self.type.ip_type != self.ipv4.ip_type:
|
||||||
|
@ -626,6 +635,8 @@ class IpList(models.Model):
|
||||||
|
|
||||||
class Service(models.Model):
|
class Service(models.Model):
|
||||||
""" Definition d'un service (dhcp, dns, etc)"""
|
""" Definition d'un service (dhcp, dns, etc)"""
|
||||||
|
PRETTY_NAME = "Services à générer (dhcp, dns, etc)"
|
||||||
|
|
||||||
service_type = models.CharField(max_length=255, blank=True, unique=True)
|
service_type = models.CharField(max_length=255, blank=True, unique=True)
|
||||||
min_time_regen = models.DurationField(
|
min_time_regen = models.DurationField(
|
||||||
default=timedelta(minutes=1),
|
default=timedelta(minutes=1),
|
||||||
|
@ -673,6 +684,8 @@ def regen(service):
|
||||||
|
|
||||||
class Service_link(models.Model):
|
class Service_link(models.Model):
|
||||||
""" Definition du lien entre serveurs et services"""
|
""" Definition du lien entre serveurs et services"""
|
||||||
|
PRETTY_NAME = "Relation entre service et serveur"
|
||||||
|
|
||||||
service = models.ForeignKey('Service', on_delete=models.CASCADE)
|
service = models.ForeignKey('Service', on_delete=models.CASCADE)
|
||||||
server = models.ForeignKey('Interface', on_delete=models.CASCADE)
|
server = models.ForeignKey('Interface', on_delete=models.CASCADE)
|
||||||
last_regen = models.DateTimeField(auto_now_add=True)
|
last_regen = models.DateTimeField(auto_now_add=True)
|
||||||
|
@ -702,6 +715,8 @@ class Service_link(models.Model):
|
||||||
|
|
||||||
class OuverturePortList(models.Model):
|
class OuverturePortList(models.Model):
|
||||||
"""Liste des ports ouverts sur une interface."""
|
"""Liste des ports ouverts sur une interface."""
|
||||||
|
PRETTY_NAME = "Profil d'ouverture de ports"
|
||||||
|
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
help_text="Nom de la configuration des ports.",
|
help_text="Nom de la configuration des ports.",
|
||||||
max_length=255
|
max_length=255
|
||||||
|
@ -748,6 +763,8 @@ class OuverturePort(models.Model):
|
||||||
|
|
||||||
On limite les ports entre 0 et 65535, tels que défini par la RFC
|
On limite les ports entre 0 et 65535, tels que défini par la RFC
|
||||||
"""
|
"""
|
||||||
|
PRETTY_NAME = "Plage de port ouverte"
|
||||||
|
|
||||||
TCP = 'T'
|
TCP = 'T'
|
||||||
UDP = 'U'
|
UDP = 'U'
|
||||||
IN = 'I'
|
IN = 'I'
|
||||||
|
|
|
@ -56,6 +56,7 @@ def all_adherent(search_time=DT_NOW):
|
||||||
return User.objects.filter(
|
return User.objects.filter(
|
||||||
facture__in=Facture.objects.filter(
|
facture__in=Facture.objects.filter(
|
||||||
vente__in=Vente.objects.filter(
|
vente__in=Vente.objects.filter(
|
||||||
|
Q(type_cotisation='All') | Q(type_cotisation='Adhesion'),
|
||||||
cotisation__in=Cotisation.objects.filter(
|
cotisation__in=Cotisation.objects.filter(
|
||||||
vente__in=Vente.objects.filter(
|
vente__in=Vente.objects.filter(
|
||||||
facture__in=Facture.objects.all().exclude(valid=False)
|
facture__in=Facture.objects.all().exclude(valid=False)
|
||||||
|
@ -94,6 +95,7 @@ def all_has_access(search_time=DT_NOW):
|
||||||
Q(facture__in=Facture.objects.filter(
|
Q(facture__in=Facture.objects.filter(
|
||||||
vente__in=Vente.objects.filter(
|
vente__in=Vente.objects.filter(
|
||||||
cotisation__in=Cotisation.objects.filter(
|
cotisation__in=Cotisation.objects.filter(
|
||||||
|
Q(type_cotisation='All') | Q(type_cotisation='Connexion'),
|
||||||
vente__in=Vente.objects.filter(
|
vente__in=Vente.objects.filter(
|
||||||
facture__in=Facture.objects.all()
|
facture__in=Facture.objects.all()
|
||||||
.exclude(valid=False)
|
.exclude(valid=False)
|
||||||
|
@ -179,15 +181,18 @@ class SortTable:
|
||||||
'cotis_user': ['user__pseudo'],
|
'cotis_user': ['user__pseudo'],
|
||||||
'cotis_paiement': ['paiement__moyen'],
|
'cotis_paiement': ['paiement__moyen'],
|
||||||
'cotis_date': ['date'],
|
'cotis_date': ['date'],
|
||||||
|
'cotis_id': ['id'],
|
||||||
'default': ['-date']
|
'default': ['-date']
|
||||||
}
|
}
|
||||||
COTISATIONS_CONTROL = {
|
COTISATIONS_CONTROL = {
|
||||||
'control_name': ['user__name'],
|
'control_name': ['user__adherent__name'],
|
||||||
'control_surname': ['user__surname'],
|
'control_surname': ['user__surname'],
|
||||||
'control_paiement': ['paiement'],
|
'control_paiement': ['paiement'],
|
||||||
'control_date': ['date'],
|
'control_date': ['date'],
|
||||||
'control_valid': ['valid'],
|
'control_valid': ['valid'],
|
||||||
'control_control': ['control'],
|
'control_control': ['control'],
|
||||||
|
'control_id': ['id'],
|
||||||
|
'control_user-id': ['user__id'],
|
||||||
'default': ['-date']
|
'default': ['-date']
|
||||||
}
|
}
|
||||||
TOPOLOGIE_INDEX = {
|
TOPOLOGIE_INDEX = {
|
||||||
|
|
|
@ -49,6 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
{% include "cookie_banner.html" %}
|
||||||
<div id="wrap">
|
<div id="wrap">
|
||||||
<nav class="navbar navbar-inverse">
|
<nav class="navbar navbar-inverse">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
|
@ -146,7 +147,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<th scope="row">Connexion</th>
|
<th scope="row">Connexion</th>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{% if request_user.has_access %}
|
{% if request_user.has_access %}
|
||||||
<font color="green">Active</font>
|
<font color="green">jusqu'au {{ request.user.end_access|date:"d b Y" }}</font>
|
||||||
{% else %}
|
{% else %}
|
||||||
<font color="red">Désactivée</font>
|
<font color="red">Désactivée</font>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -155,8 +156,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Adhésion</th>
|
<th scope="row">Adhésion</th>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{% if request_user.end_adhesion != None %}
|
{% if request_user.is_adherent %}
|
||||||
<font color="green">{{ request_user.end_adhesion }}</font>
|
<font color="green">jusqu'au {{ request_user.end_adhesion|date:"d b Y" }}</font>
|
||||||
{% else %}
|
{% else %}
|
||||||
<font color="red">Non adhérent</font>
|
<font color="red">Non adhérent</font>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
19
templates/cookie_banner.html
Normal file
19
templates/cookie_banner.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{% if not 'accept_cookies' in request.COOKIES%}
|
||||||
|
<script>
|
||||||
|
function accept_cookie() {
|
||||||
|
var d = new Date();
|
||||||
|
var expiration_time = 7 * 24 * 60 * 60 * 1000; // Accepte les cookies pendant 7 jours.
|
||||||
|
d.setTime(d.getTime() + expiration_time);
|
||||||
|
var expires = "expires="+ d.toUTCString();
|
||||||
|
document.cookie = "accept_cookies=1;" + expires + ";path=/";
|
||||||
|
var banner = document.getElementById("cookie_banner");
|
||||||
|
banner.parentNode.removeChild(banner);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div class="navbar text-center" id="cookie_banner">
|
||||||
|
<p>Ce site utilise des cookies. En poursuivant sur ce site j'accepte l'utilisation des cookies sur ce site.</p>
|
||||||
|
<a class="btn btn-primary btn-sm" role="button" onclick="accept_cookie();" title="Accepter">
|
||||||
|
J'ai compris !
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
|
@ -140,7 +140,7 @@ class EditRoomForm(ModelForm):
|
||||||
super(EditRoomForm, self).__init__(*args, prefix=prefix, **kwargs)
|
super(EditRoomForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class CreatePortsForm(Form):
|
class CreatePortsForm(forms.Form):
|
||||||
"""Permet de créer une liste de ports pour un switch."""
|
"""Permet de créer une liste de ports pour un switch."""
|
||||||
begin = forms.IntegerField(label="Début :", min_value=0)
|
begin = forms.IntegerField(label="Début :", min_value=0)
|
||||||
end = forms.IntegerField(label="Fin :", min_value=0)
|
end = forms.IntegerField(label="Fin :", min_value=0)
|
||||||
|
|
|
@ -160,6 +160,7 @@ class Switch(models.Model):
|
||||||
|
|
||||||
class ModelSwitch(models.Model):
|
class ModelSwitch(models.Model):
|
||||||
"""Un modèle (au sens constructeur) de switch"""
|
"""Un modèle (au sens constructeur) de switch"""
|
||||||
|
PRETTY_NAME = "Modèle de switch"
|
||||||
reference = models.CharField(max_length=255)
|
reference = models.CharField(max_length=255)
|
||||||
constructor = models.ForeignKey(
|
constructor = models.ForeignKey(
|
||||||
'topologie.ConstructorSwitch',
|
'topologie.ConstructorSwitch',
|
||||||
|
@ -172,6 +173,7 @@ class ModelSwitch(models.Model):
|
||||||
|
|
||||||
class ConstructorSwitch(models.Model):
|
class ConstructorSwitch(models.Model):
|
||||||
"""Un constructeur de switch"""
|
"""Un constructeur de switch"""
|
||||||
|
PRETTY_NAME = "Constructeur de switch"
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -182,7 +182,7 @@ class User(AbstractBaseUser):
|
||||||
""" Definition de l'utilisateur de base.
|
""" Definition de l'utilisateur de base.
|
||||||
Champs principaux : name, surnname, pseudo, email, room, password
|
Champs principaux : name, surnname, pseudo, email, room, password
|
||||||
Herite du django BaseUser et du système d'auth django"""
|
Herite du django BaseUser et du système d'auth django"""
|
||||||
PRETTY_NAME = "Utilisateurs"
|
PRETTY_NAME = "Utilisateurs (clubs et adhérents)"
|
||||||
STATE_ACTIVE = 0
|
STATE_ACTIVE = 0
|
||||||
STATE_DISABLED = 1
|
STATE_DISABLED = 1
|
||||||
STATE_ARCHIVE = 2
|
STATE_ARCHIVE = 2
|
||||||
|
@ -255,7 +255,7 @@ class User(AbstractBaseUser):
|
||||||
def class_name(self):
|
def class_name(self):
|
||||||
"""Renvoie si il s'agit d'un adhérent ou d'un club"""
|
"""Renvoie si il s'agit d'un adhérent ou d'un club"""
|
||||||
if hasattr(self, 'adherent'):
|
if hasattr(self, 'adherent'):
|
||||||
return "Adhérent"
|
return "Adherent"
|
||||||
elif hasattr(self, 'club'):
|
elif hasattr(self, 'club'):
|
||||||
return "Club"
|
return "Club"
|
||||||
else:
|
else:
|
||||||
|
@ -378,6 +378,22 @@ class User(AbstractBaseUser):
|
||||||
user=self
|
user=self
|
||||||
).exclude(valid=False)
|
).exclude(valid=False)
|
||||||
)
|
)
|
||||||
|
).filter(
|
||||||
|
Q(type_cotisation='All') | Q(type_cotisation='Adhesion')
|
||||||
|
).aggregate(models.Max('date_end'))['date_end__max']
|
||||||
|
return date_max
|
||||||
|
|
||||||
|
def end_connexion(self):
|
||||||
|
""" Renvoie la date de fin de connexion d'un user. Examine les objets
|
||||||
|
cotisation"""
|
||||||
|
date_max = Cotisation.objects.filter(
|
||||||
|
vente__in=Vente.objects.filter(
|
||||||
|
facture__in=Facture.objects.filter(
|
||||||
|
user=self
|
||||||
|
).exclude(valid=False)
|
||||||
|
)
|
||||||
|
).filter(
|
||||||
|
Q(type_cotisation='All') | Q(type_cotisation='Connexion')
|
||||||
).aggregate(models.Max('date_end'))['date_end__max']
|
).aggregate(models.Max('date_end'))['date_end__max']
|
||||||
return date_max
|
return date_max
|
||||||
|
|
||||||
|
@ -392,6 +408,17 @@ class User(AbstractBaseUser):
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def is_connected(self):
|
||||||
|
""" Renvoie True si l'user est adhérent : si
|
||||||
|
self.end_adhesion()>now et end_connexion>now"""
|
||||||
|
end = self.end_connexion()
|
||||||
|
if not end:
|
||||||
|
return False
|
||||||
|
elif end < DT_NOW:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return self.is_adherent()
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def end_ban(self):
|
def end_ban(self):
|
||||||
""" Renvoie la date de fin de ban d'un user, False sinon """
|
""" Renvoie la date de fin de ban d'un user, False sinon """
|
||||||
|
@ -433,20 +460,20 @@ class User(AbstractBaseUser):
|
||||||
def has_access(self):
|
def has_access(self):
|
||||||
""" Renvoie si un utilisateur a accès à internet """
|
""" Renvoie si un utilisateur a accès à internet """
|
||||||
return self.state == User.STATE_ACTIVE\
|
return self.state == User.STATE_ACTIVE\
|
||||||
and not self.is_ban and (self.is_adherent() or self.is_whitelisted)
|
and not self.is_ban and (self.is_connected() or self.is_whitelisted)
|
||||||
|
|
||||||
def end_access(self):
|
def end_access(self):
|
||||||
""" Renvoie la date de fin normale d'accès (adhésion ou whiteliste)"""
|
""" Renvoie la date de fin normale d'accès (adhésion ou whiteliste)"""
|
||||||
if not self.end_adhesion():
|
if not self.end_connexion():
|
||||||
if not self.end_whitelist:
|
if not self.end_whitelist:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return self.end_whitelist
|
return self.end_whitelist
|
||||||
else:
|
else:
|
||||||
if not self.end_whitelist:
|
if not self.end_whitelist:
|
||||||
return self.end_adhesion()
|
return self.end_connexion()
|
||||||
else:
|
else:
|
||||||
return max(self.end_adhesion(), self.end_whitelist)
|
return max(self.end_connexion(), self.end_whitelist)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def solde(self):
|
def solde(self):
|
||||||
|
@ -545,12 +572,16 @@ class User(AbstractBaseUser):
|
||||||
mail, password, shell, home
|
mail, password, shell, home
|
||||||
access_refresh : synchronise le dialup_access notant si l'user a accès
|
access_refresh : synchronise le dialup_access notant si l'user a accès
|
||||||
aux services
|
aux services
|
||||||
mac_refresh : synchronise les machines de l'user"""
|
mac_refresh : synchronise les machines de l'user
|
||||||
|
Si l'instance n'existe pas, on crée le ldapuser correspondant"""
|
||||||
self.refresh_from_db()
|
self.refresh_from_db()
|
||||||
try:
|
try:
|
||||||
user_ldap = LdapUser.objects.get(uidNumber=self.uid_number)
|
user_ldap = LdapUser.objects.get(uidNumber=self.uid_number)
|
||||||
except LdapUser.DoesNotExist:
|
except LdapUser.DoesNotExist:
|
||||||
user_ldap = LdapUser(uidNumber=self.uid_number)
|
user_ldap = LdapUser(uidNumber=self.uid_number)
|
||||||
|
base = True
|
||||||
|
access_refresh = True
|
||||||
|
mac_refresh = True
|
||||||
if base:
|
if base:
|
||||||
user_ldap.name = self.pseudo
|
user_ldap.name = self.pseudo
|
||||||
user_ldap.sn = self.pseudo
|
user_ldap.sn = self.pseudo
|
||||||
|
@ -574,7 +605,6 @@ class User(AbstractBaseUser):
|
||||||
user_ldap.macs = [str(mac) for mac in Interface.objects.filter(
|
user_ldap.macs = [str(mac) for mac in Interface.objects.filter(
|
||||||
machine__user=self
|
machine__user=self
|
||||||
).values_list('mac_address', flat=True).distinct()]
|
).values_list('mac_address', flat=True).distinct()]
|
||||||
|
|
||||||
user_ldap.save()
|
user_ldap.save()
|
||||||
|
|
||||||
def ldap_del(self):
|
def ldap_del(self):
|
||||||
|
@ -730,6 +760,7 @@ class User(AbstractBaseUser):
|
||||||
|
|
||||||
|
|
||||||
class Adherent(User):
|
class Adherent(User):
|
||||||
|
PRETTY_NAME = "Adhérents"
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
room = models.OneToOneField(
|
room = models.OneToOneField(
|
||||||
'topologie.Room',
|
'topologie.Room',
|
||||||
|
@ -741,6 +772,7 @@ class Adherent(User):
|
||||||
|
|
||||||
|
|
||||||
class Club(User):
|
class Club(User):
|
||||||
|
PRETTY_NAME = "Clubs"
|
||||||
room = models.ForeignKey(
|
room = models.ForeignKey(
|
||||||
'topologie.Room',
|
'topologie.Room',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
|
@ -750,6 +782,8 @@ class Club(User):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=Adherent)
|
||||||
|
@receiver(post_save, sender=Club)
|
||||||
@receiver(post_save, sender=User)
|
@receiver(post_save, sender=User)
|
||||||
def user_post_save(sender, **kwargs):
|
def user_post_save(sender, **kwargs):
|
||||||
""" Synchronisation post_save : envoie le mail de bienvenue si creation
|
""" Synchronisation post_save : envoie le mail de bienvenue si creation
|
||||||
|
@ -762,6 +796,8 @@ def user_post_save(sender, **kwargs):
|
||||||
regen('mailing')
|
regen('mailing')
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_delete, sender=Adherent)
|
||||||
|
@receiver(post_delete, sender=Club)
|
||||||
@receiver(post_delete, sender=User)
|
@receiver(post_delete, sender=User)
|
||||||
def user_post_delete(sender, **kwargs):
|
def user_post_delete(sender, **kwargs):
|
||||||
"""Post delete d'un user, on supprime son instance ldap"""
|
"""Post delete d'un user, on supprime son instance ldap"""
|
||||||
|
|
|
@ -119,7 +119,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<th>Accès internet</th>
|
<th>Accès internet</th>
|
||||||
{% if user.has_access == True %}
|
{% if user.has_access == True %}
|
||||||
<td><font color="green">Actif</font></td>
|
<td><font color="green">Actif (jusqu'au {{ user.end_access }})</font></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td><font color="red">Désactivé</font></td>
|
<td><font color="red">Désactivé</font></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
Loading…
Reference in a new issue