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

Merge branch 'master' into reverse_url

This commit is contained in:
root 2017-11-04 19:52:08 +01:00
commit 5dc44a7136
19 changed files with 458 additions and 74 deletions

View file

@ -38,6 +38,7 @@ ArticleForm, BanqueForm, PaiementForm permettent aux admin d'ajouter,
from __future__ import unicode_literals
from django import forms
from django.db.models import Q
from django.forms import ModelForm, Form
from django.core.validators import MinValueValidator
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)
class SelectArticleForm(Form):
class SelectUserArticleForm(Form):
"""Selection d'un article lors de la creation d'une facture"""
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",
required=True
)

View 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),
),
]

View 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',
),
]

View 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),
),
]

View file

@ -47,6 +47,7 @@ from __future__ import unicode_literals
from dateutil.relativedelta import relativedelta
from django.db import models
from django.db.models import Q
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.forms import ValidationError
@ -127,15 +128,26 @@ class Vente(models.Model):
iscotisation"""
PRETTY_NAME = "Ventes effectuées"
COTISATION_TYPE = (
('Connexion', 'Connexion'),
('Adhesion', 'Adhesion'),
('All', 'All'),
)
facture = models.ForeignKey('Facture', on_delete=models.CASCADE)
number = models.IntegerField(validators=[MinValueValidator(1)])
name = models.CharField(max_length=255)
prix = models.DecimalField(max_digits=5, decimal_places=2)
iscotisation = models.BooleanField()
duration = models.PositiveIntegerField(
help_text="Durée exprimée en mois entiers",
blank=True,
null=True)
type_cotisation = models.CharField(
choices=COTISATION_TYPE,
blank=True,
null=True,
max_length=255
)
def prix_total(self):
"""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
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') and self.type_cotisation:
cotisation = Cotisation(vente=self)
cotisation.type_cotisation = self.type_cotisation
if date_start:
end_adhesion = Cotisation.objects.filter(
end_cotisation = Cotisation.objects.filter(
vente__in=Vente.objects.filter(
facture__in=Facture.objects.filter(
user=self.facture.user
).exclude(valid=False))
).filter(Q(type_cotisation='All') | Q(type_cotisation=self.type_cotisation)
).filter(
date_start__lt=date_start
).aggregate(Max('date_end'))['date_end__max']
elif self.type_cotisation=="Adhesion":
end_cotisation = self.facture.user.end_adhesion()
else:
end_adhesion = self.facture.user.end_adhesion()
end_cotisation = self.facture.user.end_connexion()
date_start = date_start or timezone.now()
end_adhesion = end_adhesion or date_start
date_max = max(end_adhesion, date_start)
end_cotisation = end_cotisation or date_start
date_max = max(end_cotisation, date_start)
cotisation.date_start = date_max
cotisation.date_end = cotisation.date_start + relativedelta(
months=self.duration*self.number
@ -179,7 +195,7 @@ class Vente(models.Model):
def save(self, *args, **kwargs):
# On verifie que si iscotisation, duration est présent
if self.iscotisation and not self.duration:
if self.type_cotisation and not self.duration:
raise ValidationError("Cotisation et durée doivent être présents\
ensembles")
self.update_cotisation()
@ -197,7 +213,7 @@ def vente_post_save(sender, **kwargs):
if hasattr(vente, 'cotisation'):
vente.cotisation.vente = vente
vente.cotisation.save()
if vente.iscotisation:
if vente.type_cotisation:
vente.create_cotis()
vente.cotisation.save()
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
suppression d'une cotisation"""
vente = kwargs['instance']
if vente.iscotisation:
if vente.type_cotisation:
user = vente.facture.user
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"""
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)
iscotisation = models.BooleanField()
duration = models.PositiveIntegerField(
help_text="Durée exprimée en mois entiers",
blank=True,
null=True,
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):
if self.name.lower() == "solde":
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):
return self.name
@ -275,7 +320,17 @@ class Cotisation(models.Model):
"""Objet cotisation, debut et fin, relié en onetoone à une vente"""
PRETTY_NAME = "Cotisations"
COTISATION_TYPE = (
('Connexion', 'Connexion'),
('Adhesion', 'Adhesion'),
('All', 'All'),
)
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_end = models.DateTimeField()

View file

@ -27,8 +27,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<tr>
<th>Article</th>
<th>Prix</th>
<th>Cotisation</th>
<th>Type Cotisation</th>
<th>Durée (mois)</th>
<th>Article pour</th>
<th></th>
</tr>
</thead>
@ -36,8 +37,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<tr>
<td>{{ article.name }}</td>
<td>{{ article.prix }}</td>
<td>{{ article.iscotisation }}</td>
<td>{{ article.type_cotisation }}</td>
<td>{{ article.duration }}</td>
<td>{{ article.type_user }}</td>
<td class="text-right">
{% if is_trez %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'cotisations:edit-article' article.id %}">

View file

@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<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='date' text='Date' %}</th>
<th></th>
<th>{% include "buttons/sort.html" with prefix='cotis' col='id' text='Id facture' %}</th>
<th></th>
<th></th>
</tr>
@ -46,17 +46,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ facture.prix_total }}</td>
<td>{{ facture.paiement }}</td>
<td>{{ facture.date }}</td>
<td>{{ facture.id }}</td>
{% if is_cableur %}
<td>
<div class="dropdown">
<button class="btn btn-default dropdown-toggle" type="button" id="editionfacture" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
Modifier
Edition
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="editionfacture">
{% 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:history' 'facture' facture.id %}"><i class="glyphicon glyphicon-time"></i> Historique</a></li>
{% else %}
<li>Facture controlée</li>
{% endif %}
@ -74,11 +76,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<font color="red">Facture invalide</font>
{% endif %}
</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>
{% endfor %}
</table>

View file

@ -42,7 +42,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<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='surname' text='Prénom' %}</th>
<th>Designation</th>
<th>{% include "buttons/sort.html" with prefix='control' col='id' text='Id facture' %}</th>
<th>{% include "buttons/sort.html" with prefix='control' col='user-id' text='Id user' %}</th>
<th>Designation</th>
<th>Prix total</th>
<th>{% include "buttons/sort.html" with prefix='control' col='paiement' text='Moyen de paiement' %}</th>
<th>{% include "buttons/sort.html" with prefix='control' col='date' text='Date' %}</th>
@ -58,7 +60,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</td>
<td>{{ form.instance.user.name }}</td>
<td>{{ form.instance.user.surname }}</td>
<td>{{ form.instance.name }}</td>
<td>{{ form.instance.id }}</td>
<td>{{ form.instance.user.id }}</td>
<td>{{ form.instance.name }}</td>
<td>{{ form.instance.prix_total }}</td>
<td>{{ form.instance.paiement }}</td>
<td>{{ form.instance.date }}</td>

View file

@ -33,6 +33,7 @@ from django.contrib.auth.decorators import login_required, permission_required
from django.contrib import messages
from django.db.models import ProtectedError
from django.db import transaction
from django.db.models import Q
from django.forms import modelformset_factory, formset_factory
from django.utils import timezone
from reversion import revisions as reversion
@ -45,10 +46,21 @@ from re2o.views import form
from re2o.utils import SortTable
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 .forms import (
NewFactureForm,
TrezEditFactureForm,
EditFactureForm,
ArticleForm,
DelArticleForm,
PaiementForm,
DelPaiementForm,
BanqueForm,
DelBanqueForm,
NewFactureFormPdf,
SelectUserArticleForm,
SelectClubArticleForm,
CreditSoldeForm
)
from .tex import render_tex
@ -69,10 +81,15 @@ def new_facture(request, userid):
return redirect(reverse('cotisations:index'))
facture = Facture(user=user)
# 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
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():
new_facture_instance = facture_form.save(commit=False)
articles = article_formset
@ -111,7 +128,7 @@ def new_facture(request, userid):
facture=new_facture_instance,
name=article.name,
prix=article.prix,
iscotisation=article.iscotisation,
type_cotisation=article.type_cotisation,
duration=article.duration,
number=quantity
)
@ -119,7 +136,7 @@ def new_facture(request, userid):
new_vente.save()
reversion.set_user(request.user)
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):
messages.success(
request,
@ -326,8 +343,6 @@ def credit_solde(request, userid):
facture=facture_instance,
name="solde",
prix=facture.cleaned_data['montant'],
iscotisation=False,
duration=0,
number=1
)
with transaction.atomic(), reversion.create_revision():

View file

@ -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...", '')
elif not interface.is_active:
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:
return (True, u"Access ok", user.pwd_ntlm)
elif nas_type:
@ -324,9 +327,14 @@ def decide_vlan_and_register_switch(nas, nas_type, port_number, mac_address):
return (sw_name, u'Access Ok, Capture de la mac...' + extra_log, DECISION_VLAN)
else:
return (sw_name, u'Erreur dans le register mac %s' % reason + unicode(mac_address), VLAN_NOK)
elif not interface.first().is_active:
return (sw_name, u'Machine non active / adherent non cotisant', VLAN_NOK)
else:
return (sw_name, u'Machine OK' + extra_log, DECISION_VLAN)
interface = interface.first()
if not interface.is_active:
return (sw_name, u'Machine non active / adherent non cotisant', VLAN_NOK)
elif not interface.ipv4:
interface.assign_ipv4()
return (sw_name, u"Ok, Reassignation de l'ipv4" + extra_log, DECISION_VLAN)
else:
return (sw_name, u'Machine OK' + extra_log, DECISION_VLAN)

View file

@ -47,13 +47,50 @@ from django.db.models import Count
from reversion.models import Revision
from reversion.models import Version, ContentType
from users.models import User, ServiceUser, Right, School, ListRight, ListShell
from users.models import Ban, Whitelist
from cotisations.models import Facture, Vente, Article, Banque, Paiement
from cotisations.models import Cotisation
from machines.models import Machine, MachineType, IpType, Extension, Interface
from machines.models import Domain, IpList
from topologie.models import Switch, Port, Room
from users.models import (
User,
ServiceUser,
Right,
School,
ListRight,
ListShell,
Ban,
Whitelist,
Adherent,
Club
)
from cotisations.models import (
Facture,
Vente,
Article,
Banque,
Paiement,
Cotisation
)
from machines.models import (
Machine,
MachineType,
IpType,
Extension,
Interface,
Domain,
IpList,
OuverturePortList,
Service,
Vlan,
Nas,
SOA,
Mx,
Ns
)
from topologie.models import (
Switch,
Port,
Room,
Stack,
ModelSwitch,
ConstructorSwitch
)
from preferences.models import GeneralOption
from re2o.views import form
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,
cotisants, activés, archivés, etc"""
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)
used_ip = Interface.objects.filter(ipv4__in=all_ip).count()
active_ip = all_active_assigned_interfaces_count().filter(
ipv4__in=IpList.objects.filter(ip_type=ip_range)
).count()
ip_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]
_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 = [
[["Categorie", "Nombre d'utilisateurs"], {
[["Categorie", "Nombre d'utilisateurs (total club et adhérents)", "Nombre d'adhérents", "Nombre de clubs"], {
'active_users': [
"Users actifs",
User.objects.filter(state=User.STATE_ACTIVE).count()],
'inactive_users': [
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': [
"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': [
"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': [
"Adhérents à l'association",
all_adherent().count()],
"Cotisant à l'association",
_all_adherent.count(),
_all_adherent.exclude(adherent__isnull=True).count(),
_all_adherent.exclude(club__isnull=True).count()],
'connexion_users': [
"Utilisateurs bénéficiant d'une connexion",
all_has_access().count()],
_all_has_access.count(),
_all_has_access.exclude(adherent__isnull=True).count(),
_all_has_access.exclude(club__isnull=True).count()],
'ban_users': [
"Utilisateurs bannis",
all_baned().count()],
_all_baned.count(),
_all_baned.exclude(adherent__isnull=True).count(),
_all_baned.exclude(club__isnull=True).count()],
'whitelisted_user': [
"Utilisateurs bénéficiant d'une connexion gracieuse",
all_whitelisted().count()],
_all_whitelisted.count(),
_all_whitelisted.exclude(adherent__isnull=True).count(),
_all_whitelisted.exclude(club__isnull=True).count()],
'actives_interfaces': [
"Interfaces actives (ayant accès au reseau)",
all_active_interfaces_count().count()],
_all_active_interfaces_count.count(),
_all_active_interfaces_count.exclude(
machine__user__adherent__isnull=True
).count(),
_all_active_interfaces_count.exclude(
machine__user__club__isnull=True
).count()],
'actives_assigned_interfaces': [
"Interfaces actives et assignées ipv4",
all_active_assigned_interfaces_count().count()]
_all_active_assigned_interfaces_count.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]
]
return render(request, 'logs/stats_general.html', {'stats_list': stats})
@ -237,6 +306,8 @@ def stats_models(request):
stats = {
'Users': {
'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.objects.count()],
'right': [Right.PRETTY_NAME, Right.objects.count()],
@ -263,11 +334,30 @@ def stats_models(request):
'alias': [Domain.PRETTY_NAME,
Domain.objects.exclude(cname=None).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': {
'switch': [Switch.PRETTY_NAME, Switch.objects.count()],
'port': [Port.PRETTY_NAME, Port.objects.count()],
'chambre': [Room.PRETTY_NAME, Room.objects.count()],
'stack': [Stack.PRETTY_NAME, Stack.objects.count()],
'modelswitch': [
ModelSwitch.PRETTY_NAME,
ModelSwitch.objects.count()
],
'constructorswitch': [
ConstructorSwitch.PRETTY_NAME,
ConstructorSwitch.objects.count()
],
},
'Actions effectuées sur la base':
{

View file

@ -460,6 +460,15 @@ class Interface(models.Model):
def clean(self, *args, **kwargs):
""" Formate l'addresse mac en mac_bare (fonction filter_mac)
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.mac_address = str(EUI(self.mac_address)) or None
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):
""" 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)
min_time_regen = models.DurationField(
default=timedelta(minutes=1),
@ -673,6 +684,8 @@ def regen(service):
class Service_link(models.Model):
""" Definition du lien entre serveurs et services"""
PRETTY_NAME = "Relation entre service et serveur"
service = models.ForeignKey('Service', on_delete=models.CASCADE)
server = models.ForeignKey('Interface', on_delete=models.CASCADE)
last_regen = models.DateTimeField(auto_now_add=True)
@ -702,6 +715,8 @@ class Service_link(models.Model):
class OuverturePortList(models.Model):
"""Liste des ports ouverts sur une interface."""
PRETTY_NAME = "Profil d'ouverture de ports"
name = models.CharField(
help_text="Nom de la configuration des ports.",
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
"""
PRETTY_NAME = "Plage de port ouverte"
TCP = 'T'
UDP = 'U'
IN = 'I'

View file

@ -56,6 +56,7 @@ def all_adherent(search_time=DT_NOW):
return User.objects.filter(
facture__in=Facture.objects.filter(
vente__in=Vente.objects.filter(
Q(type_cotisation='All') | Q(type_cotisation='Adhesion'),
cotisation__in=Cotisation.objects.filter(
vente__in=Vente.objects.filter(
facture__in=Facture.objects.all().exclude(valid=False)
@ -94,6 +95,7 @@ def all_has_access(search_time=DT_NOW):
Q(facture__in=Facture.objects.filter(
vente__in=Vente.objects.filter(
cotisation__in=Cotisation.objects.filter(
Q(type_cotisation='All') | Q(type_cotisation='Connexion'),
vente__in=Vente.objects.filter(
facture__in=Facture.objects.all()
.exclude(valid=False)
@ -179,15 +181,18 @@ class SortTable:
'cotis_user': ['user__pseudo'],
'cotis_paiement': ['paiement__moyen'],
'cotis_date': ['date'],
'cotis_id': ['id'],
'default': ['-date']
}
COTISATIONS_CONTROL = {
'control_name': ['user__name'],
'control_name': ['user__adherent__name'],
'control_surname': ['user__surname'],
'control_paiement': ['paiement'],
'control_date': ['date'],
'control_valid': ['valid'],
'control_control': ['control'],
'control_id': ['id'],
'control_user-id': ['user__id'],
'default': ['-date']
}
TOPOLOGIE_INDEX = {

View file

@ -49,6 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</head>
<body>
{% include "cookie_banner.html" %}
<div id="wrap">
<nav class="navbar navbar-inverse">
<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>
<td class="text-right">
{% 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 %}
<font color="red">Désactivée</font>
{% endif %}
@ -155,8 +156,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<tr>
<th scope="row">Adhésion</th>
<td class="text-right">
{% if request_user.end_adhesion != None %}
<font color="green">{{ request_user.end_adhesion }}</font>
{% if request_user.is_adherent %}
<font color="green">jusqu'au {{ request_user.end_adhesion|date:"d b Y" }}</font>
{% else %}
<font color="red">Non adhérent</font>
{% endif %}

View 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 %}

View file

@ -140,7 +140,7 @@ class EditRoomForm(ModelForm):
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."""
begin = forms.IntegerField(label="Début :", min_value=0)
end = forms.IntegerField(label="Fin :", min_value=0)

View file

@ -160,6 +160,7 @@ class Switch(models.Model):
class ModelSwitch(models.Model):
"""Un modèle (au sens constructeur) de switch"""
PRETTY_NAME = "Modèle de switch"
reference = models.CharField(max_length=255)
constructor = models.ForeignKey(
'topologie.ConstructorSwitch',
@ -172,6 +173,7 @@ class ModelSwitch(models.Model):
class ConstructorSwitch(models.Model):
"""Un constructeur de switch"""
PRETTY_NAME = "Constructeur de switch"
name = models.CharField(max_length=255)
def __str__(self):

View file

@ -182,7 +182,7 @@ class User(AbstractBaseUser):
""" Definition de l'utilisateur de base.
Champs principaux : name, surnname, pseudo, email, room, password
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_DISABLED = 1
STATE_ARCHIVE = 2
@ -255,7 +255,7 @@ class User(AbstractBaseUser):
def class_name(self):
"""Renvoie si il s'agit d'un adhérent ou d'un club"""
if hasattr(self, 'adherent'):
return "Adhérent"
return "Adherent"
elif hasattr(self, 'club'):
return "Club"
else:
@ -378,6 +378,22 @@ class User(AbstractBaseUser):
user=self
).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']
return date_max
@ -392,6 +408,17 @@ class User(AbstractBaseUser):
else:
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
def end_ban(self):
""" Renvoie la date de fin de ban d'un user, False sinon """
@ -433,20 +460,20 @@ class User(AbstractBaseUser):
def has_access(self):
""" Renvoie si un utilisateur a accès à internet """
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):
""" 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:
return None
else:
return self.end_whitelist
else:
if not self.end_whitelist:
return self.end_adhesion()
return self.end_connexion()
else:
return max(self.end_adhesion(), self.end_whitelist)
return max(self.end_connexion(), self.end_whitelist)
@cached_property
def solde(self):
@ -545,12 +572,16 @@ class User(AbstractBaseUser):
mail, password, shell, home
access_refresh : synchronise le dialup_access notant si l'user a accès
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()
try:
user_ldap = LdapUser.objects.get(uidNumber=self.uid_number)
except LdapUser.DoesNotExist:
user_ldap = LdapUser(uidNumber=self.uid_number)
base = True
access_refresh = True
mac_refresh = True
if base:
user_ldap.name = 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(
machine__user=self
).values_list('mac_address', flat=True).distinct()]
user_ldap.save()
def ldap_del(self):
@ -730,6 +760,7 @@ class User(AbstractBaseUser):
class Adherent(User):
PRETTY_NAME = "Adhérents"
name = models.CharField(max_length=255)
room = models.OneToOneField(
'topologie.Room',
@ -741,6 +772,7 @@ class Adherent(User):
class Club(User):
PRETTY_NAME = "Clubs"
room = models.ForeignKey(
'topologie.Room',
on_delete=models.PROTECT,
@ -750,6 +782,8 @@ class Club(User):
pass
@receiver(post_save, sender=Adherent)
@receiver(post_save, sender=Club)
@receiver(post_save, sender=User)
def user_post_save(sender, **kwargs):
""" Synchronisation post_save : envoie le mail de bienvenue si creation
@ -762,6 +796,8 @@ def user_post_save(sender, **kwargs):
regen('mailing')
@receiver(post_delete, sender=Adherent)
@receiver(post_delete, sender=Club)
@receiver(post_delete, sender=User)
def user_post_delete(sender, **kwargs):
"""Post delete d'un user, on supprime son instance ldap"""

View file

@ -119,7 +119,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<tr>
<th>Accès internet</th>
{% 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 %}
<td><font color="red">Désactivé</font></td>
{% endif %}