8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-12-23 15:33:45 +00:00

Merge branch 'master' into documentation

This commit is contained in:
Hugo LEVY-FALK 2018-03-22 10:19:23 +01:00
commit 309cc60ef1
190 changed files with 9749 additions and 2660 deletions

1
.gitignore vendored
View file

@ -6,3 +6,4 @@ __pycache__/*
static_files/*
static/logo/*
doc/build/
media/*

View file

@ -171,10 +171,9 @@ re2o/wsgi.py permet de fonctionner avec apache2 en production
Une fois démaré, le site web devrait être accessible.
Pour créer un premier user, faire '''python3 manage.py createsuperuser'''
qui va alors créer un user admin.
Il est conseillé de créer alors les droits cableur, bureau, trésorier et infra,
qui n'existent pas par défaut dans le menu adhérents.
Il est également conseillé de créer un user portant le nom de
l'association/organisation, qui possedera l'ensemble des machines.
Il est conseillé de créer un user portant le nom de
l'association/organisation, qui possedera l'ensemble des machines, à indiquer
dans le menu reglages sur l'interface.
## Installations Optionnelles
### Générer le schéma des dépendances

360
api/serializers.py Normal file
View file

@ -0,0 +1,360 @@
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2018 Mael Kervella
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""
Serializers for the API app
"""
from rest_framework import serializers
from users.models import Club, Adherent
from machines.models import (
Interface,
IpType,
Extension,
IpList,
MachineType,
Domain,
Txt,
Mx,
Srv,
Service_link,
Ns,
OuverturePortList,
OuverturePort,
Ipv6List
)
class ServiceLinkSerializer(serializers.ModelSerializer):
name = serializers.CharField(source='service.service_type')
class Meta:
model = Service_link
fields = ('name',)
class MailingSerializer(serializers.ModelSerializer):
name = serializers.CharField(source='pseudo')
class Meta:
model = Club
fields = ('name',)
class MailingMemberSerializer(serializers.ModelSerializer):
class Meta:
model = Adherent
fields = ('email', 'name', 'surname', 'pseudo',)
class IpTypeField(serializers.RelatedField):
"""Serialisation d'une iptype, renvoie son evaluation str"""
def to_representation(self, value):
return value.type
class IpListSerializer(serializers.ModelSerializer):
"""Serialisation d'une iplist, ip_type etant une foreign_key,
on evalue sa methode str"""
ip_type = IpTypeField(read_only=True)
class Meta:
model = IpList
fields = ('ipv4', 'ip_type')
class Ipv6ListSerializer(serializers.ModelSerializer):
class Meta:
model = Ipv6List
fields = ('ipv6', 'slaac_ip')
class InterfaceSerializer(serializers.ModelSerializer):
"""Serialisation d'une interface, ipv4, domain et extension sont
des foreign_key, on les override et on les evalue avec des fonctions
get_..."""
ipv4 = IpListSerializer(read_only=True)
mac_address = serializers.SerializerMethodField('get_macaddress')
domain = serializers.SerializerMethodField('get_dns')
extension = serializers.SerializerMethodField('get_interface_extension')
class Meta:
model = Interface
fields = ('ipv4', 'mac_address', 'domain', 'extension')
def get_dns(self, obj):
return obj.domain.name
def get_interface_extension(self, obj):
return obj.domain.extension.name
def get_macaddress(self, obj):
return str(obj.mac_address)
class FullInterfaceSerializer(serializers.ModelSerializer):
"""Serialisation complete d'une interface avec les ipv6 en plus"""
ipv4 = IpListSerializer(read_only=True)
ipv6 = Ipv6ListSerializer(read_only=True, many=True)
mac_address = serializers.SerializerMethodField('get_macaddress')
domain = serializers.SerializerMethodField('get_dns')
extension = serializers.SerializerMethodField('get_interface_extension')
class Meta:
model = Interface
fields = ('ipv4', 'ipv6', 'mac_address', 'domain', 'extension')
def get_dns(self, obj):
return obj.domain.name
def get_interface_extension(self, obj):
return obj.domain.extension.name
def get_macaddress(self, obj):
return str(obj.mac_address)
class ExtensionNameField(serializers.RelatedField):
"""Evaluation str d'un objet extension (.example.org)"""
def to_representation(self, value):
return value.name
class TypeSerializer(serializers.ModelSerializer):
"""Serialisation d'un iptype : extension et la liste des
ouvertures de port son evalués en get_... etant des
foreign_key ou des relations manytomany"""
extension = ExtensionNameField(read_only=True)
ouverture_ports_tcp_in = serializers\
.SerializerMethodField('get_port_policy_input_tcp')
ouverture_ports_tcp_out = serializers\
.SerializerMethodField('get_port_policy_output_tcp')
ouverture_ports_udp_in = serializers\
.SerializerMethodField('get_port_policy_input_udp')
ouverture_ports_udp_out = serializers\
.SerializerMethodField('get_port_policy_output_udp')
class Meta:
model = IpType
fields = ('type', 'extension', 'domaine_ip_start', 'domaine_ip_stop',
'prefix_v6',
'ouverture_ports_tcp_in', 'ouverture_ports_tcp_out',
'ouverture_ports_udp_in', 'ouverture_ports_udp_out',)
def get_port_policy(self, obj, protocole, io):
if obj.ouverture_ports is None:
return []
return map(
str,
obj.ouverture_ports.ouvertureport_set.filter(
protocole=protocole
).filter(io=io)
)
def get_port_policy_input_tcp(self, obj):
"""Renvoie la liste des ports ouverts en entrée tcp"""
return self.get_port_policy(obj, OuverturePort.TCP, OuverturePort.IN)
def get_port_policy_output_tcp(self, obj):
"""Renvoie la liste des ports ouverts en sortie tcp"""
return self.get_port_policy(obj, OuverturePort.TCP, OuverturePort.OUT)
def get_port_policy_input_udp(self, obj):
"""Renvoie la liste des ports ouverts en entrée udp"""
return self.get_port_policy(obj, OuverturePort.UDP, OuverturePort.IN)
def get_port_policy_output_udp(self, obj):
"""Renvoie la liste des ports ouverts en sortie udp"""
return self.get_port_policy(obj, OuverturePort.UDP, OuverturePort.OUT)
class ExtensionSerializer(serializers.ModelSerializer):
"""Serialisation d'une extension : origin_ip et la zone sont
des foreign_key donc evalués en get_..."""
origin = serializers.SerializerMethodField('get_origin_ip')
zone_entry = serializers.SerializerMethodField('get_zone_name')
soa = serializers.SerializerMethodField('get_soa_data')
class Meta:
model = Extension
fields = ('name', 'origin', 'origin_v6', 'zone_entry', 'soa')
def get_origin_ip(self, obj):
return obj.origin.ipv4
def get_zone_name(self, obj):
return str(obj.dns_entry)
def get_soa_data(self, obj):
return { 'mail': obj.soa.dns_soa_mail, 'param': obj.soa.dns_soa_param }
class MxSerializer(serializers.ModelSerializer):
"""Serialisation d'un MX, evaluation du nom, de la zone
et du serveur cible, etant des foreign_key"""
name = serializers.SerializerMethodField('get_entry_name')
zone = serializers.SerializerMethodField('get_zone_name')
mx_entry = serializers.SerializerMethodField('get_mx_name')
class Meta:
model = Mx
fields = ('zone', 'priority', 'name', 'mx_entry')
def get_entry_name(self, obj):
return str(obj.name)
def get_zone_name(self, obj):
return obj.zone.name
def get_mx_name(self, obj):
return str(obj.dns_entry)
class TxtSerializer(serializers.ModelSerializer):
"""Serialisation d'un txt : zone cible et l'entrée txt
sont evaluées à part"""
zone = serializers.SerializerMethodField('get_zone_name')
txt_entry = serializers.SerializerMethodField('get_txt_name')
class Meta:
model = Txt
fields = ('zone', 'txt_entry', 'field1', 'field2')
def get_zone_name(self, obj):
return str(obj.zone.name)
def get_txt_name(self, obj):
return str(obj.dns_entry)
class SrvSerializer(serializers.ModelSerializer):
"""Serialisation d'un srv : zone cible et l'entrée txt"""
extension = serializers.SerializerMethodField('get_extension_name')
srv_entry = serializers.SerializerMethodField('get_srv_name')
class Meta:
model = Srv
fields = (
'service',
'protocole',
'extension',
'ttl',
'priority',
'weight',
'port',
'target',
'srv_entry'
)
def get_extension_name(self, obj):
return str(obj.extension.name)
def get_srv_name(self, obj):
return str(obj.dns_entry)
class NsSerializer(serializers.ModelSerializer):
"""Serialisation d'un NS : la zone, l'entrée ns complète et le serveur
ns sont évalués à part"""
zone = serializers.SerializerMethodField('get_zone_name')
ns = serializers.SerializerMethodField('get_domain_name')
ns_entry = serializers.SerializerMethodField('get_text_name')
class Meta:
model = Ns
fields = ('zone', 'ns', 'ns_entry')
def get_zone_name(self, obj):
return obj.zone.name
def get_domain_name(self, obj):
return str(obj.ns)
def get_text_name(self, obj):
return str(obj.dns_entry)
class DomainSerializer(serializers.ModelSerializer):
"""Serialisation d'un domain, extension, cname sont des foreign_key,
et l'entrée complète, sont évalués à part"""
extension = serializers.SerializerMethodField('get_zone_name')
cname = serializers.SerializerMethodField('get_alias_name')
cname_entry = serializers.SerializerMethodField('get_cname_name')
class Meta:
model = Domain
fields = ('name', 'extension', 'cname', 'cname_entry')
def get_zone_name(self, obj):
return obj.extension.name
def get_alias_name(self, obj):
return str(obj.cname)
def get_cname_name(self, obj):
return str(obj.dns_entry)
class ServicesSerializer(serializers.ModelSerializer):
"""Evaluation d'un Service, et serialisation"""
server = serializers.SerializerMethodField('get_server_name')
service = serializers.SerializerMethodField('get_service_name')
need_regen = serializers.SerializerMethodField('get_regen_status')
class Meta:
model = Service_link
fields = ('server', 'service', 'need_regen')
def get_server_name(self, obj):
return str(obj.server.domain.name)
def get_service_name(self, obj):
return str(obj.service)
def get_regen_status(self, obj):
return obj.need_regen()
class OuverturePortsSerializer(serializers.Serializer):
"""Serialisation de l'ouverture des ports"""
ipv4 = serializers.SerializerMethodField()
ipv6 = serializers.SerializerMethodField()
def get_ipv4():
return {i.ipv4.ipv4:
{
"tcp_in":[j.tcp_ports_in() for j in i.port_lists.all()],
"tcp_out":[j.tcp_ports_out()for j in i.port_lists.all()],
"udp_in":[j.udp_ports_in() for j in i.port_lists.all()],
"udp_out":[j.udp_ports_out() for j in i.port_lists.all()],
}
for i in Interface.objects.all() if i.ipv4
}
def get_ipv6():
return {i.ipv6:
{
"tcp_in":[j.tcp_ports_in() for j in i.port_lists.all()],
"tcp_out":[j.tcp_ports_out()for j in i.port_lists.all()],
"udp_in":[j.udp_ports_in() for j in i.port_lists.all()],
"udp_out":[j.udp_ports_out() for j in i.port_lists.all()],
}
for i in Interface.objects.all() if i.ipv6
}

25
api/tests.py Normal file
View file

@ -0,0 +1,25 @@
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from django.test import TestCase
# Create your tests here.

59
api/urls.py Normal file
View file

@ -0,0 +1,59 @@
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2018 Mael Kervella
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""api.urls
Urls de l'api, pointant vers les fonctions de views
"""
from __future__ import unicode_literals
from django.conf.urls import url
from . import views
urlpatterns = [
# Services
url(r'^services/$', views.services),
url(r'^services/(?P<server_name>\w+)/(?P<service_name>\w+)/regen/$', views.services_server_service_regen),
url(r'^services/(?P<server_name>\w+)/$', views.services_server),
# DNS
url(r'^dns/mac-ip-dns/$', views.dns_mac_ip_dns),
url(r'^dns/alias/$', views.dns_alias),
url(r'^dns/corresp/$', views.dns_corresp),
url(r'^dns/mx/$', views.dns_mx),
url(r'^dns/ns/$', views.dns_ns),
url(r'^dns/txt/$', views.dns_txt),
url(r'^dns/srv/$', views.dns_srv),
url(r'^dns/zones/$', views.dns_zones),
# Firewall
url(r'^firewall/ouverture_ports/$', views.firewall_ouverture_ports),
# DHCP
url(r'^dhcp/mac-ip/$', views.dhcp_mac_ip),
# Mailings
url(r'^mailing/standard/$', views.mailing_standard),
url(r'^mailing/standard/(?P<ml_name>\w+)/members/$', views.mailing_standard_ml_members),
url(r'^mailing/club/$', views.mailing_club),
url(r'^mailing/club/(?P<ml_name>\w+)/members/$', views.mailing_club_ml_members),
]

114
api/utils.py Normal file
View file

@ -0,0 +1,114 @@
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2018 Maël Kervella
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""api.utils.
Set of various and usefull functions for the API app
"""
from rest_framework.renderers import JSONRenderer
from django.http import HttpResponse
class JSONResponse(HttpResponse):
"""A JSON response that can be send as an HTTP response.
Usefull in case of REST API.
"""
def __init__(self, data, **kwargs):
"""Initialisz a JSONResponse object.
Args:
data: the data to render as JSON (often made of lists, dicts,
strings, boolean and numbers). See `JSONRenderer.render(data)` for
further details.
Creates:
An HTTPResponse containing the data in JSON format.
"""
content = JSONRenderer().render(data)
kwargs['content_type'] = 'application/json'
super(JSONResponse, self).__init__(content, **kwargs)
class JSONError(JSONResponse):
"""A JSON response when the request failed.
"""
def __init__(self, error_msg, data=None, **kwargs):
"""Initialise a JSONError object.
Args:
error_msg: A message explaining where the error is.
data: An optional field for further data to send along.
Creates:
A JSONResponse containing a field `status` set to `error` and a field
`reason` containing `error_msg`. If `data` argument has been given,
a field `data` containing it is added to the JSON response.
"""
response = {
'status' : 'error',
'reason' : error_msg
}
if data is not None:
response['data'] = data
super(JSONError, self).__init__(response, **kwargs)
class JSONSuccess(JSONResponse):
"""A JSON response when the request suceeded.
"""
def __init__(self, data=None, **kwargs):
"""Initialise a JSONSucess object.
Args:
error_msg: A message explaining where the error is.
data: An optional field for further data to send along.
Creates:
A JSONResponse containing a field `status` set to `sucess`. If `data`
argument has been given, a field `data` containing it is added to the
JSON response.
"""
response = {
'status' : 'success',
}
if data is not None:
response['data'] = data
super(JSONSuccess, self).__init__(response, **kwargs)
def accept_method(methods):
"""Decorator to set a list of accepted request method.
Check if the method used is accepted. If not, send a NotAllowed response.
"""
def decorator(view):
def wrapper(request, *args, **kwargs):
if request.method in methods:
return view(request, *args, **kwargs)
else:
return JSONError('Invalid request method. Request methods authorize are '+str(methods))
return view(request, *args, **kwargs)
return wrapper
return decorator

464
api/views.py Normal file
View file

@ -0,0 +1,464 @@
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2018 Maël Kervella
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""api.views
The views for the API app. They should all return JSON data and not fallback on
HTML pages such as the login and index pages for a better integration.
"""
from django.contrib.auth.decorators import login_required, permission_required
from django.views.decorators.csrf import csrf_exempt
from re2o.utils import all_has_access, all_active_assigned_interfaces
from users.models import Club
from machines.models import (Service_link, Service, Interface, Domain,
OuverturePortList)
from .serializers import *
from .utils import JSONError, JSONSuccess, accept_method
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def services(request):
"""The list of the different services and servers couples
Return:
GET:
A JSONSuccess response with a field `data` containing:
* a list of dictionnaries (one for each service-server couple) containing:
* a field `server`: the server name
* a field `service`: the service name
* a field `need_regen`: does the service need a regeneration ?
"""
service_link = Service_link.objects.all().select_related('server__domain').select_related('service')
seria = ServicesSerializer(service_link, many=True)
return JSONSuccess(seria.data)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET', 'POST'])
def services_server_service_regen(request, server_name, service_name):
"""The status of a particular service linked to a particular server.
Mark the service as regenerated if POST used.
Returns:
GET:
A JSONSucess response with a field `data` containing:
* a field `need_regen`: does the service need a regeneration ?
POST:
An empty JSONSuccess response.
"""
query = Service_link.objects.filter(
service__in=Service.objects.filter(service_type=service_name),
server__in=Interface.objects.filter(
domain__in=Domain.objects.filter(name=server_name)
)
)
if not query:
return JSONError("This service is not active for this server")
service = query.first()
if request.method == 'GET':
return JSONSuccess({'need_regen': service.need_regen()})
else:
service.done_regen()
return JSONSuccess()
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def services_server(request, server_name):
"""The list of services attached to a specific server
Returns:
GET:
A JSONSuccess response with a field `data` containing:
* a list of dictionnaries (one for each service) containing:
* a field `name`: the name of a service
"""
query = Service_link.objects.filter(
server__in=Interface.objects.filter(
domain__in=Domain.objects.filter(name=server_name)
)
)
if not query:
return JSONError("This service is not active for this server")
services = query.all()
seria = ServiceLinkSerializer(services, many=True)
return JSONSuccess(seria.data)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def dns_mac_ip_dns(request):
"""The list of all active interfaces with all the associated infos
(MAC, IP, IpType, DNS name and associated zone extension)
Returns:
GET:
A JSON Success response with a field `data` containing:
* a list of dictionnaries (one for each interface) containing:
* a field `ipv4` containing:
* a field `ipv4`: the ip for this interface
* a field `ip_type`: the name of the IpType of this interface
* a field `ipv6` containing `null` if ipv6 is deactivated else:
* a field `ipv6`: the ip for this interface
* a field `ip_type`: the name of the IpType of this interface
* a field `mac_address`: the MAC of this interface
* a field `domain`: the DNS name for this interface
* a field `extension`: the extension for the DNS zone of this interface
"""
interfaces = all_active_assigned_interfaces(full=True)
seria = FullInterfaceSerializer(interfaces, many=True)
return JSONSuccess(seria.data)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def dns_alias(request):
"""The list of all the alias used and the DNS info associated
Returns:
GET:
A JSON Success response with a field `data` containing:
* a list of dictionnaries (one for each alias) containing:
* a field `name`: the alias used
* a field `cname`: the target of the alias (real name of the interface)
* a field `cname_entry`: the entry to write in the DNS to have the alias
* a field `extension`: the extension for the DNS zone of this interface
"""
alias = Domain.objects.filter(interface_parent=None).filter(cname__in=Domain.objects.filter(interface_parent__in=Interface.objects.exclude(ipv4=None))).select_related('extension').select_related('cname__extension')
seria = DomainSerializer(alias, many=True)
return JSONSuccess(seria.data)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def dns_corresp(request):
"""The list of the IpTypes possible with the infos about each
Returns:
GET:
A JSON Success response with a field `data` containing:
* a list of dictionnaries (one for each IpType) containing:
* a field `type`: the name of the type
* a field `extension`: the DNS extension associated
* a field `domain_ip_start`: the first ip to use for this type
* a field `domain_ip_stop`: the last ip to use for this type
* a field `prefix_v6`: `null` if IPv6 is deactivated else the prefix to use
* a field `ouverture_ports_tcp_in`: the policy for TCP IN ports
* a field `ouverture_ports_tcp_out`: the policy for TCP OUT ports
* a field `ouverture_ports_udp_in`: the policy for UDP IN ports
* a field `ouverture_ports_udp_out`: the policy for UDP OUT ports
"""
ip_type = IpType.objects.all().select_related('extension')
seria = TypeSerializer(ip_type, many=True)
return JSONSuccess(seria.data)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def dns_mx(request):
"""The list of MX record to add to the DNS
Returns:
GET:
A JSON Success response with a field `data` containing:
* a list of dictionnaries (one for each MX record) containing:
* a field `zone`: the extension for the concerned zone
* a field `priority`: the priority to use
* a field `name`: the name of the target
* a field `mx_entry`: the full entry to add in the DNS for this MX record
"""
mx = Mx.objects.all().select_related('zone').select_related('name__extension')
seria = MxSerializer(mx, many=True)
return JSONSuccess(seria.data)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def dns_ns(request):
"""The list of NS record to add to the DNS
Returns:
GET:
A JSON Success response with a field `data` containing:
* a list of dictionnaries (one for each NS record) containing:
* a field `zone`: the extension for the concerned zone
* a field `ns`: the DNS name for the NS server targeted
* a field `ns_entry`: the full entry to add in the DNS for this NS record
"""
ns = Ns.objects.exclude(ns__in=Domain.objects.filter(interface_parent__in=Interface.objects.filter(ipv4=None))).select_related('zone').select_related('ns__extension')
seria = NsSerializer(ns, many=True)
return JSONSuccess(seria.data)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def dns_txt(request):
"""The list of TXT record to add to the DNS
Returns:
GET:
A JSON Success response with a field `data` containing:
* a list of dictionnaries (one for each TXT record) containing:
* a field `zone`: the extension for the concerned zone
* a field `field1`: the first field in the record (target)
* a field `field2`: the second field in the record (value)
* a field `txt_entry`: the full entry to add in the DNS for this TXT record
"""
txt = Txt.objects.all().select_related('zone')
seria = TxtSerializer(txt, many=True)
return JSONSuccess(seria.data)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def dns_srv(request):
"""The list of SRV record to add to the DNS
Returns:
GET:
A JSON Success response with a field `data` containing:
* a list of dictionnaries (one for each SRV record) containing:
* a field `extension`: the extension for the concerned zone
* a field `service`: the name of the service concerned
* a field `protocole`: the name of the protocol to use
* a field `ttl`: the Time To Live to use
* a field `priority`: the priority for this service
* a field `weight`: the weight for same priority entries
* a field `port`: the port targeted
* a field `target`: the interface targeted by this service
* a field `srv_entry`: the full entry to add in the DNS for this SRV record
"""
srv = Srv.objects.all().select_related('extension').select_related('target__extension')
seria = SrvSerializer(srv, many=True)
return JSONSuccess(seria.data)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def dns_zones(request):
"""The list of the zones managed
Returns:
GET:
A JSON Success response with a field `data` containing:
* a list of dictionnaries (one for each zone) containing:
* a field `name`: the extension for the zone
* a field `origin`: the server IPv4 for the orgin of the zone
* a field `origin_v6`: `null` if ipv6 is deactivated else the server IPv6 for the origin of the zone
* a field `soa` containing:
* a field `mail` containing the mail to contact in case of problem with the zone
* a field `param` containing the full soa paramters to use in the DNS for this zone
* a field `zone_entry`: the full entry to add in the DNS for the origin of the zone
"""
zones = Extension.objects.all().select_related('origin')
seria = ExtensionSerializer(zones, many=True)
return JSONSuccess(seria.data)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def firewall_ouverture_ports(request):
"""The list of the ports authorized to be openned by the firewall
Returns:
GET:
A JSONSuccess response with a `data` field containing:
* a field `ipv4` containing:
* a field `tcp_in` containing:
* a list of port number where ipv4 tcp in should be ok
* a field `tcp_out` containing:
* a list of port number where ipv4 tcp ou should be ok
* a field `udp_in` containing:
* a list of port number where ipv4 udp in should be ok
* a field `udp_out` containing:
* a list of port number where ipv4 udp out should be ok
* a field `ipv6` containing:
* a field `tcp_in` containing:
* a list of port number where ipv6 tcp in should be ok
* a field `tcp_out` containing:
* a list of port number where ipv6 tcp ou should be ok
* a field `udp_in` containing:
* a list of port number where ipv6 udp in should be ok
* a field `udp_out` containing:
* a list of port number where ipv6 udp out should be ok
"""
r = {'ipv4':{}, 'ipv6':{}}
for o in OuverturePortList.objects.all().prefetch_related('ouvertureport_set').prefetch_related('interface_set', 'interface_set__ipv4'):
pl = {
"tcp_in":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.TCP, io=OuverturePort.IN))),
"tcp_out":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.TCP, io=OuverturePort.OUT))),
"udp_in":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.UDP, io=OuverturePort.IN))),
"udp_out":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.UDP, io=OuverturePort.OUT))),
}
for i in filter_active_interfaces(o.interface_set):
if i.may_have_port_open():
d = r['ipv4'].get(i.ipv4.ipv4, {})
d["tcp_in"] = d.get("tcp_in",set()).union(pl["tcp_in"])
d["tcp_out"] = d.get("tcp_out",set()).union(pl["tcp_out"])
d["udp_in"] = d.get("udp_in",set()).union(pl["udp_in"])
d["udp_out"] = d.get("udp_out",set()).union(pl["udp_out"])
r['ipv4'][i.ipv4.ipv4] = d
if i.ipv6():
for ipv6 in i.ipv6():
d = r['ipv6'].get(ipv6.ipv6, {})
d["tcp_in"] = d.get("tcp_in",set()).union(pl["tcp_in"])
d["tcp_out"] = d.get("tcp_out",set()).union(pl["tcp_out"])
d["udp_in"] = d.get("udp_in",set()).union(pl["udp_in"])
d["udp_out"] = d.get("udp_out",set()).union(pl["udp_out"])
r['ipv6'][ipv6.ipv6] = d
return JSONSuccess(r)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def dhcp_mac_ip(request):
"""The list of all active interfaces with all the associated infos
(MAC, IP, IpType, DNS name and associated zone extension)
Returns:
GET:
A JSON Success response with a field `data` containing:
* a list of dictionnaries (one for each interface) containing:
* a field `ipv4` containing:
* a field `ipv4`: the ip for this interface
* a field `ip_type`: the name of the IpType of this interface
* a field `mac_address`: the MAC of this interface
* a field `domain`: the DNS name for this interface
* a field `extension`: the extension for the DNS zone of this interface
"""
interfaces = all_active_assigned_interfaces()
seria = InterfaceSerializer(interfaces, many=True)
return JSONSuccess(seria.data)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def mailing_standard(request):
"""All the available standard mailings.
Returns:
GET:
A JSONSucess response with a field `data` containing:
* a list of dictionnaries (one for each mailing) containing:
* a field `name`: the name of a mailing
"""
return JSONSuccess([
{'name': 'adherents'}
])
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def mailing_standard_ml_members(request):
"""All the members of a specific standard mailing
Returns:
GET:
A JSONSucess response with a field `data` containing:
* a list if dictionnaries (one for each member) containing:
* a field `email`: the email of the member
* a field `name`: the name of the member
* a field `surname`: the surname of the member
* a field `pseudo`: the pseudo of the member
"""
# All with active connextion
if ml_name == 'adherents':
members = all_has_access().values('email').distinct()
# Unknown mailing
else:
return JSONError("This mailing does not exist")
seria = MailingMemberSerializer(members, many=True)
return JSONSuccess(seria.data)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def mailing_club(request):
"""All the available club mailings.
Returns:
GET:
A JSONSucess response with a field `data` containing:
* a list of dictionnaries (one for each mailing) containing:
* a field `name` indicating the name of a mailing
"""
clubs = Club.objects.filter(mailing=True).values('pseudo')
seria = MailingSerializer(clubs, many=True)
return JSONSuccess(seria.data)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def mailing_club_ml_members(request):
"""All the members of a specific club mailing
Returns:
GET:
A JSONSucess response with a field `data` containing:
* a list if dictionnaries (one for each member) containing:
* a field `email`: the email of the member
* a field `name`: the name of the member
* a field `surname`: the surname of the member
* a field `pseudo`: the pseudo of the member
"""
try:
club = Club.objects.get(mailing=True, pseudo=ml_name)
except Club.DoesNotExist:
return JSONError("This mailing does not exist")
members = club.administrators.all().values('email').distinct()
seria = MailingMemberSerializer(members, many=True)
return JSONSuccess(seria.data)

3
contributors.py Normal file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env python3
contributeurs = ['Gabriel Detraz', 'chirac', 'Maël Kervella', 'LEVY-FALK Hugo', 'Dalahro', 'lhark', 'root', 'Hugo LEVY-FALK', 'Chirac', 'guimoz', 'Mael Kervella', 'klafyvel', 'matthieu', 'Yoann Pietri', 'Simon Brélivet', 'chibrac', 'David Sinquin', 'Pierre Cadart', 'moamoak', 'Éloi Alain', 'FERNET Laouen', 'Hugo Levy-Falk', 'Joanne Steiner', 'Matthieu Michelet', 'Yoann PIETRI', 'B', 'Daniel STAN', 'Eloi Alain', 'Guimoz', 'Hugo Hervieux', 'Laouen Fernet', 'Lemesle', 'MICHELET matthieu', 'Nymous', 'Thibault de BOUTRAY', 'Tipunchetrhum', 'Éloi ALAIN']

View file

@ -21,3 +21,4 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from .acl import *

40
cotisations/acl.py Normal file
View file

@ -0,0 +1,40 @@
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""cotisations.acl
Here are defined some functions to check acl on the application.
"""
def can_view(user):
"""Check if an user can view the application.
Args:
user: The user who wants to view the application.
Returns:
A couple (allowed, msg) where allowed is a boolean which is True if
viewing is granted and msg is a message (can be None).
"""
can = user.has_module_perms('cotisations')
return can, None if can else "Vous ne pouvez pas voir cette application."

View file

@ -26,9 +26,8 @@ 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
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)
@ -40,8 +39,12 @@ 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 django.core.validators import MinValueValidator,MaxValueValidator
from .models import Article, Paiement, Facture, Banque
from preferences.models import OptionalUser
from users.models import User
from re2o.field_permissions import FieldPermissionFormMixin
class NewFactureForm(ModelForm):
@ -141,27 +144,18 @@ class NewFactureFormPdf(Form):
)
class EditFactureForm(NewFactureForm):
class EditFactureForm(FieldPermissionFormMixin, NewFactureForm):
"""Edition d'une facture : moyen de paiement, banque, user parent"""
class Meta(NewFactureForm.Meta):
fields = ['paiement', 'banque', 'cheque', 'user']
model = Facture
fields = '__all__'
def __init__(self, *args, **kwargs):
super(EditFactureForm, self).__init__(*args, **kwargs)
self.fields['user'].label = 'Adherent'
self.fields['user'].empty_label = "Séléctionner\
l'adhérent propriétaire"
class TrezEditFactureForm(EditFactureForm):
"""Vue pour édition controle trésorier"""
class Meta(EditFactureForm.Meta):
fields = '__all__'
def __init__(self, *args, **kwargs):
super(TrezEditFactureForm, self).__init__(*args, **kwargs)
self.fields['valid'].label = 'Validité de la facture'
self.fields['control'].label = 'Contrôle de la facture'
class ArticleForm(ModelForm):
@ -180,11 +174,19 @@ class DelArticleForm(Form):
"""Suppression d'un ou plusieurs articles en vente. Choix
parmis les modèles"""
articles = forms.ModelMultipleChoiceField(
queryset=Article.objects.all(),
queryset=Article.objects.none(),
label="Articles actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelArticleForm, self).__init__(*args, **kwargs)
if instances:
self.fields['articles'].queryset = instances
else:
self.fields['articles'].queryset = Article.objects.all()
class PaiementForm(ModelForm):
"""Creation d'un moyen de paiement, champ text moyen et type
@ -204,11 +206,19 @@ class DelPaiementForm(Form):
"""Suppression d'un ou plusieurs moyens de paiements, selection
parmis les models"""
paiements = forms.ModelMultipleChoiceField(
queryset=Paiement.objects.all(),
queryset=Paiement.objects.none(),
label="Moyens de paiement actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelPaiementForm, self).__init__(*args, **kwargs)
if instances:
self.fields['paiements'].queryset = instances
else:
self.fields['paiements'].queryset = Paiement.objects.all()
class BanqueForm(ModelForm):
"""Creation d'une banque, field name"""
@ -225,7 +235,69 @@ class BanqueForm(ModelForm):
class DelBanqueForm(Form):
"""Selection d'une ou plusieurs banques, pour suppression"""
banques = forms.ModelMultipleChoiceField(
queryset=Banque.objects.all(),
queryset=Banque.objects.none(),
label="Banques actuelles",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelBanqueForm, self).__init__(*args, **kwargs)
if instances:
self.fields['banques'].queryset = instances
else:
self.fields['banques'].queryset = Banque.objects.all()
class NewFactureSoldeForm(NewFactureForm):
"""Creation d'une facture, moyen de paiement, banque et numero
de cheque"""
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
self.fields['cheque'].required = False
self.fields['banque'].required = False
self.fields['cheque'].label = 'Numero de chèque'
self.fields['banque'].empty_label = "Non renseigné"
self.fields['paiement'].empty_label = "Séléctionner\
une bite de paiement"
paiement_list = Paiement.objects.filter(type_paiement=1)
if paiement_list:
self.fields['paiement'].widget\
.attrs['data-cheque'] = paiement_list.first().id
class Meta:
model = Facture
fields = ['paiement', 'banque']
def clean(self):
cleaned_data = super(NewFactureSoldeForm, self).clean()
paiement = cleaned_data.get("paiement")
cheque = cleaned_data.get("cheque")
banque = cleaned_data.get("banque")
if not paiement:
raise forms.ValidationError("Le moyen de paiement est obligatoire")
elif paiement.type_paiement == "check" and not (cheque and banque):
raise forms.ValidationError("Le numéro de chèque et\
la banque sont obligatoires.")
return cleaned_data
class RechargeForm(Form):
value = forms.FloatField(
label='Valeur',
min_value=0.01,
validators = []
)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(RechargeForm, self).__init__(*args, **kwargs)
def clean_value(self):
value = self.cleaned_data['value']
if value < OptionalUser.get_cached_value('min_online_payment'):
raise forms.ValidationError("Montant inférieur au montant minimal de paiement en ligne (%s) €" % OptionalUser.get_cached_value('min_online_payment'))
if value + self.user.solde > OptionalUser.get_cached_value('max_solde'):
raise forms.ValidationError("Le solde ne peux excéder %s " % OptionalUser.get_cached_value('max_solde'))
return value

View file

@ -59,7 +59,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='cotisation',
name='type_cotisation',
field=models.CharField(choices=[('Connexion', 'Connexion'), ('Adhesion', 'Adhesion'), ('All', 'All')], max_length=255),
field=models.CharField(choices=[('Connexion', 'Connexion'), ('Adhesion', 'Adhesion'), ('All', 'All')], max_length=255, default='All'),
),
migrations.AddField(
model_name='vente',

View file

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-30 23:07
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cotisations', '0027_auto_20171029_1156'),
]
operations = [
migrations.AlterModelOptions(
name='article',
options={'permissions': (('view_article', 'Peut voir un objet article'),)},
),
migrations.AlterModelOptions(
name='banque',
options={'permissions': (('view_banque', 'Peut voir un objet banque'),)},
),
migrations.AlterModelOptions(
name='cotisation',
options={'permissions': (('view_cotisation', 'Peut voir un objet cotisation'), ('change_all_cotisation', 'Superdroit, peut modifier toutes les cotisations'))},
),
migrations.AlterModelOptions(
name='facture',
options={'permissions': (('change_facture_control', "Peut changer l'etat de controle"), ('change_facture_pdf', 'Peut éditer une facture pdf'), ('view_facture', 'Peut voir un objet facture'), ('change_all_facture', 'Superdroit, peut modifier toutes les factures'))},
),
migrations.AlterModelOptions(
name='paiement',
options={'permissions': (('view_paiement', 'Peut voir un objet paiement'),)},
),
migrations.AlterModelOptions(
name='vente',
options={'permissions': (('view_vente', 'Peut voir un objet vente'), ('change_all_vente', 'Superdroit, peut modifier toutes les ventes'))},
),
]

View file

@ -56,8 +56,10 @@ from django.db.models import Max
from django.utils import timezone
from machines.models import regen
from re2o.field_permissions import FieldPermissionModelMixin
class Facture(models.Model):
class Facture(FieldPermissionModelMixin, 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
@ -76,6 +78,15 @@ class Facture(models.Model):
valid = models.BooleanField(default=True)
control = models.BooleanField(default=False)
class Meta:
abstract = False
permissions = (
("change_facture_control", "Peut changer l'etat de controle"),
("change_facture_pdf", "Peut éditer une facture pdf"),
("view_facture", "Peut voir un objet facture"),
("change_all_facture", "Superdroit, peut modifier toutes les factures"),
)
def prix(self):
"""Renvoie le prix brut sans les quantités. Méthode
dépréciée"""
@ -103,6 +114,65 @@ class Facture(models.Model):
).values_list('name', flat=True))
return name
def get_instance(factureid, *args, **kwargs):
return Facture.objects.get(pk=factureid)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.add_facture'), u"Vous n'avez pas le\
droit de créer des factures"
def can_edit(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.change_facture'):
return False, u"Vous n'avez pas le droit d'éditer les factures"
elif not user_request.has_perm('cotisations.change_all_facture') and not self.user.can_edit(user_request, *args, **kwargs)[0]:
return False, u"Vous ne pouvez pas éditer les factures de cet user protégé"
elif not user_request.has_perm('cotisations.change_all_facture') and\
(self.control or not self.valid):
return False, u"Vous n'avez pas le droit d'éditer une facture\
controlée ou invalidée par un trésorier"
else:
return True, None
def can_delete(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.delete_facture'):
return False, u"Vous n'avez pas le droit de supprimer une facture"
if not self.user.can_edit(user_request, *args, **kwargs)[0]:
return False, u"Vous ne pouvez pas éditer les factures de cet user protégé"
if self.control or not self.valid:
return False, u"Vous ne pouvez pas supprimer une facture\
contrôlée ou invalidée par un trésorier"
else:
return True, None
def can_view_all(user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.view_facture'):
return False, u"Vous n'avez pas le droit de voir les factures"
return True, None
def can_view(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.view_facture') and\
self.user != user_request:
return False, u"Vous ne pouvez pas afficher l'historique d'une\
facture d'un autre user que vous sans droit cableur"
elif not self.valid:
return False, u"La facture est invalidée et ne peut être affichée"
else:
return True, None
@staticmethod
def can_change_control(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.change_facture_control'), "Vous ne pouvez pas éditer le controle sans droit trésorier"
@staticmethod
def can_change_pdf(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.change_facture_pdf'), "Vous ne pouvez pas éditer une facture sans droit trésorier"
def __init__(self, *args, **kwargs):
super(Facture, self).__init__(*args, **kwargs)
self.field_permissions = {
'control' : self.can_change_control,
}
def __str__(self):
return str(self.user) + ' ' + str(self.date)
@ -149,6 +219,12 @@ class Vente(models.Model):
max_length=255
)
class Meta:
permissions = (
("view_vente", "Peut voir un objet vente"),
("change_all_vente", "Superdroit, peut modifier toutes les ventes"),
)
def prix_total(self):
"""Renvoie le prix_total de self (nombre*prix)"""
return self.prix*self.number
@ -201,6 +277,50 @@ class Vente(models.Model):
self.update_cotisation()
super(Vente, self).save(*args, **kwargs)
def get_instance(venteid, *args, **kwargs):
return Vente.objects.get(pk=venteid)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.add_vente'), u"Vous n'avez pas le\
droit de créer des ventes"
return True, None
def can_edit(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.change_vente'):
return False, u"Vous n'avez pas le droit d'éditer les ventes"
elif not user_request.has_perm('cotisations.change_all_facture') and not self.facture.user.can_edit(user_request, *args, **kwargs)[0]:
return False, u"Vous ne pouvez pas éditer les factures de cet user protégé"
elif not user_request.has_perm('cotisations.change_all_vente') and\
(self.facture.control or not self.facture.valid):
return False, u"Vous n'avez pas le droit d'éditer une vente\
controlée ou invalidée par un trésorier"
else:
return True, None
def can_delete(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.delete_vente'):
return False, u"Vous n'avez pas le droit de supprimer une vente"
if not self.facture.user.can_edit(user_request, *args, **kwargs)[0]:
return False, u"Vous ne pouvez pas éditer les factures de cet user protégé"
if self.facture.control or not self.facture.valid:
return False, u"Vous ne pouvez pas supprimer une vente\
contrôlée ou invalidée par un trésorier"
else:
return True, None
def can_view_all(user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.view_vente'):
return False, u"Vous n'avez pas le droit de voir les ventes"
return True, None
def can_view(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.view_vente') and\
self.facture.user != user_request:
return False, u"Vous ne pouvez pas afficher l'historique d'une\
facture d'un autre user que vous sans droit cableur"
else:
return True, None
def __str__(self):
return str(self.name) + ' ' + str(self.facture)
@ -269,6 +389,11 @@ class Article(models.Model):
unique_together = ('name', 'type_user')
class Meta:
permissions = (
("view_article", "Peut voir un objet article"),
)
def clean(self):
if self.name.lower() == "solde":
raise ValidationError("Solde est un nom d'article invalide")
@ -277,6 +402,29 @@ class Article(models.Model):
"La durée est obligatoire si il s'agit d'une cotisation"
)
def get_instance(articleid, *args, **kwargs):
return Article.objects.get(pk=articleid)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.add_article'), u"Vous n'avez pas le\
droit d'ajouter des articles"
def can_edit(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.change_article'), u"Vous n'avez pas le\
droit d'éditer des articles"
def can_delete(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.delete_article'), u"Vous n'avez pas le\
droit de supprimer des articles"
def can_view_all(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.view_article'), u"Vous n'avez pas le\
droit de voir des articles"
def can_view(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.view_article'), u"Vous n'avez pas le\
droit de voir des articles"
def __str__(self):
return self.name
@ -287,6 +435,34 @@ class Banque(models.Model):
name = models.CharField(max_length=255)
class Meta:
permissions = (
("view_banque", "Peut voir un objet banque"),
)
def get_instance(banqueid, *args, **kwargs):
return Banque.objects.get(pk=banqueid)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.add_banque'), u"Vous n'avez pas le\
droit d'ajouter des banques"
def can_edit(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.change_banque'), u"Vous n'avez pas le\
droit d'éditer des banques"
def can_delete(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.delete_banque'), u"Vous n'avez pas le\
droit de supprimer des banques"
def can_view_all(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.view_banque'), u"Vous n'avez pas le\
droit de voir des banques"
def can_view(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.view_banque'), u"Vous n'avez pas le\
droit de voir des banques"
def __str__(self):
return self.name
@ -302,6 +478,34 @@ class Paiement(models.Model):
moyen = models.CharField(max_length=255)
type_paiement = models.IntegerField(choices=PAYMENT_TYPES, default=0)
class Meta:
permissions = (
("view_paiement", "Peut voir un objet paiement"),
)
def get_instance(paiementid, *args, **kwargs):
return Paiement.objects.get(pk=paiementid)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.add_paiement'), u"Vous n'avez pas le\
droit d'ajouter des paiements"
def can_edit(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.change_paiement'), u"Vous n'avez pas le\
droit d'éditer des paiements"
def can_delete(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.delete_paiement'), u"Vous n'avez pas le\
droit de supprimer des paiements"
def can_view_all(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.view_paiement'), u"Vous n'avez pas le\
droit de voir des paiements"
def can_view(self, user_request, *args, **kwargs):
return user_request.has_perm('cotisations.view_paiement'), u"Vous n'avez pas le\
droit de voir des paiements"
def __str__(self):
return self.moyen
@ -330,10 +534,57 @@ class Cotisation(models.Model):
type_cotisation = models.CharField(
choices=COTISATION_TYPE,
max_length=255,
default='All',
)
date_start = models.DateTimeField()
date_end = models.DateTimeField()
class Meta:
permissions = (
("view_cotisation", "Peut voir un objet cotisation"),
("change_all_cotisation", "Superdroit, peut modifier toutes les cotisations"),
)
def get_instance(cotisationid, *args, **kwargs):
return Cotisations.objects.get(pk=cotisationid)
def can_create(user_request, *args, **kwargs):
return user_request.has_perm('cotisations.add_cotisation'), u"Vous n'avez pas le\
droit de créer des cotisations"
return True, None
def can_edit(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.change_cotisation'):
return False, u"Vous n'avez pas le droit d'éditer les cotisations"
elif not user_request.has_perm('cotisations.change_all_cotisation') and\
(self.vente.facture.control or not self.vente.facture.valid):
return False, u"Vous n'avez pas le droit d'éditer une cotisation\
controlée ou invalidée par un trésorier"
else:
return True, None
def can_delete(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.delete_cotisation'):
return False, u"Vous n'avez pas le droit de supprimer une cotisations"
if self.vente.facture.control or not self.vente.facture.valid:
return False, u"Vous ne pouvez pas supprimer une cotisations\
contrôlée ou invalidée par un trésorier"
else:
return True, None
def can_view_all(user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.view_cotisation'):
return False, u"Vous n'avez pas le droit de voir les cotisations"
return True, None
def can_view(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.view_cotisation') and\
self.vente.facture.user != user_request:
return False, u"Vous ne pouvez pas afficher l'historique d'une\
cotisation d'un autre user que vous sans droit cableur"
else:
return True, None
def __str__(self):
return str(self.vente)

111
cotisations/payment.py Normal file
View file

@ -0,0 +1,111 @@
"""Payment
Here are defined some views dedicated to online payement.
"""
from django.urls import reverse
from django.shortcuts import redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.views.decorators.csrf import csrf_exempt
from django.utils.datastructures import MultiValueDictKeyError
from django.http import HttpResponse, HttpResponseBadRequest
from collections import OrderedDict
from preferences.models import AssoOption
from .models import Facture
from .payment_utils.comnpay import Payment as ComnpayPayment
@csrf_exempt
@login_required
def accept_payment(request, factureid):
facture = get_object_or_404(Facture, id=factureid)
messages.success(
request,
"Le paiement de {} € a été accepté.".format(facture.prix())
)
return redirect(reverse('users:profil', kwargs={'userid':request.user.id}))
@csrf_exempt
@login_required
def refuse_payment(request):
messages.error(
request,
"Le paiement a été refusé."
)
return redirect(reverse('users:profil', kwargs={'userid':request.user.id}))
@csrf_exempt
def ipn(request):
p = ComnpayPayment()
order = ('idTpe', 'idTransaction', 'montant', 'result', 'sec', )
try:
data = OrderedDict([(f, request.POST[f]) for f in order])
except MultiValueDictKeyError:
return HttpResponseBadRequest("HTTP/1.1 400 Bad Request")
if not p.validSec(data, AssoOption.get_cached_value('payment_pass')):
return HttpResponseBadRequest("HTTP/1.1 400 Bad Request")
result = True if (request.POST['result'] == 'OK') else False
idTpe = request.POST['idTpe']
idTransaction = request.POST['idTransaction']
# On vérifie que le paiement nous est destiné
if not idTpe == AssoOption.get_cached_value('payment_id'):
return HttpResponseBadRequest("HTTP/1.1 400 Bad Request")
try:
factureid = int(idTransaction)
except ValueError:
return HttpResponseBadRequest("HTTP/1.1 400 Bad Request")
facture = get_object_or_404(Facture, id=factureid)
# On vérifie que le paiement est valide
if not result:
# Le paiement a échoué : on effectue les actions nécessaires (On indique qu'elle a échoué)
facture.delete()
# On notifie au serveur ComNPay qu'on a reçu les données pour traitement
return HttpResponse("HTTP/1.1 200 OK")
facture.valid = True
facture.save()
# A nouveau, on notifie au serveur qu'on a bien traité les données
return HttpResponse("HTTP/1.0 200 OK")
def comnpay(facture, request):
host = request.get_host()
p = ComnpayPayment(
str(AssoOption.get_cached_value('payment_id')),
str(AssoOption.get_cached_value('payment_pass')),
'https://' + host + reverse(
'cotisations:accept_payment',
kwargs={'factureid':facture.id}
),
'https://' + host + reverse('cotisations:refuse_payment'),
'https://' + host + reverse('cotisations:ipn'),
"",
"D"
)
r = {
'action' : 'https://secure.homologation.comnpay.com',
'method' : 'POST',
'content' : p.buildSecretHTML(
"Rechargement du solde",
facture.prix(),
idTransaction=str(facture.id)
),
'amount' : facture.prix,
}
return r
PAYMENT_SYSTEM = {
'COMNPAY' : comnpay,
'NONE' : None
}

View file

View file

@ -0,0 +1,68 @@
import time
from random import randrange
import base64
import hashlib
from collections import OrderedDict
from itertools import chain
class Payment():
vad_number = ""
secret_key = ""
urlRetourOK = ""
urlRetourNOK = ""
urlIPN = ""
source = ""
typeTr = "D"
def __init__(self, vad_number = "", secret_key = "", urlRetourOK = "", urlRetourNOK = "", urlIPN = "", source="", typeTr="D"):
self.vad_number = vad_number
self.secret_key = secret_key
self.urlRetourOK = urlRetourOK
self.urlRetourNOK = urlRetourNOK
self.urlIPN = urlIPN
self.source = source
self.typeTr = typeTr
def buildSecretHTML(self, produit="Produit", montant="0.00", idTransaction=""):
if idTransaction == "":
self.idTransaction = str(time.time())+self.vad_number+str(randrange(999))
else:
self.idTransaction = idTransaction
array_tpe = OrderedDict(
montant= str(montant),
idTPE= self.vad_number,
idTransaction= self.idTransaction,
devise= "EUR",
lang= 'fr',
nom_produit= produit,
source= self.source,
urlRetourOK= self.urlRetourOK,
urlRetourNOK= self.urlRetourNOK,
typeTr= str(self.typeTr)
)
if self.urlIPN!="":
array_tpe['urlIPN'] = self.urlIPN
array_tpe['key'] = self.secret_key;
strWithKey = base64.b64encode(bytes('|'.join(array_tpe.values()), 'utf-8'))
del array_tpe["key"]
array_tpe['sec'] = hashlib.sha512(strWithKey).hexdigest()
ret = ""
for key in array_tpe:
ret += '<input type="hidden" name="'+key+'" value="'+array_tpe[key]+'"/>'
return ret
def validSec(self, values, secret_key):
if "sec" in values:
sec = values['sec']
del values["sec"]
strWithKey = hashlib.sha512(base64.b64encode(bytes('|'.join(values.values()) +"|"+secret_key, 'utf-8'))).hexdigest()
return strWithKey.upper() == sec.upper()
else:
return False

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -41,13 +43,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ article.duration }}</td>
<td>{{ article.type_user }}</td>
<td class="text-right">
{% if is_trez %}
{% can_edit article %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'cotisations:edit-article' article.id %}">
<i class="glyphicon glyphicon-edit"></i>
<i class="fa fa-edit"></i>
</a>
{% endif %}
{% acl_end %}
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'cotisations:history' 'article' article.id %}">
<i class="glyphicon glyphicon-time"></i>
<i class="fa fa-history"></i>
</a>
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -33,13 +35,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<tr>
<td>{{ banque.name }}</td>
<td class="text-right">
{% if is_trez %}
{% can_edit banque %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'cotisations:edit-banque' banque.id %}">
<i class="glyphicon glyphicon-edit"></i>
<i class="fa fa-edit"></i>
</a>
{% endif %}
{% acl_end %}
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'cotisations:history' 'banque' banque.id %}">
<i class="glyphicon glyphicon-time"></i>
<i class="fa fa-history"></i>
</a>
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
{% if facture_list.paginator %}
{% include "pagination.html" with list=facture_list %}
{% endif %}
@ -47,7 +49,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<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">
@ -55,21 +56,22 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<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> 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 %}
{% can_edit facture %}
<li><a href="{% url 'cotisations:edit-facture' facture.id %}"><i class="fa fa-dollar-sign"></i> Modifier</a></li>
{% acl_else %}
<li>Facture controlée</li>
{% endif %}
{% acl_end %}
{% can_delete facture %}
<li><a href="{% url 'cotisations:del-facture' facture.id %}"><i class="fa fa-trash"></i> Supprimer</a></li>
{% acl_end %}
<li><a href="{% url 'cotisations:history' 'facture' facture.id %}"><i class="fa fa-history"></i> Historique</a></li>
</ul>
</div>
</td>
{% endif %}
<td>
{% if facture.valid %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:facture-pdf' facture.id %}">
<i class="glyphicon glyphicon-save"></i>
<i class="fa fa-file-pdf"></i>
PDF
</a>
{% else %}

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -33,13 +35,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<tr>
<td>{{ paiement.moyen }}</td>
<td class="text-right">
{% if is_trez %}
{% can_edit paiement %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'cotisations:edit-paiement' paiement.id %}">
<i class="glyphicon glyphicon-edit"></i>
<i class="fa fa-edit"></i>
</a>
{% endif %}
{% acl_end %}
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'cotisations:history' 'paiement' paiement.id %}">
<i class="glyphicon glyphicon-time"></i>
<i class="fa fa-history"></i>
</a>
</td>
</tr>

View file

@ -56,7 +56,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% bootstrap_form_errors form %}
<tr>
<td><a href="{% url "users:profil" form.instance.user.id%}" class="btn btn-primary btn-sm" role="button"><i class="glyphicon glyphicon-user"></i></a>
<td><a href="{% url "users:profil" form.instance.user.id%}" class="btn btn-primary btn-sm" role="button"><i class="fa fa-user"></i></a>
</td>
<td>{{ form.instance.user.name }}</td>
<td>{{ form.instance.user.surname }}</td>

View file

@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<form class="form" method="post">
{% csrf_token %}
{% bootstrap_form factureform %}
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
{% bootstrap_button action_name button_type="submit" icon="star" %}
</form>
{% endblock %}

View file

@ -24,15 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% block title %}Articles{% endblock %}
{% block content %}
<h2>Liste des types d'articles</h2>
{% if is_trez %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:add-article' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un type d'articles</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'cotisations:del-article' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs types d'articles</a>
{% endif %}
{% can_create Article %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:add-article' %}"><i class="fa fa-cart-plus"></i> Ajouter un type d'articles</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'cotisations:del-article' %}"><i class="fa fa-trash"></i> Supprimer un ou plusieurs types d'articles</a>
{% include "cotisations/aff_article.html" with article_list=article_list %}
<br />
<br />

View file

@ -24,15 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% block title %}Banques{% endblock %}
{% block content %}
<h2>Liste des banques</h2>
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:add-banque' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter une banque</a>
{% if is_trez %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'cotisations:del-banque' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer une ou plusieurs banques</a>
{% endif %}
{% can_create Banque %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:add-banque' %}"><i class="fa fa-cart-plus"></i> Ajouter une banque</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'cotisations:del-banque' %}"><i class="fa fa-trash"></i> Supprimer une ou plusieurs banques</a>
{% include "cotisations/aff_banque.html" with banque_list=banque_list %}
<br />
<br />

View file

@ -24,15 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% block title %}Paiements{% endblock %}
{% block content %}
<h2>Liste des types de paiements</h2>
{% if is_trez %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:add-paiement' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un type de paiement</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'cotisations:del-paiement' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs types de paiements</a>
{% endif %}
{% can_create Paiement %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:add-paiement' %}"><i class="fa fa-cart-plus"></i> Ajouter un type de paiement</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'cotisations:del-paiement' %}"><i class="fa fa-trash"></i> Supprimer un ou plusieurs types de paiements</a>
{% include "cotisations/aff_paiement.html" with paiement_list=paiement_list %}
<br />
<br />

View file

@ -34,6 +34,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<form class="form" method="post">
{% csrf_token %}
<h3>Nouvelle facture</h3>
<p>
Solde de l'utilisateur : {{ user.solde }} €
</p>
{% bootstrap_form factureform %}
{{ venteform.management_form }}
<!-- TODO: FIXME to include data-type="check" for right option in id_cheque select -->
@ -46,7 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
&nbsp;
<button class="btn btn-danger btn-sm"
id="id_form-0-article-remove" type="button">
<span class="glyphicon glyphicon-remove"></span>
<span class="fa fa-times"></span>
</button>
</div>
{% endfor %}
@ -55,7 +58,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<p>
Prix total : <span id="total_price">0,00</span>
</p>
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
{% bootstrap_button "Créer" button_type="submit" icon="star" %}
</form>
<script type="text/javascript">
@ -70,7 +73,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
&nbsp;
<button class="btn btn-danger btn-sm"
id="id_form-__prefix__-article-remove" type="button">
<span class="glyphicon glyphicon-remove"></span>
<span class="fa fa-times"></span>
</button>`
function add_article(){

View file

@ -0,0 +1,157 @@
{% extends "cotisations/sidebar.html" %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load staticfiles%}
{% block title %}Création et modification de factures{% endblock %}
{% block content %}
{% bootstrap_form_errors venteform.management_form %}
<form class="form" method="post">
{% csrf_token %}
<h3>Nouvelle facture</h3>
{{ venteform.management_form }}
<!-- TODO: FIXME to include data-type="check" for right option in id_cheque select -->
<h3>Articles de la facture</h3>
<div id="form_set" class="form-group">
{% for form in venteform.forms %}
<div class='product_to_sell form-inline'>
Article : &nbsp;
{% bootstrap_form form label_class='sr-only' %}
&nbsp;
<button class="btn btn-danger btn-sm"
id="id_form-0-article-remove" type="button">
<span class="fa fa-times"></span>
</button>
</div>
{% endfor %}
</div>
<input class="btn btn-primary btn-sm" role="button" value="Ajouter un article" id="add_one">
<p>
Prix total : <span id="total_price">0,00</span>
</p>
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
</form>
<script type="text/javascript">
var prices = {};
{% for article in articlelist %}
prices[{{ article.id|escapejs }}] = {{ article.prix }};
{% endfor %}
var template = `Article : &nbsp;
{% bootstrap_form venteform.empty_form label_class='sr-only' %}
&nbsp;
<button class="btn btn-danger btn-sm"
id="id_form-__prefix__-article-remove" type="button">
<span class="fa fa-times"></span>
</button>`
function add_article(){
// Index start at 0 => new_index = number of items
var new_index =
document.getElementsByClassName('product_to_sell').length;
document.getElementById('id_form-TOTAL_FORMS').value ++;
var new_article = document.createElement('div');
new_article.className = 'product_to_sell form-inline';
new_article.innerHTML = template.replace(/__prefix__/g, new_index);
document.getElementById('form_set').appendChild(new_article);
add_listenner_for_id(new_index);
}
function update_price(){
var price = 0;
var product_count =
document.getElementsByClassName('product_to_sell').length;
var article, article_price, quantity;
for (i = 0; i < product_count; ++i){
article = document.getElementById(
'id_form-' + i.toString() + '-article').value;
if (article == '') {
continue;
}
article_price = prices[article];
quantity = document.getElementById(
'id_form-' + i.toString() + '-quantity').value;
price += article_price * quantity;
}
document.getElementById('total_price').innerHTML =
price.toFixed(2).toString().replace('.', ',');
}
function add_listenner_for_id(i){
document.getElementById('id_form-' + i.toString() + '-article')
.addEventListener("change", update_price, true);
document.getElementById('id_form-' + i.toString() + '-article')
.addEventListener("onkeypress", update_price, true);
document.getElementById('id_form-' + i.toString() + '-quantity')
.addEventListener("change", update_price, true);
document.getElementById('id_form-' + i.toString() + '-article-remove')
.addEventListener("click", function(event) {
var article = event.target.parentNode;
article.parentNode.removeChild(article);
document.getElementById('id_form-TOTAL_FORMS').value --;
update_price();
}
)
}
function set_cheque_info_visibility() {
var paiement = document.getElementById("id_Facture-paiement");
var visible = paiement.value == paiement.getAttribute('data-cheque');
p = document.getElementById("id_Facture-paiement");
var display = 'none';
if (visible) {
display = 'block';
}
document.getElementById("id_Facture-cheque")
.parentNode.style.display = display;
document.getElementById("id_Facture-banque")
.parentNode.style.display = display;
}
// Add events manager when DOM is fully loaded
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("add_one")
.addEventListener("click", add_article, true);
var product_count =
document.getElementsByClassName('product_to_sell').length;
for (i = 0; i < product_count; ++i){
add_listenner_for_id(i);
}
document.getElementById("id_Facture-paiement")
.addEventListener("change", set_cheque_info_visibility, true);
set_cheque_info_visibility();
update_price();
});
</script>
{% endblock %}

View file

@ -0,0 +1,37 @@
{% extends "cotisations/sidebar.html" %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load staticfiles%}
{% block title %}Rechargement du solde{% endblock %}
{% block content %}
<h3>Recharger de {{ amount }} €</h3>
<form class="form" method="{{ method }}" action="{{action}}">
{{ content | safe }}
{% bootstrap_button "Payer" button_type="submit" icon="piggy-bank" %}
</form>
{% endblock %}

View file

@ -0,0 +1,39 @@
{% extends "cotisations/sidebar.html" %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load staticfiles%}
{% block title %}Rechargement du solde{% endblock %}
{% block content %}
<h2>Rechargement du solde</h2>
<h3>Solde : <span class="label label-default">{{ request.user.solde }} €</span></h3>
<form class="form" method="post">
{% csrf_token %}
{% bootstrap_form rechargeform %}
{% bootstrap_button "Valider" button_type="submit" icon="piggy-bank" %}
</form>
{% endblock %}

View file

@ -23,32 +23,41 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
{% block sidebar %}
{% if is_trez %}
{% can_change Facture pdf %}
<a class="list-group-item list-group-item-success" href="{% url "cotisations:new-facture-pdf" %}">
<i class="glyphicon glyphicon-plus"></i>
<i class="fa fa-plus"></i>
Créer une facture
</a>
<a class="list-group-item list-group-item-warning" href="{% url "cotisations:control" %}">
<i class="glyphicon glyphicon-eye-open"></i>
<i class="fa fa-eye"></i>
Contrôler les factures
</a>
{% endif %}
{% acl_end %}
{% can_view_all Facture %}
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index" %}">
<i class="glyphicon glyphicon-list"></i>
<i class="fa fa-list-ul"></i>
Factures
</a>
{% acl_end %}
{% can_view_all Article %}
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-article" %}">
<i class="glyphicon glyphicon-list"></i>
<i class="fa fa-list-ul"></i>
Articles en vente
</a>
{% acl_end %}
{% can_view_all Banque %}
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-banque" %}">
<i class="glyphicon glyphicon-list"></i>
<i class="fa fa-list-ul"></i>
Banques
</a>
{% acl_end %}
{% can_view_all Paiement %}
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-paiement" %}">
<i class="glyphicon glyphicon-list"></i>
<i class="fa fa-list-ul"></i>
Moyens de paiement
</a>
{% acl_end %}
{% endblock %}

View file

@ -24,7 +24,9 @@ from __future__ import unicode_literals
from django.conf.urls import url
import re2o
from . import views
from . import payment
urlpatterns = [
url(r'^new_facture/(?P<userid>[0-9]+)$',
@ -99,24 +101,35 @@ urlpatterns = [
views.index_paiement,
name='index-paiement'
),
url(r'^history/(?P<object_name>facture)/(?P<object_id>[0-9]+)$',
views.history,
name='history'
),
url(r'^history/(?P<object_name>article)/(?P<object_id>[0-9]+)$',
views.history,
name='history'
),
url(r'^history/(?P<object_name>paiement)/(?P<object_id>[0-9]+)$',
views.history,
name='history'),
url(r'^history/(?P<object_name>banque)/(?P<object_id>[0-9]+)$',
views.history,
name='history'
url(
r'history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
re2o.views.history,
name='history',
kwargs={'application':'cotisations'},
),
url(r'^control/$',
views.control,
name='control'
),
url(r'^new_facture_solde/(?P<userid>[0-9]+)$',
views.new_facture_solde,
name='new_facture_solde'
),
url(r'^recharge/$',
views.recharge,
name='recharge'
),
url(r'^payment/accept/(?P<factureid>[0-9]+)$',
payment.accept_payment,
name='accept_payment'
),
url(r'^payment/refuse/$',
payment.refuse_payment,
name='refuse_payment'
),
url(r'^payment/ipn/$',
payment.ipn,
name='ipn'
),
url(r'^$', views.index, name='index'),
]

View file

@ -29,6 +29,7 @@ import os
from django.urls import reverse
from django.shortcuts import render, redirect
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.core.validators import MaxValueValidator
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib import messages
from django.db.models import ProtectedError
@ -36,6 +37,8 @@ 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 django.views.decorators.csrf import csrf_exempt
from django.views.decorators.debug import sensitive_variables
from reversion import revisions as reversion
from reversion.models import Version
# Import des models, forms et fonctions re2o
@ -44,11 +47,19 @@ from re2o.settings import LOGO_PATH
from re2o import settings
from re2o.views import form
from re2o.utils import SortTable
from re2o.acl import (
can_create,
can_edit,
can_delete,
can_view,
can_view_all,
can_delete_set,
can_change,
)
from preferences.models import OptionalUser, AssoOption, GeneralOption
from .models import Facture, Article, Vente, Paiement, Banque
from .forms import (
NewFactureForm,
TrezEditFactureForm,
EditFactureForm,
ArticleForm,
DelArticleForm,
@ -59,14 +70,19 @@ from .forms import (
NewFactureFormPdf,
SelectUserArticleForm,
SelectClubArticleForm,
CreditSoldeForm
CreditSoldeForm,
NewFactureSoldeForm,
RechargeForm
)
from . import payment
from .tex import render_invoice
@login_required
@permission_required('cableur')
def new_facture(request, userid):
@can_create(Facture)
@can_edit(User)
def new_facture(request, user, 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.
@ -74,11 +90,6 @@ def new_facture(request, userid):
enfin sauve la facture parente.
TODO : simplifier cette fonction, déplacer l'intelligence coté models
Facture et Vente."""
try:
user = User.objects.get(pk=userid)
except User.DoesNotExist:
messages.error(request, u"Utilisateur inexistant")
return redirect(reverse('cotisations:index'))
facture = Facture(user=user)
# Le template a besoin de connaitre les articles pour le js
article_list = Article.objects.filter(
@ -95,9 +106,8 @@ def new_facture(request, userid):
articles = article_formset
# Si au moins un article est rempli
if any(art.cleaned_data for art in articles):
options, _created = OptionalUser.objects.get_or_create()
user_solde = options.user_solde
solde_negatif = options.solde_negatif
user_solde = OptionalUser.get_cached_value('user_solde')
solde_negatif = OptionalUser.get_cached_value('solde_negatif')
# 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:
@ -163,14 +173,13 @@ def new_facture(request, userid):
@login_required
@permission_required('tresorier')
@can_change(Facture, 'pdf')
def new_facture_pdf(request):
"""Permet de générer un pdf d'une facture. Réservée
au trésorier, permet d'emettre des factures sans objet
Vente ou Facture correspondant en bdd"""
facture_form = NewFactureFormPdf(request.POST or None)
if facture_form.is_valid():
options, _created = AssoOption.objects.get_or_create()
tbl = []
article = facture_form.cleaned_data['article']
quantite = facture_form.cleaned_data['number']
@ -189,48 +198,30 @@ def new_facture_pdf(request):
'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,
'asso_name': AssoOption.get_cached_value('name'),
'line1': AssoOption.get_cached_value('adresse1'),
'line2': AssoOption.get_cached_value('adresse2'),
'siret': AssoOption.get_cached_value('siret'),
'email': AssoOption.get_cached_value('contact'),
'phone': AssoOption.get_cached_value('telephone'),
'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH)
})
return form({
'factureform': facture_form
'factureform': facture_form,
'action_name' : 'Editer'
}, 'cotisations/facture.html', request)
@login_required
def facture_pdf(request, factureid):
@can_view(Facture)
def facture_pdf(request, facture, factureid):
"""Affiche en pdf une facture. Cree une ligne par Vente de la facture,
et génére une facture avec le total, le moyen de paiement, l'adresse
de l'adhérent, etc. Réservée à self pour un user sans droits,
les droits cableurs permettent d'afficher toute facture"""
try:
facture = Facture.objects.get(pk=factureid)
except Facture.DoesNotExist:
messages.error(request, u"Facture inexistante")
return redirect(reverse('cotisations:index'))
if not request.user.has_perms(('cableur',))\
and facture.user != request.user:
messages.error(request, "Vous ne pouvez pas afficher une facture ne vous\
appartenant pas sans droit cableur")
return redirect(reverse(
'users:profil',
kwargs={'userid': str(request.user.id)}
))
if not facture.valid:
messages.error(request, "Vous ne pouvez pas afficher\
une facture non valide")
return redirect(reverse(
'users:profil',
kwargs={'userid': str(request.user.id)}
))
ventes_objects = Vente.objects.all().filter(facture=facture)
ventes = []
options, _created = AssoOption.objects.get_or_create()
for vente in ventes_objects:
ventes.append([vente, vente.number, vente.prix_total])
return render_invoice(request, {
@ -240,38 +231,23 @@ def facture_pdf(request, factureid):
'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,
'asso_name': AssoOption.get_cached_value('name'),
'line1': AssoOption.get_cached_value('adresse1'),
'line2': AssoOption.get_cached_value('adresse2'),
'siret': AssoOption.get_cached_value('siret'),
'email': AssoOption.get_cached_value('contact'),
'phone': AssoOption.get_cached_value('telephone'),
'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH)
})
@login_required
@permission_required('cableur')
def edit_facture(request, factureid):
@can_edit(Facture)
def edit_facture(request, facture, factureid):
"""Permet l'édition d'une facture. On peut y éditer les ventes
déjà effectuer, ou rendre une facture invalide (non payées, chèque
en bois etc). Mets à jour les durée de cotisation attenantes"""
try:
facture = Facture.objects.get(pk=factureid)
except Facture.DoesNotExist:
messages.error(request, u"Facture inexistante")
return redirect(reverse('cotisations:index'))
if request.user.has_perms(['tresorier']):
facture_form = TrezEditFactureForm(
request.POST or None,
instance=facture
)
elif facture.control or not facture.valid:
messages.error(request, "Vous ne pouvez pas editer une facture\
controlée ou invalidée par le trésorier")
return redirect(reverse('cotisations:index'))
else:
facture_form = EditFactureForm(request.POST or None, instance=facture)
facture_form = EditFactureForm(request.POST or None, instance=facture, user=request.user)
ventes_objects = Vente.objects.filter(facture=facture)
vente_form_set = modelformset_factory(
Vente,
@ -297,19 +273,10 @@ def edit_facture(request, factureid):
@login_required
@permission_required('cableur')
def del_facture(request, factureid):
@can_delete(Facture)
def del_facture(request, facture, factureid):
"""Suppression d'une facture. Supprime en cascade les ventes
et cotisations filles"""
try:
facture = Facture.objects.get(pk=factureid)
except Facture.DoesNotExist:
messages.error(request, u"Facture inexistante")
return redirect(reverse('cotisations:index'))
if facture.control or not facture.valid:
messages.error(request, "Vous ne pouvez pas editer une facture\
controlée ou invalidée par le trésorier")
return redirect(reverse('cotisations:index'))
if request.method == "POST":
with transaction.atomic(), reversion.create_revision():
facture.delete()
@ -323,14 +290,10 @@ def del_facture(request, factureid):
@login_required
@permission_required('cableur')
def credit_solde(request, userid):
@can_create(Facture)
@can_edit(User)
def credit_solde(request, user, userid):
""" Credit ou débit de solde """
try:
user = User.objects.get(pk=userid)
except User.DoesNotExist:
messages.error(request, u"Utilisateur inexistant")
return redirect(reverse('cotisations:index'))
facture = CreditSoldeForm(request.POST or None)
if facture.is_valid():
facture_instance = facture.save(commit=False)
@ -351,11 +314,11 @@ def credit_solde(request, userid):
reversion.set_comment("Création")
messages.success(request, "Solde modifié")
return redirect(reverse('cotisations:index'))
return form({'factureform': facture}, 'cotisations/facture.html', request)
return form({'factureform': facture, 'action_name' : 'Créditer'}, 'cotisations/facture.html', request)
@login_required
@permission_required('tresorier')
@can_create(Article)
def add_article(request):
"""Ajoute un article. Champs : désignation,
prix, est-ce une cotisation et si oui sa durée
@ -372,19 +335,14 @@ def add_article(request):
reversion.set_comment("Création")
messages.success(request, "L'article a été ajouté")
return redirect(reverse('cotisations:index-article'))
return form({'factureform': article}, 'cotisations/facture.html', request)
return form({'factureform': article, 'action_name' : 'Ajouter'}, 'cotisations/facture.html', request)
@login_required
@permission_required('tresorier')
def edit_article(request, articleid):
@can_edit(Article)
def edit_article(request, article_instance, articleid):
"""Edition d'un article (designation, prix, etc)
Réservé au trésorier"""
try:
article_instance = Article.objects.get(pk=articleid)
except Article.DoesNotExist:
messages.error(request, u"Entrée inexistante")
return redirect(reverse('cotisations:index-article'))
article = ArticleForm(request.POST or None, instance=article_instance)
if article.is_valid():
with transaction.atomic(), reversion.create_revision():
@ -397,14 +355,14 @@ def edit_article(request, articleid):
)
messages.success(request, "Type d'article modifié")
return redirect(reverse('cotisations:index-article'))
return form({'factureform': article}, 'cotisations/facture.html', request)
return form({'factureform': article, 'action_name' : 'Editer'}, 'cotisations/facture.html', request)
@login_required
@permission_required('tresorier')
def del_article(request):
@can_delete_set(Article)
def del_article(request, instances):
"""Suppression d'un article en vente"""
article = DelArticleForm(request.POST or None)
article = DelArticleForm(request.POST or None, instances=instances)
if article.is_valid():
article_del = article.cleaned_data['articles']
with transaction.atomic(), reversion.create_revision():
@ -412,11 +370,11 @@ def del_article(request):
reversion.set_user(request.user)
messages.success(request, "Le/les articles ont été supprimé")
return redirect(reverse('cotisations:index-article'))
return form({'factureform': article}, 'cotisations/facture.html', request)
return form({'factureform': article, 'action_name' : 'Supprimer'}, 'cotisations/facture.html', request)
@login_required
@permission_required('tresorier')
@can_create(Paiement)
def add_paiement(request):
"""Ajoute un moyen de paiement. Relié aux factures
via foreign key"""
@ -428,18 +386,13 @@ def add_paiement(request):
reversion.set_comment("Création")
messages.success(request, "Le moyen de paiement a été ajouté")
return redirect(reverse('cotisations:index-paiement'))
return form({'factureform': paiement}, 'cotisations/facture.html', request)
return form({'factureform': paiement, 'action_name' : 'Ajouter'}, 'cotisations/facture.html', request)
@login_required
@permission_required('tresorier')
def edit_paiement(request, paiementid):
@can_edit(Paiement)
def edit_paiement(request, paiement_instance, paiementid):
"""Edition d'un moyen de paiement"""
try:
paiement_instance = Paiement.objects.get(pk=paiementid)
except Paiement.DoesNotExist:
messages.error(request, u"Entrée inexistante")
return redirect(reverse('cotisations:index-paiement'))
paiement = PaiementForm(request.POST or None, instance=paiement_instance)
if paiement.is_valid():
with transaction.atomic(), reversion.create_revision():
@ -452,14 +405,14 @@ def edit_paiement(request, paiementid):
)
messages.success(request, "Type de paiement modifié")
return redirect(reverse('cotisations:index-paiement'))
return form({'factureform': paiement}, 'cotisations/facture.html', request)
return form({'factureform': paiement, 'action_name' : 'Editer'}, 'cotisations/facture.html', request)
@login_required
@permission_required('tresorier')
def del_paiement(request):
@can_delete_set(Paiement)
def del_paiement(request, instances):
"""Suppression d'un moyen de paiement"""
paiement = DelPaiementForm(request.POST or None)
paiement = DelPaiementForm(request.POST or None, instances=instances)
if paiement.is_valid():
paiement_dels = paiement.cleaned_data['paiements']
for paiement_del in paiement_dels:
@ -479,11 +432,11 @@ def del_paiement(request):
facture, vous ne pouvez pas le supprimer" % paiement_del
)
return redirect(reverse('cotisations:index-paiement'))
return form({'factureform': paiement}, 'cotisations/facture.html', request)
return form({'factureform': paiement, 'action_name' : 'Supprimer'}, 'cotisations/facture.html', request)
@login_required
@permission_required('cableur')
@can_create(Banque)
def add_banque(request):
"""Ajoute une banque à la liste des banques"""
banque = BanqueForm(request.POST or None)
@ -494,18 +447,13 @@ def add_banque(request):
reversion.set_comment("Création")
messages.success(request, "La banque a été ajoutée")
return redirect(reverse('cotisations:index-banque'))
return form({'factureform': banque}, 'cotisations/facture.html', request)
return form({'factureform': banque, 'action_name' : 'Ajouter'}, 'cotisations/facture.html', request)
@login_required
@permission_required('tresorier')
def edit_banque(request, banqueid):
@can_edit(Banque)
def edit_banque(request, banque_instance, banqueid):
"""Edite le nom d'une banque"""
try:
banque_instance = Banque.objects.get(pk=banqueid)
except Banque.DoesNotExist:
messages.error(request, u"Entrée inexistante")
return redirect(reverse('cotisations:index-banque'))
banque = BanqueForm(request.POST or None, instance=banque_instance)
if banque.is_valid():
with transaction.atomic(), reversion.create_revision():
@ -518,14 +466,14 @@ def edit_banque(request, banqueid):
)
messages.success(request, "Banque modifiée")
return redirect(reverse('cotisations:index-banque'))
return form({'factureform': banque}, 'cotisations/facture.html', request)
return form({'factureform': banque, 'action_name' : 'Editer'}, 'cotisations/facture.html', request)
@login_required
@permission_required('tresorier')
def del_banque(request):
@can_delete_set(Banque)
def del_banque(request, instances):
"""Supprime une banque"""
banque = DelBanqueForm(request.POST or None)
banque = DelBanqueForm(request.POST or None, instances=instances)
if banque.is_valid():
banque_dels = banque.cleaned_data['banques']
for banque_del in banque_dels:
@ -539,16 +487,16 @@ def del_banque(request):
messages.error(request, "La banque %s est affectée à au moins\
une facture, vous ne pouvez pas la supprimer" % banque_del)
return redirect(reverse('cotisations:index-banque'))
return form({'factureform': banque}, 'cotisations/facture.html', request)
return form({'factureform': banque, 'action_name' : 'Supprimer'}, 'cotisations/facture.html', request)
@login_required
@permission_required('tresorier')
@can_view_all(Facture)
@can_change(Facture, 'control')
def control(request):
"""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 = GeneralOption.get_cached_value('pagination_number')
facture_list = Facture.objects.select_related('user').select_related('paiement')
facture_list = SortTable.sort(
facture_list,
@ -583,7 +531,7 @@ def control(request):
@login_required
@permission_required('cableur')
@can_view_all(Article)
def index_article(request):
"""Affiche l'ensemble des articles en vente"""
article_list = Article.objects.order_by('name')
@ -593,7 +541,7 @@ def index_article(request):
@login_required
@permission_required('cableur')
@can_view_all(Paiement)
def index_paiement(request):
"""Affiche l'ensemble des moyens de paiement en vente"""
paiement_list = Paiement.objects.order_by('moyen')
@ -603,7 +551,7 @@ def index_paiement(request):
@login_required
@permission_required('cableur')
@can_view_all(Banque)
def index_banque(request):
"""Affiche l'ensemble des banques"""
banque_list = Banque.objects.order_by('name')
@ -613,11 +561,10 @@ def index_banque(request):
@login_required
@permission_required('cableur')
@can_view_all(Facture)
def index(request):
"""Affiche l'ensemble des factures, pour les cableurs et +"""
options, _created = GeneralOption.objects.get_or_create()
pagination_number = options.pagination_number
pagination_number = GeneralOption.get_cached_value('pagination_number')
facture_list = Facture.objects.select_related('user')\
.select_related('paiement').prefetch_related('vente_set')
facture_list = SortTable.sort(
@ -642,57 +589,122 @@ def index(request):
@login_required
def history(request, object_name, object_id):
"""Affiche l'historique de chaque objet"""
if object_name == 'facture':
try:
object_instance = Facture.objects.get(pk=object_id)
except Facture.DoesNotExist:
messages.error(request, "Facture inexistante")
return redirect(reverse('cotisations:index'))
if not request.user.has_perms(('cableur',))\
and object_instance.user != request.user:
messages.error(request, "Vous ne pouvez pas afficher l'historique\
d'une facture d'un autre user que vous sans droit cableur")
def new_facture_solde(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."""
user = request.user
facture = Facture(user=user)
paiement, _created = Paiement.objects.get_or_create(moyen='Solde')
facture.paiement = paiement
# Le template a besoin de connaitre les articles pour le js
article_list = Article.objects.filter(
Q(type_user='All') | Q(type_user=request.user.class_name)
)
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 article_formset.is_valid():
articles = article_formset
# Si au moins un article est rempli
if any(art.cleaned_data for art in articles):
user_solde = OptionalUser.get_cached_value('user_solde')
solde_negatif = OptionalUser.get_cached_value('solde_negatif')
# 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:
prix_total = 0
for art_item in articles:
if art_item.cleaned_data:
prix_total += art_item.cleaned_data['article']\
.prix*art_item.cleaned_data['quantity']
if float(user.solde) - float(prix_total) < solde_negatif:
messages.error(request, "Le solde est insuffisant pour\
effectuer l'opération")
return redirect(reverse(
'users:profil',
kwargs={'userid':str(request.user.id)}
kwargs={'userid': userid}
))
elif object_name == 'paiement' and request.user.has_perms(('cableur',)):
try:
object_instance = Paiement.objects.get(pk=object_id)
except Paiement.DoesNotExist:
messages.error(request, "Paiement inexistant")
return redirect(reverse('cotisations:index'))
elif object_name == 'article' and request.user.has_perms(('cableur',)):
try:
object_instance = Article.objects.get(pk=object_id)
except Article.DoesNotExist:
messages.error(request, "Article inexistante")
return redirect(reverse('cotisations:index'))
elif object_name == 'banque' and request.user.has_perms(('cableur',)):
try:
object_instance = Banque.objects.get(pk=object_id)
except Banque.DoesNotExist:
messages.error(request, "Banque inexistante")
return redirect(reverse('cotisations:index'))
with transaction.atomic(), reversion.create_revision():
facture.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
for art_item in articles:
if art_item.cleaned_data:
article = art_item.cleaned_data['article']
quantity = art_item.cleaned_data['quantity']
new_vente = Vente.objects.create(
facture=facture,
name=article.name,
prix=article.prix,
type_cotisation=article.type_cotisation,
duration=article.duration,
number=quantity
)
with transaction.atomic(), reversion.create_revision():
new_vente.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
if any(art_item.cleaned_data['article'].type_cotisation
for art_item in articles if art_item.cleaned_data):
messages.success(
request,
"La cotisation a été prolongée\
pour l'adhérent %s jusqu'au %s" % (
user.pseudo, user.end_adhesion()
)
)
else:
messages.error(request, "Objet inconnu")
return redirect(reverse('cotisations:index'))
options, _created = GeneralOption.objects.get_or_create()
pagination_number = options.pagination_number
reversions = Version.objects.get_for_object(object_instance)
paginator = Paginator(reversions, pagination_number)
page = request.GET.get('page')
try:
reversions = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
reversions = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
reversions = paginator.page(paginator.num_pages)
return render(request, 're2o/history.html', {
'reversions': reversions,
'object': object_instance
})
messages.success(request, "La facture a été crée")
return redirect(reverse(
'users:profil',
kwargs={'userid': userid}
))
messages.error(
request,
u"Il faut au moins un article valide pour créer une facture"
)
return redirect(reverse(
'users:profil',
kwargs={'userid': userid}
))
return form({
'venteform': article_formset,
'articlelist': article_list
}, 'cotisations/new_facture_solde.html', request)
@login_required
def recharge(request):
if AssoOption.get_cached_value('payment') == 'NONE':
messages.error(
request,
"Le paiement en ligne est désactivé."
)
return redirect(reverse(
'users:profil',
kwargs={'userid': request.user.id}
))
f = RechargeForm(request.POST or None, user=request.user)
if f.is_valid():
facture = Facture(user=request.user)
paiement, _created = Paiement.objects.get_or_create(moyen='Rechargement en ligne')
facture.paiement = paiement
facture.valid = False
facture.save()
v = Vente.objects.create(
facture=facture,
name='solde',
prix=f.cleaned_data['value'],
number=1,
)
v.save()
content = payment.PAYMENT_SYSTEM[AssoOption.get_cached_value('payment')](facture, request)
return render(request, 'cotisations/payment.html', content)
return form({'rechargeform':f}, 'cotisations/recharge.html', request)

View file

@ -36,6 +36,15 @@ export DEBIAN_FRONTEND=noninteractive
apt-get -y install sudo dialog
HEIGHT=15
WIDTH=40
init=$(dialog --clear \
--title "Installation de Re2o !" \
--msgbox "Cet utilitaire va procéder à l'installation initiale de re2o. Le serveur présent doit être vierge de préférence. Preconfiguration..." \
$HEIGHT $WIDTH \
2>&1 >/dev/tty)
HEIGHT=15
WIDTH=40
CHOICE_HEIGHT=4
@ -145,7 +154,14 @@ ldap_is_local=$(dialog --clear \
"${OPTIONS[@]}" \
2>&1 >/dev/tty)
echo "Vous devrez fournir un login/host dans le cas où le ldap est non local"
HEIGHT=15
WIDTH=40
instal_ldap=$(dialog --clear \
--title "Installation de Re2o !" \
--msgbox "Vous devrez fournir un login/host dans le cas où le ldap est non local" \
$HEIGHT $WIDTH \
2>&1 >/dev/tty)
TITLE="Mot de passe ldap"
ldap_password=$(dialog --title "$TITLE" \
@ -213,9 +229,16 @@ ldap_cn+=$ldap_dn
ldap_host="localhost"
fi
HEIGHT=15
WIDTH=40
install_base=$(dialog --clear \
--title "Installation de Re2o !" \
--msgbox "Installation des paquets de base" \
$HEIGHT $WIDTH \
2>&1 >/dev/tty)
echo "Installation des paquets de base"
apt-get -y install python3-django python3-dateutil texlive-latex-base texlive-fonts-recommended python3-djangorestframework python3-django-reversion python3-pip libsasl2-dev libldap2-dev libssl-dev
apt-get -y install python3-django python3-dateutil texlive-latex-base texlive-fonts-recommended python3-djangorestframework python3-django-reversion python3-pip libsasl2-dev libldap2-dev libssl-dev python3-crypto
pip3 install django-bootstrap3
pip3 install django-ldapdb
pip3 install django-macaddress
@ -270,13 +293,20 @@ then
setup_ldap $ldap_password $ldap_dn
else
echo "Vous devrez manuellement effectuer les opérations de setup de la base ldap sur le serveurs distant.
Lancez la commande : ./install_re2o.sh ldap $ldap_password $ldap_dn"
HEIGHT=15
WIDTH=40
ldap_setup=$(dialog --clear \
--title "Setup ldap" \
--msgbox "Vous devrez manuellement effectuer les opérations de setup de la base ldap sur le serveurs distant. Lancez la commande : ./install_re2o.sh ldap $ldap_password $ldap_dn" \
$HEIGHT $WIDTH \
2>&1 >/dev/tty)
fi
echo "Ecriture de settings_local"
django_secret_key=$(python -c "import random; print(''.join([random.SystemRandom().choice('abcdefghijklmnopqrstuvwxyz0123456789%=+') for i in range(50)]))")
aes_key=$(python -c "import random; print(''.join([random.SystemRandom().choice('abcdefghijklmnopqrstuvwxyz0123456789%=+') for i in range(32)]))")
cp re2o/settings_local.example.py re2o/settings_local.py
if [ $sql_bdd_type == 1 ]
@ -286,6 +316,7 @@ else
sed -i 's/db_engine/django.db.backends.postgresql_psycopg2/g' re2o/settings_local.py
fi
sed -i 's/SUPER_SECRET_KEY/'"$django_secret_key"'/g' re2o/settings_local.py
sed -i 's/THE_AES_KEY/'"$aes_key"'/g' re2o/settings_local.py
sed -i 's/SUPER_SECRET_DB/'"$sql_password"'/g' re2o/settings_local.py
sed -i 's/db_name_value/'"$sql_name"'/g' re2o/settings_local.py
sed -i 's/db_user_value/'"$sql_login"'/g' re2o/settings_local.py
@ -298,10 +329,22 @@ sed -i 's/example.org/'"$extension_locale"'/g' re2o/settings_local.py
sed -i 's/MY_EMAIL_HOST/'"$email_host"'/g' re2o/settings_local.py
sed -i 's/MY_EMAIL_PORT/'"$email_port"'/g' re2o/settings_local.py
echo "Application des migrations"
HEIGHT=15
WIDTH=40
migrations=$(dialog --clear \
--title "Setup django" \
--msgbox "Application des migrations" \
$HEIGHT $WIDTH \
2>&1 >/dev/tty)
python3 manage.py migrate
echo "Collecte des statics"
HEIGHT=15
WIDTH=40
static=$(dialog --clear \
--title "Setup django" \
--msgbox "Collecte des statiques" \
$HEIGHT $WIDTH \
2>&1 >/dev/tty)
python3 manage.py collectstatic
BACKTITLE="Fin de l'installation"
@ -319,7 +362,7 @@ web_serveur=$(dialog --clear \
clear
TITLE="Url où servir le serveur web (ex : re2o.example.org)"
TITLE="Url où servir le serveur web (ex : re2o.example.org). Assurez-vous que ce tld existe bien et répond auprès du DNS"
url_server=$(dialog --title "$TITLE" \
--backtitle "$BACKTITLE" \
--inputbox "$TITLE" $HEIGHT $WIDTH \
@ -365,11 +408,25 @@ sed -i 's|PATH|'"$current_path"'|g' /etc/apache2/sites-available/re2o.conf
a2ensite re2o
service apache2 reload
else
echo "Nginx non supporté, vous devrez installer manuellement"
HEIGHT=15
WIDTH=40
web_server=$(dialog --clear \
--title "Setup serveur web" \
--msgbox "Nginx non supporté, vous devrez installer manuellement" \
$HEIGHT $WIDTH \
2>&1 >/dev/tty)
fi
python3 manage.py createsuperuser
HEIGHT=15
WIDTH=40
end=$(dialog --clear \
--title "Installation terminée" \
--msgbox "Vous pouvez à présent vous rendre sur $url_server, et vous connecter. Votre utilisateur dispose des privilèges superuser" \
$HEIGHT $WIDTH \
2>&1 >/dev/tty)
}
main_function() {

View file

@ -21,3 +21,4 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from .acl import *

40
logs/acl.py Normal file
View file

@ -0,0 +1,40 @@
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""logs.acl
Here are defined some functions to check acl on the application.
"""
def can_view(user):
"""Check if an user can view the application.
Args:
user: The user who wants to view the application.
Returns:
A couple (allowed, msg) where allowed is a boolean which is True if
viewing is granted and msg is a message (can be None).
"""
can = user.has_module_perms('admin')
return can, None if can else "Vous ne pouvez pas voir cette application."

View file

@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endif %}
{% load logs_extra %}
{% load acl %}
<table class="table table-striped">
<thead>
@ -47,14 +48,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ revision.user }}</td>
<td>{{ revision.date_created }}</td>
<td>{{ revision.comment }}</td>
{% if is_bureau %}
{% can_edit_history %}
<td>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' revision.id %}">
<i class="glyphicon glyphicon-remove"></i>
<i class="fa fa-times"></i>
Annuler
</a>
</td>
{% endif %}
{% acl_end %}
</tr>
{% endfor %}
{% endfor %}

View file

@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endif %}
{% load logs_extra %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -51,14 +51,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endif %}
</i>)
</td>
{% if is_bureau %}
{% can_edit_history %}
<td>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' v.rev_id %}">
<i class="glyphicon glyphicon-remove"></i>
<i class="fa fa-times"></i>
Annuler
</a>
</td>
{% endif %}
{% acl_end %}
</tr>
{% elif v.version.content_type.model == 'whitelist' %}
<tr class="success">
@ -74,14 +74,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endif %}
</i>)
</td>
{% if is_bureau %}
{% can_edit_history%}
<td>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' v.rev_id %}">
<i class="glyphicon glyphicon-remove"></i>
<i class="fa fa-times"></i>
Annuler
</a>
</td>
{% endif %}
{% acl_end %}
</tr>
{% elif v.version.content_type.model == 'user' %}
<tr>
@ -93,14 +93,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
(<i>{{ v.comment }}</i>)
{% endif %}
</td>
{% if is_bureau %}
{% can_edit_history %}
<td>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' v.rev_id %}">
<i class="glyphicon glyphicon-remove"></i>
<i class="fa fa-times"></i>
Annuler
</a>
</td>
{% endif %}
{% acl_end %}
</tr>
{% elif v.version.content_type.model == 'vente' %}
<tr>
@ -112,14 +112,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
(<i>+{{ v.version.object.duration }} mois</i>)
{% endif %}
</td>
{% if is_bureau %}
{% can_edit_history %}
<td>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' v.rev_id %}">
<i class="glyphicon glyphicon-remove"></i>
<i class="fa fa-times"></i>
Annuler
</a>
</td>
{% endif %}
{% acl_end %}
</tr>
{% elif v.version.content_type.model == 'interface' %}
<tr>
@ -131,14 +131,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
(<i>{{ v.comment }}</i>)
{% endif %}
</td>
{% if is_bureau %}
{% can_edit_history %}
<td>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' v.rev_id %}">
<i class="glyphicon glyphicon-remove"></i>
<i class="fa fa-times"></i>
Annuler
</a>
</td>
{% endif %}
{% acl_end %}
</tr>
{% endif %}
{% endfor %}

View file

@ -23,32 +23,33 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
{% block sidebar %}
{% if is_cableur %}
{% can_view_app logs %}
<a class="list-group-item list-group-item-info" href="{% url "logs:index" %}">
<i class="glyphicon glyphicon-stats"></i>
<i class="fa fa-clipboard-list"></i>
Résumé
</a>
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-logs" %}">
<i class="glyphicon glyphicon-stats"></i>
<i class="fa fa-calendar-alt"></i>
Évènements
</a>
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-general" %}">
<i class="glyphicon glyphicon-stats"></i>
<i class="fa fa-chart-area"></i>
Général
</a>
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-models" %}">
<i class="glyphicon glyphicon-stats"></i>
<i class="fa fa-database"></i>
Base de données
</a>
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-actions" %}">
<i class="glyphicon glyphicon-stats"></i>
<i class="fa fa-plug"></i>
Actions de cablage
</a>
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-users" %}">
<i class="glyphicon glyphicon-stats"></i>
<i class="fa fa-users"></i>
Utilisateurs
</a>
{% endif %}
{% acl_end %}
{% endblock %}

View file

@ -41,7 +41,7 @@ from django.urls import reverse
from django.shortcuts import render, redirect
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.decorators import login_required
from django.db.models import Count
from reversion.models import Revision
@ -50,7 +50,6 @@ from reversion.models import Version, ContentType
from users.models import (
User,
ServiceUser,
Right,
School,
ListRight,
ListShell,
@ -93,7 +92,17 @@ from topologie.models import (
)
from preferences.models import GeneralOption
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,
)
from re2o.acl import (
can_view_all,
can_view_app,
can_edit_history,
)
from re2o.utils import all_active_assigned_interfaces_count
from re2o.utils import all_active_interfaces_count, SortTable
@ -108,12 +117,11 @@ STATS_DICT = {
@login_required
@permission_required('cableur')
@can_view_app('logs')
def index(request):
"""Affiche les logs affinés, date reformatées, selectionne
les event importants (ajout de droits, ajout de ban/whitelist)"""
options, _created = GeneralOption.objects.get_or_create()
pagination_number = options.pagination_number
pagination_number = GeneralOption.get_cached_value('pagination_number')
# The types of content kept for display
content_type_filter = ['ban', 'whitelist', 'vente', 'interface', 'user']
# Select only wanted versions
@ -167,12 +175,11 @@ def index(request):
@login_required
@permission_required('cableur')
@can_view_all(GeneralOption)
def stats_logs(request):
"""Affiche l'ensemble des logs et des modifications sur les objets,
classés par date croissante, en vrac"""
options, _created = GeneralOption.objects.get_or_create()
pagination_number = options.pagination_number
pagination_number = GeneralOption.get_cached_value('pagination_number')
revisions = Revision.objects.all().select_related('user')\
.prefetch_related('version_set__object')
revisions = SortTable.sort(
@ -197,7 +204,7 @@ def stats_logs(request):
@login_required
@permission_required('bureau')
@can_edit_history
def revert_action(request, revision_id):
""" Annule l'action en question """
try:
@ -215,7 +222,9 @@ def revert_action(request, revision_id):
@login_required
@permission_required('cableur')
@can_view_all(IpList)
@can_view_all(Interface)
@can_view_all(User)
def stats_general(request):
"""Statistiques générales affinées sur les ip, activées, utilisées par
range, et les statistiques générales sur les users : users actifs,
@ -298,7 +307,10 @@ def stats_general(request):
@login_required
@permission_required('cableur')
@can_view_app('users')
@can_view_app('cotisations')
@can_view_app('machines')
@can_view_app('topologie')
def stats_models(request):
"""Statistiques générales, affiche les comptages par models:
nombre d'users, d'écoles, de droits, de bannissements,
@ -310,7 +322,6 @@ def stats_models(request):
'clubs': [Club.PRETTY_NAME, Club.objects.count()],
'serviceuser': [ServiceUser.PRETTY_NAME,
ServiceUser.objects.count()],
'right': [Right.PRETTY_NAME, Right.objects.count()],
'school': [School.PRETTY_NAME, School.objects.count()],
'listright': [ListRight.PRETTY_NAME, ListRight.objects.count()],
'listshell': [ListShell.PRETTY_NAME, ListShell.objects.count()],
@ -340,7 +351,7 @@ def stats_models(request):
OuverturePortList.objects.count()
],
'vlan': [Vlan.PRETTY_NAME, Vlan.objects.count()],
'SOA': [Mx.PRETTY_NAME, Mx.objects.count()],
'SOA': [SOA.PRETTY_NAME, SOA.objects.count()],
'Mx': [Mx.PRETTY_NAME, Mx.objects.count()],
'Ns': [Ns.PRETTY_NAME, Ns.objects.count()],
'nas': [Nas.PRETTY_NAME, Nas.objects.count()],
@ -368,7 +379,7 @@ def stats_models(request):
@login_required
@permission_required('cableur')
@can_view_app('users')
def stats_users(request):
"""Affiche les statistiques base de données aggrégées par user :
nombre de machines par user, d'etablissements par user,
@ -395,7 +406,7 @@ def stats_users(request):
num=Count('whitelist')
).order_by('-num')[:10],
'Droits': User.objects.annotate(
num=Count('right')
num=Count('groups')
).order_by('-num')[:10],
},
'Etablissement': {
@ -422,7 +433,7 @@ def stats_users(request):
@login_required
@permission_required('cableur')
@can_view_app('users')
def stats_actions(request):
"""Vue qui affiche les statistiques de modifications d'objets par
utilisateurs.

View file

@ -21,3 +21,4 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from .acl import *

40
machines/acl.py Normal file
View file

@ -0,0 +1,40 @@
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""machines.acl
Here are defined some functions to check acl on the application.
"""
def can_view(user):
"""Check if an user can view the application.
Args:
user: The user who wants to view the application.
Returns:
A couple (allowed, msg) where allowed is a boolean which is True if
viewing is granted and msg is a message (can be None).
"""
can = user.has_module_perms('machines')
return can, None if can else "Vous ne pouvez pas voir cette application."

View file

@ -38,6 +38,8 @@ from __future__ import unicode_literals
from django.forms import ModelForm, Form
from django import forms
from re2o.field_permissions import FieldPermissionFormMixin
from .models import (
Domain,
Machine,
@ -55,10 +57,11 @@ from .models import (
Nas,
IpType,
OuverturePortList,
Ipv6List,
)
class EditMachineForm(ModelForm):
class EditMachineForm(FieldPermissionFormMixin, ModelForm):
"""Formulaire d'édition d'une machine"""
class Meta:
model = Machine
@ -76,14 +79,7 @@ class NewMachineForm(EditMachineForm):
fields = ['name']
class BaseEditMachineForm(EditMachineForm):
"""Edition basique, ne permet que de changer le nom et le statut.
Réservé aux users sans droits spécifiques"""
class Meta(EditMachineForm.Meta):
fields = ['name', 'active']
class EditInterfaceForm(ModelForm):
class EditInterfaceForm(FieldPermissionFormMixin, ModelForm):
"""Edition d'une interface. Edition complète"""
class Meta:
model = Interface
@ -91,13 +87,21 @@ class EditInterfaceForm(ModelForm):
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
user = kwargs.get('user')
super(EditInterfaceForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['mac_address'].label = 'Adresse mac'
self.fields['type'].label = 'Type de machine'
self.fields['type'].empty_label = "Séléctionner un type de machine"
if "ipv4" in self.fields:
self.fields['ipv4'].empty_label = "Assignation automatique\
de l'ipv4"
self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4"
self.fields['ipv4'].queryset = IpList.objects.filter(
interface__isnull=True
)
if not IpType.can_use_all(user):
self.fields['ipv4'].queryset = IpList.objects.filter(
interface__isnull=True
).filter(ip_type__in=IpType.objects.filter(need_infra=False))
else:
self.fields['ipv4'].queryset = IpList.objects.filter(
interface__isnull=True
)
@ -108,6 +112,10 @@ class EditInterfaceForm(ModelForm):
if "machine" in self.fields:
self.fields['machine'].queryset = Machine.objects.all()\
.select_related('user')
if not MachineType.can_use_all(user):
self.fields['type'].queryset = MachineType.objects.filter(
ip_type__in=IpType.objects.filter(need_infra=False)
)
class AddInterfaceForm(EditInterfaceForm):
@ -116,58 +124,6 @@ class AddInterfaceForm(EditInterfaceForm):
class Meta(EditInterfaceForm.Meta):
fields = ['type', 'ipv4', 'mac_address', 'details']
def __init__(self, *args, **kwargs):
infra = kwargs.pop('infra')
super(AddInterfaceForm, self).__init__(*args, **kwargs)
self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4"
if not infra:
self.fields['type'].queryset = MachineType.objects.filter(
ip_type__in=IpType.objects.filter(need_infra=False)
)
self.fields['ipv4'].queryset = IpList.objects.filter(
interface__isnull=True
).filter(ip_type__in=IpType.objects.filter(need_infra=False))
else:
self.fields['ipv4'].queryset = IpList.objects.filter(
interface__isnull=True
)
class NewInterfaceForm(EditInterfaceForm):
"""Formulaire light, sans choix de l'ipv4; d'ajout d'une interface"""
class Meta(EditInterfaceForm.Meta):
fields = ['type', 'mac_address', 'details']
class BaseEditInterfaceForm(EditInterfaceForm):
"""Edition basique d'une interface. En fonction des droits,
ajoute ou non l'ensemble des ipv4 disponibles (infra)"""
class Meta(EditInterfaceForm.Meta):
fields = ['type', 'ipv4', 'mac_address', 'details']
def __init__(self, *args, **kwargs):
infra = kwargs.pop('infra')
super(BaseEditInterfaceForm, self).__init__(*args, **kwargs)
self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4"
if not infra:
self.fields['type'].queryset = MachineType.objects.filter(
ip_type__in=IpType.objects.filter(need_infra=False)
)
self.fields['ipv4'].queryset = IpList.objects.filter(
interface__isnull=True
).filter(ip_type__in=IpType.objects.filter(need_infra=False))
# Add it's own address
self.fields['ipv4'].queryset |= IpList.objects.filter(
interface=self.instance
)
else:
self.fields['ipv4'].queryset = IpList.objects.filter(
interface__isnull=True
)
self.fields['ipv4'].queryset |= IpList.objects.filter(
interface=self.instance
)
class AliasForm(ModelForm):
"""Ajout d'un alias (et edition), CNAME, contenant nom et extension"""
@ -177,9 +133,10 @@ class AliasForm(ModelForm):
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
infra = kwargs.pop('infra')
user = kwargs.pop('user')
super(AliasForm, self).__init__(*args, prefix=prefix, **kwargs)
if not infra:
can_use_all, reason = Extension.can_use_all(user)
if not can_use_all:
self.fields['extension'].queryset = Extension.objects.filter(
need_infra=False
)
@ -233,11 +190,19 @@ class MachineTypeForm(ModelForm):
class DelMachineTypeForm(Form):
"""Suppression d'un ou plusieurs machinetype"""
machinetypes = forms.ModelMultipleChoiceField(
queryset=MachineType.objects.all(),
queryset=MachineType.objects.none(),
label="Types de machines actuelles",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelMachineTypeForm, self).__init__(*args, **kwargs)
if instances:
self.fields['machinetypes'].queryset = instances
else:
self.fields['machinetypes'].queryset = MachineType.objects.all()
class IpTypeForm(ModelForm):
"""Formulaire d'ajout d'un iptype. Pas d'edition de l'ip de start et de
@ -264,11 +229,19 @@ class EditIpTypeForm(IpTypeForm):
class DelIpTypeForm(Form):
"""Suppression d'un ou plusieurs iptype"""
iptypes = forms.ModelMultipleChoiceField(
queryset=IpType.objects.all(),
queryset=IpType.objects.none(),
label="Types d'ip actuelles",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelIpTypeForm, self).__init__(*args, **kwargs)
if instances:
self.fields['iptypes'].queryset = instances
else:
self.fields['iptypes'].queryset = IpType.objects.all()
class ExtensionForm(ModelForm):
"""Formulaire d'ajout et edition d'une extension"""
@ -288,11 +261,30 @@ class ExtensionForm(ModelForm):
class DelExtensionForm(Form):
"""Suppression d'une ou plusieurs extensions"""
extensions = forms.ModelMultipleChoiceField(
queryset=Extension.objects.all(),
queryset=Extension.objects.none(),
label="Extensions actuelles",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelExtensionForm, self).__init__(*args, **kwargs)
if instances:
self.fields['extensions'].queryset = instances
else:
self.fields['extensions'].queryset = Extension.objects.all()
class Ipv6ListForm(FieldPermissionFormMixin, ModelForm):
"""Gestion des ipv6 d'une machine"""
class Meta:
model = Ipv6List
fields = ['ipv6', 'slaac_ip']
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(Ipv6ListForm, self).__init__(*args, prefix=prefix, **kwargs)
class SOAForm(ModelForm):
"""Ajout et edition d'un SOA"""
@ -308,11 +300,19 @@ class SOAForm(ModelForm):
class DelSOAForm(Form):
"""Suppression d'un ou plusieurs SOA"""
soa = forms.ModelMultipleChoiceField(
queryset=SOA.objects.all(),
queryset=SOA.objects.none(),
label="SOA actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelSOAForm, self).__init__(*args, **kwargs)
if instances:
self.fields['soa'].queryset = instances
else:
self.fields['soa'].queryset = SOA.objects.all()
class MxForm(ModelForm):
"""Ajout et edition d'un MX"""
@ -327,15 +327,22 @@ class MxForm(ModelForm):
interface_parent=None
).select_related('extension')
class DelMxForm(Form):
"""Suppression d'un ou plusieurs MX"""
mx = forms.ModelMultipleChoiceField(
queryset=Mx.objects.all(),
queryset=Mx.objects.none(),
label="MX actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelMxForm, self).__init__(*args, **kwargs)
if instances:
self.fields['mx'].queryset = instances
else:
self.fields['mx'].queryset = Mx.objects.all()
class NsForm(ModelForm):
"""Ajout d'un NS pour une zone
@ -356,11 +363,19 @@ class NsForm(ModelForm):
class DelNsForm(Form):
"""Suppresion d'un ou plusieurs NS"""
ns = forms.ModelMultipleChoiceField(
queryset=Ns.objects.all(),
queryset=Ns.objects.none(),
label="Enregistrements NS actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelNsForm, self).__init__(*args, **kwargs)
if instances:
self.fields['ns'].queryset = instances
else:
self.fields['ns'].queryset = Ns.objects.all()
class TxtForm(ModelForm):
"""Ajout d'un txt pour une zone"""
@ -376,11 +391,19 @@ class TxtForm(ModelForm):
class DelTxtForm(Form):
"""Suppression d'un ou plusieurs TXT"""
txt = forms.ModelMultipleChoiceField(
queryset=Txt.objects.all(),
queryset=Txt.objects.none(),
label="Enregistrements Txt actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelTxtForm, self).__init__(*args, **kwargs)
if instances:
self.fields['txt'].queryset = instances
else:
self.fields['txt'].queryset = Txt.objects.all()
class SrvForm(ModelForm):
"""Ajout d'un srv pour une zone"""
@ -396,11 +419,19 @@ class SrvForm(ModelForm):
class DelSrvForm(Form):
"""Suppression d'un ou plusieurs Srv"""
srv = forms.ModelMultipleChoiceField(
queryset=Srv.objects.all(),
queryset=Srv.objects.none(),
label="Enregistrements Srv actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelSrvForm, self).__init__(*args, **kwargs)
if instances:
self.fields['srv'].queryset = instances
else:
self.fields['srv'].queryset = Srv.objects.all()
class NasForm(ModelForm):
"""Ajout d'un type de nas (machine d'authentification,
@ -417,11 +448,19 @@ class NasForm(ModelForm):
class DelNasForm(Form):
"""Suppression d'un ou plusieurs nas"""
nas = forms.ModelMultipleChoiceField(
queryset=Nas.objects.all(),
queryset=Nas.objects.none(),
label="Enregistrements Nas actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelNasForm, self).__init__(*args, **kwargs)
if instances:
self.fields['nas'].queryset = instances
else:
self.fields['nas'].queryset = Nas.objects.all()
class ServiceForm(ModelForm):
"""Ajout et edition d'une classe de service : dns, dhcp, etc"""
@ -446,11 +485,19 @@ class ServiceForm(ModelForm):
class DelServiceForm(Form):
"""Suppression d'un ou plusieurs service"""
service = forms.ModelMultipleChoiceField(
queryset=Service.objects.all(),
queryset=Service.objects.none(),
label="Services actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelServiceForm, self).__init__(*args, **kwargs)
if instances:
self.fields['service'].queryset = instances
else:
self.fields['service'].queryset = Service.objects.all()
class VlanForm(ModelForm):
"""Ajout d'un vlan : id, nom"""
@ -466,11 +513,19 @@ class VlanForm(ModelForm):
class DelVlanForm(Form):
"""Suppression d'un ou plusieurs vlans"""
vlan = forms.ModelMultipleChoiceField(
queryset=Vlan.objects.all(),
queryset=Vlan.objects.none(),
label="Vlan actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelVlanForm, self).__init__(*args, **kwargs)
if instances:
self.fields['vlan'].queryset = instances
else:
self.fields['vlan'].queryset = Vlan.objects.all()
class EditOuverturePortConfigForm(ModelForm):
"""Edition de la liste des profils d'ouverture de ports

View file

@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 18:47
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('machines', '0069_auto_20171116_0822'),
]
operations = [
migrations.AlterModelOptions(
name='domain',
options={'permissions': (('view_domain', 'Peut voir un objet domain'),)},
),
migrations.AlterModelOptions(
name='extension',
options={'permissions': (('view_extension', 'Peut voir un objet extension'), ('use_all_extension', 'Peut utiliser toutes les extension'))},
),
migrations.AlterModelOptions(
name='interface',
options={'permissions': (('view_interface', 'Peut voir un objet interface'),)},
),
migrations.AlterModelOptions(
name='iplist',
options={'permissions': (('view_iplist', 'Peut voir un objet iplist'),)},
),
migrations.AlterModelOptions(
name='iptype',
options={'permissions': (('view_iptype', 'Peut voir un objet iptype'), ('use_all_iptype', 'Peut utiliser tous les iptype'))},
),
migrations.AlterModelOptions(
name='machine',
options={'permissions': (('view_machine', 'Peut voir un objet machine quelquonque'), ('change_machine_user', "Peut changer le propriétaire d'une machine"))},
),
migrations.AlterModelOptions(
name='machinetype',
options={'permissions': (('view_machinetype', 'Peut voir un objet machinetype'), ('use_all_machinetype', "Peut utiliser n'importe quel type de machine"))},
),
migrations.AlterModelOptions(
name='mx',
options={'permissions': (('view_mx', 'Peut voir un objet mx'),)},
),
migrations.AlterModelOptions(
name='nas',
options={'permissions': (('view_nas', 'Peut voir un objet Nas'),)},
),
migrations.AlterModelOptions(
name='ns',
options={'permissions': (('view_nx', 'Peut voir un objet nx'),)},
),
migrations.AlterModelOptions(
name='ouvertureportlist',
options={'permissions': (('view_ouvertureportlist', 'Peut voir un objet ouvertureport'),)},
),
migrations.AlterModelOptions(
name='service',
options={'permissions': (('view_service', 'Peut voir un objet service'),)},
),
migrations.AlterModelOptions(
name='soa',
options={'permissions': (('view_soa', 'Peut voir un objet soa'),)},
),
migrations.AlterModelOptions(
name='srv',
options={'permissions': (('view_soa', 'Peut voir un objet soa'),)},
),
migrations.AlterModelOptions(
name='txt',
options={'permissions': (('view_txt', 'Peut voir un objet txt'),)},
),
migrations.AlterModelOptions(
name='vlan',
options={'permissions': (('view_vlan', 'Peut voir un objet vlan'),)},
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 20:00
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('machines', '0070_auto_20171231_1947'),
]
operations = [
migrations.AlterModelOptions(
name='ns',
options={'permissions': (('view_ns', 'Peut voir un objet ns'),)},
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-08 17:22
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('machines', '0071_auto_20171231_2100'),
]
operations = [
migrations.AlterModelOptions(
name='interface',
options={'permissions': (('view_interface', 'Peut voir un objet interface'), ('change_interface_machine', "Peut changer le propriétaire d'une interface"))},
),
]

View file

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-28 21:03
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('machines', '0072_auto_20180108_1822'),
]
operations = [
migrations.CreateModel(
name='Ipv6List',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ipv6', models.GenericIPAddressField(protocol='IPv6', unique=True)),
('slaac_ip', models.BooleanField(default=False)),
('interface', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='machines.Interface')),
],
),
migrations.AlterUniqueTogether(
name='ipv6list',
unique_together=set([('interface', 'slaac_ip')]),
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-29 02:52
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('machines', '0073_auto_20180128_2203'),
]
operations = [
migrations.AlterModelOptions(
name='ipv6list',
options={'permissions': (('view_ipv6list', 'Peut voir un objet ipv6'), ('change_ipv6list_slaac_ip', 'Peut changer la valeur slaac sur une ipv6'))},
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-29 23:52
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('machines', '0074_auto_20180129_0352'),
]
operations = [
migrations.AlterUniqueTogether(
name='ipv6list',
unique_together=set([]),
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-30 15:23
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('machines', '0075_auto_20180130_0052'),
]
operations = [
migrations.AlterField(
model_name='ipv6list',
name='interface',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ipv6list', to='machines.Interface'),
),
]

File diff suppressed because it is too large Load diff

View file

@ -37,7 +37,8 @@ from machines.models import (
Service_link,
Ns,
OuverturePortList,
OuverturePort
OuverturePort,
Ipv6List
)
@ -57,6 +58,12 @@ class IpListSerializer(serializers.ModelSerializer):
fields = ('ipv4', 'ip_type')
class Ipv6ListSerializer(serializers.ModelSerializer):
class Meta:
model = Ipv6List
fields = ('ipv6', 'slaac_ip')
class InterfaceSerializer(serializers.ModelSerializer):
"""Serialisation d'une interface, ipv4, domain et extension sont
des foreign_key, on les override et on les evalue avec des fonctions
@ -81,8 +88,9 @@ class InterfaceSerializer(serializers.ModelSerializer):
class FullInterfaceSerializer(serializers.ModelSerializer):
"""Serialisation complete d'une interface avec l'ipv6 en plus"""
"""Serialisation complete d'une interface avec les ipv6 en plus"""
ipv4 = IpListSerializer(read_only=True)
ipv6 = Ipv6ListSerializer(read_only=True, many=True)
mac_address = serializers.SerializerMethodField('get_macaddress')
domain = serializers.SerializerMethodField('get_dns')
extension = serializers.SerializerMethodField('get_interface_extension')
@ -124,6 +132,7 @@ class TypeSerializer(serializers.ModelSerializer):
class Meta:
model = IpType
fields = ('type', 'extension', 'domaine_ip_start', 'domaine_ip_stop',
'prefix_v6',
'ouverture_ports_tcp_in', 'ouverture_ports_tcp_out',
'ouverture_ports_udp_in', 'ouverture_ports_udp_out',)

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -33,7 +35,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<tr>
<td>{{ alias }}</td>
<td class="text-right">
{% can_edit alias %}
{% include 'buttons/edit.html' with href='machines:edit-alias' id=alias.id %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='alias' id=alias.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -45,9 +47,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ extension.origin_v6 }}</td>
{% endif %}
<td class="text-right">
{% if is_infra %}
{% can_create Extension %}
{% include 'buttons/edit.html' with href='machines:edit-extension' id=extension.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='extension' id=extension.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -48,9 +50,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ type.vlan }}</td>
<td>{{ type.ouverture_ports }}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit type %}
{% include 'buttons/edit.html' with href='machines:edit-iptype' id=type.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='iptype' id=type.id %}
</td>
</tr>

View file

@ -0,0 +1,51 @@
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
<th>Ipv6</th>
<th>Slaac</th>
<th></th>
</tr>
</thead>
{% for ipv6 in ipv6_list %}
<tr>
<td>{{ ipv6.ipv6 }}</td>
<td>{{ ipv6.slaac_ip }}</td>
<td class="text-right">
{% can_edit ipv6 %}
{% include 'buttons/edit.html' with href='machines:edit-ipv6list' id=ipv6.id %}
{% acl_end %}
{% can_delete ipv6 %}
{% include 'buttons/suppr.html' with href='machines:del-ipv6list' id=ipv6.id %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='ipv6list' id=ipv6.id %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -22,11 +22,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
{% if machines_list.paginator %}
{% include "pagination.html" with list=machines_list %}
{% endif %}
<table class="table">
<table class="table" id="machines_table">
<colgroup>
<col>
<col>
@ -44,36 +46,29 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% for machine in machines_list %}
<tr class="info">
<td colspan="4">
<b>{{ machine.name|default:'<i>Pas de nom</i>' }}</b> <i class="glyphicon glyphicon-chevron-right"></i>
<b>{{ machine.name|default:'<i>Pas de nom</i>' }}</b> <i class="fa-angle-right"></i>
<a href="{% url 'users:profil' userid=machine.user.id %}" title="Voir le profil">
<i class="glyphicon glyphicon-user"></i> {{ machine.user }}
<i class="fa fa-user"></i> {{ machine.user }}
</a>
</td>
<td class="text-right">
{% can_create Interface machine.id %}
{% include 'buttons/add.html' with href='machines:new-interface' id=machine.id desc='Ajouter une interface' %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='machine' id=machine.id %}
{% can_delete machine %}
{% include 'buttons/suppr.html' with href='machines:del-machine' id=machine.id %}
{% acl_end %}
</td>
</tr>
{% for interface in machine.interface_set.all %}
<tr>
<td>
{% if interface.domain.related_domain.all %}
<div class="dropdown">
<button class="btn btn-default dropdown-toggle" type="button" id="editioninterface" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
{{ interface.domain }} <span class="caret"></span>
{{ interface.domain }}
<button class="btn btn-default btn-xs" type="button" data-toggle="collapse" data-target="#collapseDomain_{{interface.id}}" aria-expanded="true" aria-controls="collapseDomain_{{interface.id}}">
Afficher les alias
</button>
<ul class="dropdown-menu" aria-labelledby="editioninterface">
{% for al in interface.domain.related_domain.all %}
<li>
<a href="http://{{ al }}">
{{ al }}
<i class="glyphicon glyphicon-share-alt"></i>
</a>
</li>
{% endfor %}
</ul>
</div>
{% else %}
{{ interface.domain }}
{% endif %}
@ -88,36 +83,90 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<b>IPv4</b> {{ interface.ipv4 }}
<br>
{% if ipv6_enabled and interface.ipv6 != 'None'%}
<b>IPv6</b> {{ interface.ipv6 }}
<b>IPv6</b>
<button class="btn btn-default btn-xs" type="button" data-toggle="collapse" data-target="#collapseIpv6_{{interface.id}}" aria-expanded="true" aria-controls="collapseIpv6_{{interface.id}}">
Afficher l'IPV6
</button>
{% endif %}
</td>
<td class="text-right">
<div class="dropdown" style="width: 128px;">
<button class="btn btn-primary btn-sm dropdown-toggle" type="button" id="editioninterface" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="glyphicon glyphicon-edit"></i> <span class="caret"></span>
<i class="fa fa-edit"></i> <span class="caret"></span>
</button>
{% include 'buttons/history.html' with href='machines:history' name='interface' id=interface.id %}
{% can_delete interface %}
{% include 'buttons/suppr.html' with href='machines:del-interface' id=interface.id %}
{% acl_end %}
<ul class="dropdown-menu" aria-labelledby="editioninterface">
{% can_edit interface %}
<li>
<a href="{% url 'machines:edit-interface' interface.id %}">
<i class="glyphicon glyphicon-edit"></i> Editer
<i class="fa fa-edit"></i> Editer
</a>
</li>
{% acl_end %}
{% can_create Domain interface.id %}
<li>
<a href="{% url 'machines:index-alias' interface.id %}">
<i class="glyphicon glyphicon-edit"></i> Gerer les alias
<i class="fa fa-edit"></i> Gerer les alias
</a>
</li>
{% acl_end %}
{% can_create Ipv6List interface.id %}
<li>
<a href="{% url 'machines:index-ipv6' interface.id %}">
<i class="fa fa-edit"></i> Gerer les ipv6
</a>
</li>
{% acl_end %}
{% can_create OuverturePortList %}
<li>
<a href="{% url 'machines:port-config' interface.id%}">
<i class="glyphicon glyphicon-edit"></i> Gerer la configuration des ports
<i class="fa fa-edit"></i> Gerer la configuration des ports
</a>
</li>
{% acl_end %}
</ul>
</div>
</td>
</tr>
{% if ipv6_enabled and interface.ipv6 != 'None'%}
<tr>
<td colspan=5 style="border-top: none; padding: 1px;">
<div class="collapse in" id="collapseIpv6_{{interface.id}}">
<ul class="list-group" style="margin-bottom: 0px;">
{% for ipv6 in interface.ipv6.all %}
<li class="list-group-item col-xs-6 col-sm-6 col-md-6" style="border: none;">
{{ipv6}}
</li>
{% endfor %}
</ul>
</div>
</td>
<tr>
{% endif %}
{% if interface.domain.related_domain.all %}
<tr>
<td colspan=5 style="border-top: none; padding: 1px;">
<div class="collapse in" id="collapseDomain_{{interface.id}}">
<ul class="list-group" style="margin-bottom: 0px;">
{% for al in interface.domain.related_domain.all %}
<li class="list-group-item col-xs-6 col-sm-4 col-md-3" style="border: none;">
<a href="http://{{ al }}">
{{ al }}
<i class="fa fa-share"></i>
</a>
</li>
{% endfor %}
</ul>
</div>
</td>
<tr>
{% endif %}
{% endfor %}
<tr>
<td colspan="8"></td>
@ -126,6 +175,21 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</tbody>
</table>
<script>
$("#machines_table").ready( function() {
var alias_div = [{% for machine in machines_list %}{% for interface in machine.interface_set.all %}{% if interface.domain.related_domain.all %}$("#collapseDomain_{{interface.id}}"), {% endif %}{% endfor %}{% endfor %}];
for (var i=0 ; i<alias_div.length ; i++) {
alias_div[i].collapse('hide');
}
} );
$("#machines_table").ready( function() {
var ipv6_div = [{% for machine in machines_list %}{% for interface in machine.interface_set.all %}{% if interface.ipv6.all %}$("#collapseIpv6_{{interface.id}}"), {% endif %}{% endfor %}{% endfor %}];
for (var i=0 ; i<ipv6_div.length ; i++) {
ipv6_div[i].collapse('hide');
}
} );
</script>
{% if machines_list.paginator %}
{% include "pagination.html" with list=machines_list %}
{% endif %}

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -35,9 +37,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ type.type }}</td>
<td>{{ type.ip_type }}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit type %}
{% include 'buttons/edit.html' with href='machines:edit-machinetype' id=type.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='machinetype' id=type.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -38,9 +40,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ mx.priority }}</td>
<td>{{ mx.name }}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit mx %}
{% include 'buttons/edit.html' with href='machines:edit-mx' id=mx.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='mx' id=mx.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -41,9 +43,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ nas.port_access_mode }}</td>
<td>{{ nas.autocapture_mac }}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit nas %}
{% include 'buttons/edit.html' with href='machines:edit-nas' id=nas.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='nas' id=nas.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -36,9 +38,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ ns.zone }}</td>
<td>{{ ns.ns }}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit ns %}
{% include 'buttons/edit.html' with href='machines:edit-ns' id=ns.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='ns' id=ns.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -40,9 +42,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ service.regular_time_regen }}</td>
<td>{% for serv in service.servers.all %}{{ serv }}, {% endfor %}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit service %}
{% include 'buttons/edit.html' with href='machines:edit-service' id=service.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='service' id=service.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -44,9 +46,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ soa.expire }}</td>
<td>{{ soa.ttl }}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit soa %}
{% include 'buttons/edit.html' with href='machines:edit-soa' id=soa.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='soa' id=soa.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -48,9 +50,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ srv.port }}</td>
<td>{{ srv.target }}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit srv %}
{% include 'buttons/edit.html' with href='machines:edit-srv' id=srv.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='srv' id=srv.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -36,9 +38,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ txt.zone }}</td>
<td>{{ txt.dns_entry }}</td>
<td class="text-right">
{% if is_infra %}
{% can_edit txt %}
{% include 'buttons/edit.html' with href='machines:edit-txt' id=txt.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='txt' id=txt.id %}
</td>
</tr>

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
@ -39,9 +41,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ vlan.comment }}</td>
<td>{% for range in vlan.iptype_set.all %}{{ range }}, {% endfor%}</td>
<td class="text-right">
{% if is_infra %}
{% can_create Vlan %}
{% include 'buttons/edit.html' with href='machines:edit-vlan' id=vlan.id %}
{% endif %}
{% acl_end %}
{% include 'buttons/history.html' with href='machines:history' name='vlan' id=vlan.id %}
</td>
</tr>

View file

@ -29,8 +29,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% block content %}
<h2>Liste des alias de l'interface</h2>
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-alias' interface_id %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un alias</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-alias' interface_id %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs alias</a>
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-alias' interface_id %}"><i class="fa fa-plus"></i> Ajouter un alias</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-alias' interface_id %}"><i class="fa fa-trash"></i> Supprimer un ou plusieurs alias</a>
{% include "machines/aff_alias.html" with alias_list=alias_list %}
<br />
<br />

View file

@ -25,45 +25,47 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load bootstrap3 %}
{% load acl %}
{% block title %}Machines{% endblock %}
{% block content %}
<h2>Liste des extensions</h2>
{% if is_infra %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-extension' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter une extension</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-extension' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer une ou plusieurs extensions</a>
{% endif %}
{% can_create Extension %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-extension' %}"><i class="fa fa-plus"></i> Ajouter une extension</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-extension' %}"><i class="fa fa-trash"></i> Supprimer une ou plusieurs extensions</a>
{% include "machines/aff_extension.html" with extension_list=extension_list %}
<h2>Liste des enregistrements SOA</h2>
{% if is_infra %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-soa' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un enregistrement SOA</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-soa' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un enregistrement SOA</a>
{% endif %}
{% can_create SOA %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-soa' %}"><i class="fa fa-plus"></i> Ajouter un enregistrement SOA</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-soa' %}"><i class="fa fa-trash"></i> Supprimer un enregistrement SOA</a>
{% include "machines/aff_soa.html" with soa_list=soa_list %}
<h2>Liste des enregistrements MX</h2>
{% if is_infra %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-mx' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un enregistrement MX</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-mx' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un enregistrement MX</a>
{% endif %}
{% can_create Mx %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-mx' %}"><i class="fa fa-plus"></i> Ajouter un enregistrement MX</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-mx' %}"><i class="fa fa-trash"></i> Supprimer un enregistrement MX</a>
{% include "machines/aff_mx.html" with mx_list=mx_list %}
<h2>Liste des enregistrements NS</h2>
{% if is_infra %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-ns' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un enregistrement NS</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-ns' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un enregistrement NS</a>
{% endif %}
{% can_create Ns %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-ns' %}"><i class="fa fa-plus"></i> Ajouter un enregistrement NS</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-ns' %}"><i class="fa fa-trash"></i> Supprimer un enregistrement NS</a>
{% include "machines/aff_ns.html" with ns_list=ns_list %}
<h2>Liste des enregistrements TXT</h2>
{% if is_infra %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-txt' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un enregistrement TXT</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-txt' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un enregistrement TXT</a>
{% endif %}
{% can_create Txt %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-txt' %}"><i class="fa fa-plus"></i> Ajouter un enregistrement TXT</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-txt' %}"><i class="fa fa-trash"></i> Supprimer un enregistrement TXT</a>
{% include "machines/aff_txt.html" with txt_list=txt_list %}
<h2>Liste des enregistrements SRV</h2>
{% if is_infra %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-srv' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un enregistrement SRV</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-srv' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un enregistrement SRV</a>
{% endif %}
{% can_create Srv %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-srv' %}"><i class="fa fa-plus"></i> Ajouter un enregistrement SRV</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-srv' %}"><i class="fa fa-trash"></i> Supprimer un enregistrement SRV</a>
{% include "machines/aff_srv.html" with srv_list=srv_list %}
<br />
<br />

View file

@ -25,14 +25,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load bootstrap3 %}
{% load acl %}
{% block title %}Ip{% endblock %}
{% block content %}
<h2>Liste des types d'ip</h2>
{% if is_infra %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-iptype' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un type d'ip</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-iptype' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs types d'ip</a>
{% endif %}
{% can_create IpType %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-iptype' %}"><i class="fa fa-plus"></i> Ajouter un type d'ip</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-iptype' %}"><i class="fa fa-trash"></i> Supprimer un ou plusieurs types d'ip</a>
{% include "machines/aff_iptype.html" with iptype_list=iptype_list %}
<br />
<br />

View file

@ -0,0 +1,41 @@
{% extends "machines/sidebar.html" %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% block title %}Machines{% endblock %}
{% block content %}
<h2>Liste des ipv6 de l'interface</h2>
{% can_create Ipv6List interface_id %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:new-ipv6list' interface_id %}"><i class="fa fa-plus"></i> Ajouter une ipv6</a>
{% acl_end %}
{% include "machines/aff_ipv6.html" with ipv6_list=ipv6_list %}
<br />
<br />
<br />
{% endblock %}

View file

@ -25,14 +25,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load bootstrap3 %}
{% load acl %}
{% block title %}Machines{% endblock %}
{% block content %}
<h2>Liste des types de machines</h2>
{% if is_infra %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-machinetype' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un type de machine</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-machinetype' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs types de machines</a>
{% endif %}
{% can_create MachineType %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-machinetype' %}"><i class="fa fa-plus"></i> Ajouter un type de machine</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-machinetype' %}"><i class="fa fa-trash"></i> Supprimer un ou plusieurs types de machines</a>
{% include "machines/aff_machinetype.html" with machinetype_list=machinetype_list %}
<br />
<br />

View file

@ -25,16 +25,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load bootstrap3 %}
{% load acl %}
{% block title %}Machines{% endblock %}
{% block content %}
<h2>Liste des nas</h2>
<h5>La correpondance nas-machinetype relie le type de nas à un type de machine.
Elle est utile pour l'autoenregistrement des macs par radius, et permet de choisir le type de machine à affecter aux machines en fonction du type de nas</h5>
{% if is_infra %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-nas' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un type de nas</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-nas' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs types nas</a>
{% endif %}
{% can_create Nas %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-nas' %}"><i class="fa fa-plus"></i> Ajouter un type de nas</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-nas' %}"><i class="fa fa-trash"></i> Supprimer un ou plusieurs types nas</a>
{% include "machines/aff_nas.html" with nas_list=nas_list %}
<br />
<br />

View file

@ -2,11 +2,15 @@
{% load bootstrap3 %}
{% load acl %}
{% block title %}Configuration de ports{% endblock %}
{% block content %}
<h2>Liste des configurations de ports</h2>
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-portlist' %}"><i class="glyphicon glyphicon-plus"></i>Ajouter une configuration</a>
{% can_create OuverturePortList %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-portlist' %}"><i class="fa fa-plus"></i>Ajouter une configuration</a>
{% acl_end %}
<table class="table table-striped">
<thead>
<tr>
@ -44,8 +48,12 @@
</div>
{% endif %}
<td class="text-right">
{% can_delete pl %}
{% include 'buttons/suppr.html' with href='machines:del-portlist' id=pl.id %}
{% acl_end %}
{% can_edit pl %}
{% include 'buttons/edit.html' with href='machines:edit-portlist' id=pl.id %}
{% acl_end %}
</td>
</tr>
{%endfor%}

View file

@ -24,15 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% block title %}Machines{% endblock %}
{% block content %}
<h2>Liste des services</h2>
{% if is_infra %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-service' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un service</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-service' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs service</a>
{% endif %}
{% can_create machines.Service %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-service' %}"><i class="fa fa-plus"></i> Ajouter un service</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-service' %}"><i class="fa fa-trash"></i> Supprimer un ou plusieurs service</a>
{% include "machines/aff_service.html" with service_list=service_list %}
<h2>Etat des serveurs</h2>
{% include "machines/aff_servers.html" with servers_list=servers_list %}

View file

@ -25,14 +25,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load bootstrap3 %}
{% load acl %}
{% block title %}Machines{% endblock %}
{% block content %}
<h2>Liste des vlans</h2>
{% if is_infra %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-vlan' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un vlan</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-vlan' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs vlan</a>
{% endif %}
{% can_create Vlan %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-vlan' %}"><i class="fa fa-plus"></i> Ajouter un vlan</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-vlan' %}"><i class="fa fa-trash"></i> Supprimer un ou plusieurs vlan</a>
{% include "machines/aff_vlan.html" with vlan_list=vlan_list %}
<br />
<br />

View file

@ -72,12 +72,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% if nasform %}
{% bootstrap_form_errors nasform %}
{% endif %}
{% if ipv6form %}
{% bootstrap_form_errors ipv6form %}
{% endif %}
<form class="form" method="post">
{% csrf_token %}
{% if machineform %}
<h3>Machine</h3>
{% bootstrap_form machineform %}
{% massive_bootstrap_form machineform 'user' %}
{% endif %}
{% if interfaceform %}
<h3>Interface</h3>
@ -139,7 +142,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<h3>NAS</h3>
{% bootstrap_form nasform %}
{% endif %}
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
{% if ipv6form %}
<h3>Ipv6</h3>
{% bootstrap_form ipv6form %}
{% endif %}
{% bootstrap_button action_name button_type="submit" icon="star" %}
</form>
<br />
<br />

View file

@ -23,42 +23,55 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
{% block sidebar %}
{% if is_cableur %}
{% can_view_all Machine %}
<a class="list-group-item list-group-item-info" href="{% url "machines:index" %}">
<i class="glyphicon glyphicon-list"></i>
<i class="fa fa-list-ul"></i>
Machines
</a>
{% acl_end %}
{% can_view_all MachineType %}
<a class="list-group-item list-group-item-info" href="{% url "machines:index-machinetype" %}">
<i class="glyphicon glyphicon-list"></i>
<i class="fa fa-list-ul"></i>
Types de machines
</a>
{% acl_end %}
{% can_view_all Extension %}
<a class="list-group-item list-group-item-info" href="{% url "machines:index-extension" %}">
<i class="glyphicon glyphicon-list"></i>
<i class="fa fa-list-ul"></i>
Extensions et zones
</a>
{% acl_end %}
{% can_view_all IpType %}
<a class="list-group-item list-group-item-info" href="{% url "machines:index-iptype" %}">
<i class="glyphicon glyphicon-list"></i>
<i class="fa fa-list-ul"></i>
Plages d'IP
</a>
{% acl_end %}
{% can_view_all Vlan %}
<a class="list-group-item list-group-item-info" href="{% url "machines:index-vlan" %}">
<i class="glyphicon glyphicon-list"></i>
<i class="fa fa-list-ul"></i>
Vlans
</a>
{% acl_end %}
{% can_view_all Nas %}
<a class="list-group-item list-group-item-info" href="{% url "machines:index-nas" %}">
<i class="glyphicon glyphicon-list"></i>
<i class="fa fa-list-ul"></i>
Gestion des nas
</a>
{% acl_end %}
{% can_view_all machines.Service %}
<a class="list-group-item list-group-item-info" href="{% url "machines:index-service" %}">
<i class="glyphicon glyphicon-list"></i>
<i class="fa fa-list-ul"></i>
Services (dhcp, dns...)
</a>
{% endif %}
{% if is_cableur %}
{% acl_end %}
{% can_view_all OuverturePortList %}
<a class="list-group-item list-group-item-info" href="{% url "machines:index-portlist" %}">
<i class="glyphicon glyphicon-list"></i>
<i class="fa fa-list-ul"></i>
Ouverture de ports
</a>
{%endif%}
{% acl_end %}
{% endblock %}

View file

@ -24,7 +24,7 @@
from __future__ import unicode_literals
from django.conf.urls import url
import re2o
from . import views
urlpatterns = [
@ -61,9 +61,13 @@ urlpatterns = [
url(r'^del_srv/$', views.del_srv, name='del-srv'),
url(r'^index_extension/$', views.index_extension, name='index-extension'),
url(r'^add_alias/(?P<interfaceid>[0-9]+)$', views.add_alias, name='add-alias'),
url(r'^edit_alias/(?P<aliasid>[0-9]+)$', views.edit_alias, name='edit-alias'),
url(r'^edit_alias/(?P<domainid>[0-9]+)$', views.edit_alias, name='edit-alias'),
url(r'^del_alias/(?P<interfaceid>[0-9]+)$', views.del_alias, name='del-alias'),
url(r'^index_alias/(?P<interfaceid>[0-9]+)$', views.index_alias, name='index-alias'),
url(r'^new_ipv6list/(?P<interfaceid>[0-9]+)$', views.new_ipv6list, name='new-ipv6list'),
url(r'^edit_ipv6list/(?P<ipv6listid>[0-9]+)$', views.edit_ipv6list, name='edit-ipv6list'),
url(r'^del_ipv6list/(?P<ipv6listid>[0-9]+)$', views.del_ipv6list, name='del-ipv6list'),
url(r'^index_ipv6/(?P<interfaceid>[0-9]+)$', views.index_ipv6, name='index-ipv6'),
url(r'^add_service/$', views.add_service, name='add-service'),
url(r'^edit_service/(?P<serviceid>[0-9]+)$', views.edit_service, name='edit-service'),
url(r'^del_service/$', views.del_service, name='del-service'),
@ -76,20 +80,12 @@ urlpatterns = [
url(r'^edit_nas/(?P<nasid>[0-9]+)$', views.edit_nas, name='edit-nas'),
url(r'^del_nas/$', views.del_nas, name='del-nas'),
url(r'^index_nas/$', views.index_nas, name='index-nas'),
url(r'^history/(?P<object>machine)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>interface)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>machinetype)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>extension)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>soa)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>mx)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>ns)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>txt)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>srv)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>iptype)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>alias)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>vlan)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>nas)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>service)/(?P<id>[0-9]+)$', views.history, name='history'),
url(
r'history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
re2o.views.history,
name='history',
kwargs={'application':'machines'},
),
url(r'^$', views.index, name='index'),
url(r'^rest/mac-ip/$', views.mac_ip, name='mac-ip'),
url(r'^rest/regen-achieved/$', views.regen_achieved, name='regen-achieved'),
@ -104,9 +100,9 @@ urlpatterns = [
url(r'^rest/service_servers/$', views.service_servers, name='service-servers'),
url(r'^rest/ouverture_ports/$', views.ouverture_ports, name='ouverture-ports'),
url(r'index_portlist/$', views.index_portlist, name='index-portlist'),
url(r'^edit_portlist/(?P<pk>[0-9]+)$', views.edit_portlist, name='edit-portlist'),
url(r'^del_portlist/(?P<pk>[0-9]+)$', views.del_portlist, name='del-portlist'),
url(r'^edit_portlist/(?P<ouvertureportlistid>[0-9]+)$', views.edit_portlist, name='edit-portlist'),
url(r'^del_portlist/(?P<ouvertureportlistid>[0-9]+)$', views.del_portlist, name='del-portlist'),
url(r'^add_portlist/$', views.add_portlist, name='add-portlist'),
url(r'^port_config/(?P<pk>[0-9]+)$', views.configure_ports, name='port-config'),
url(r'^port_config/(?P<interfaceid>[0-9]+)$', views.configure_ports, name='port-config'),
]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,2 @@
from .acl import *

40
preferences/acl.py Normal file
View file

@ -0,0 +1,40 @@
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""preferences.acl
Here are defined some functions to check acl on the application.
"""
def can_view(user):
"""Check if an user can view the application.
Args:
user: The user who wants to view the application.
Returns:
A couple (allowed, msg) where allowed is a boolean which is True if
viewing is granted and msg is a message (can be None).
"""
can = user.has_module_perms('preferences')
return can, None if can else "Vous ne pouvez pas voir cette application."

56
preferences/aes_field.py Normal file
View file

@ -0,0 +1,56 @@
import string
import binascii
from random import choice
from Crypto.Cipher import AES
from django.db import models
from django.conf import settings
EOD = '`%EofD%`' # This should be something that will not occur in strings
def genstring(length=16, chars=string.printable):
return ''.join([choice(chars) for i in range(length)])
def encrypt(key, s):
obj = AES.new(key)
datalength = len(s) + len(EOD)
if datalength < 16:
saltlength = 16 - datalength
else:
saltlength = 16 - datalength % 16
ss = ''.join([s, EOD, genstring(saltlength)])
return obj.encrypt(ss)
def decrypt(key, s):
obj = AES.new(key)
ss = obj.decrypt(s)
return ss.split(bytes(EOD, 'utf-8'))[0]
class AESEncryptedField(models.CharField):
def save_form_data(self, instance, data):
setattr(instance, self.name,
binascii.b2a_base64(encrypt(settings.AES_KEY, data)))
def to_python(self, value):
if value is None:
return None
return decrypt(settings.AES_KEY,
binascii.a2b_base64(value)).decode('utf-8')
def from_db_value(self, value, expression, connection, *args):
if value is None:
return value
return decrypt(settings.AES_KEY,
binascii.a2b_base64(value)).decode('utf-8')
def get_prep_value(self, value):
if value is None:
return value
return binascii.b2a_base64(encrypt(
settings.AES_KEY,
value
))

View file

@ -48,6 +48,9 @@ class EditOptionalUserForm(ModelForm):
téléphone'
self.fields['user_solde'].label = 'Activation du solde pour\
les utilisateurs'
self.fields['max_solde'].label = 'Solde maximum'
self.fields['min_online_payment'].label = 'Montant de rechargement minimum en ligne'
self.fields['self_adhesion'].label = 'Auto inscription'
class EditOptionalMachineForm(ModelForm):
@ -114,6 +117,7 @@ class EditGeneralOptionForm(ModelForm):
self.fields['site_name'].label = 'Nom du site web'
self.fields['email_from'].label = "Adresse mail d\
'expedition automatique"
self.fields['GTU_sum_up'].label = "Résumé des CGU"
class EditAssoOptionForm(ModelForm):
@ -173,7 +177,15 @@ class ServiceForm(ModelForm):
class DelServiceForm(Form):
"""Suppression de services sur la page d'accueil"""
services = forms.ModelMultipleChoiceField(
queryset=Service.objects.all(),
queryset=Service.objects.none(),
label="Enregistrements service actuels",
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelServiceForm, self).__init__(*args, **kwargs)
if instances:
self.fields['services'].queryset = instances
else:
self.fields['services'].queryset = Service.objects.all()

View file

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 20:42
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('preferences', '0024_optionaluser_all_can_create'),
]
operations = [
migrations.AlterModelOptions(
name='assooption',
options={'permissions': (('view_assooption', "Peut voir les options de l'asso"),)},
),
migrations.AlterModelOptions(
name='generaloption',
options={'permissions': (('view_generaloption', 'Peut voir les options générales'),)},
),
migrations.AlterModelOptions(
name='mailmessageoption',
options={'permissions': (('view_mailmessageoption', 'Peut voir les options de mail'),)},
),
migrations.AlterModelOptions(
name='optionalmachine',
options={'permissions': (('view_optionalmachine', 'Peut voir les options de machine'),)},
),
migrations.AlterModelOptions(
name='optionaltopologie',
options={'permissions': (('view_optionaltopologie', 'Peut voir les options de topologie'),)},
),
migrations.AlterModelOptions(
name='optionaluser',
options={'permissions': (('view_optionaluser', "Peut voir les options de l'user"),)},
),
migrations.AlterModelOptions(
name='service',
options={'permissions': (('view_service', 'Peut voir les options de service'),)},
),
]

View file

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-06 19:19
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('preferences', '0025_auto_20171231_2142'),
('preferences', '0026_auto_20171216_0401'),
]
operations = [
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-08 14:12
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0027_merge_20180106_2019'),
('preferences', '0043_optionalmachine_create_machine'),
]
operations = [
migrations.AddField(
model_name='assooption',
name='description',
field=models.TextField(default=''),
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-11 10:29
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0027_merge_20180106_2019'),
]
operations = [
migrations.AddField(
model_name='optionaluser',
name='max_recharge',
field=models.DecimalField(decimal_places=2, default=100, max_digits=5),
),
]

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-28 21:03
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0027_merge_20180106_2019'),
]
operations = [
migrations.RemoveField(
model_name='optionalmachine',
name='ipv6',
),
migrations.AddField(
model_name='optionalmachine',
name='ipv6_mode',
field=models.CharField(choices=[('SLAAC', 'Autoconfiguration par RA'), ('DHCPV6', 'Attribution des ip par dhcpv6'), ('DISABLED', 'Désactivé')], default='DISABLED', max_length=32),
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-11 10:34
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0028_auto_20180111_1129'),
]
operations = [
migrations.AddField(
model_name='assooption',
name='payment',
field=models.CharField(choices=[('NONE', 'NONE'), ('COMNPAY', 'COMNPAY')], default='NONE', max_length=255),
),
]

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-11 22:46
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0029_auto_20180111_1134'),
]
operations = [
migrations.RemoveField(
model_name='optionaluser',
name='max_recharge',
),
migrations.AddField(
model_name='optionaluser',
name='max_solde',
field=models.DecimalField(decimal_places=2, default=50, max_digits=5),
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-12 11:34
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0030_auto_20180111_2346'),
]
operations = [
migrations.AddField(
model_name='optionaluser',
name='self_adhesion',
field=models.BooleanField(default=False, help_text='Un nouvel utilisateur peut se créer son compte sur re2o'),
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-13 16:43
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0031_optionaluser_self_adhesion'),
]
operations = [
migrations.AddField(
model_name='optionaluser',
name='min_online_payment',
field=models.DecimalField(decimal_places=2, default=10, max_digits=5),
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-14 19:12
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0032_optionaluser_min_online_payment'),
]
operations = [
migrations.AddField(
model_name='generaloption',
name='GTU_sum_up',
field=models.TextField(blank=True, default='', help_text='Résumé des CGU'),
),
]

View file

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-14 19:25
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0033_generaloption_gtu_sum_up'),
]
operations = [
migrations.AddField(
model_name='generaloption',
name='GTU',
field=models.FileField(default='', upload_to='GTU'),
),
migrations.AlterField(
model_name='generaloption',
name='GTU_sum_up',
field=models.TextField(blank=True, default=''),
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-14 20:32
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0034_auto_20180114_2025'),
]
operations = [
migrations.AlterField(
model_name='generaloption',
name='GTU',
field=models.FileField(default='', upload_to='/var/www/static/'),
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-14 20:41
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0035_auto_20180114_2132'),
]
operations = [
migrations.AlterField(
model_name='generaloption',
name='GTU',
field=models.FileField(default='', upload_to=''),
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-14 20:56
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0036_auto_20180114_2141'),
]
operations = [
migrations.AlterField(
model_name='generaloption',
name='GTU',
field=models.FileField(default='', null=True, upload_to=''),
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-14 21:09
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0037_auto_20180114_2156'),
]
operations = [
migrations.AlterField(
model_name='generaloption',
name='GTU',
field=models.FileField(blank=True, default='', null=True, upload_to=''),
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-14 23:03
from __future__ import unicode_literals
from django.db import migrations, models
import preferences.aes_field
class Migration(migrations.Migration):
dependencies = [
('preferences', '0038_auto_20180114_2209'),
]
operations = [
migrations.AddField(
model_name='assooption',
name='payment_id',
field=models.CharField(max_length=255, null=True),
),
]

View file

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-01-29 16:45
from __future__ import unicode_literals
from django.db import migrations, models
import preferences.aes_field
class Migration(migrations.Migration):
dependencies = [
('preferences', '0039_auto_20180115_0003'),
]
operations = [
migrations.AddField(
model_name='assooption',
name='payment_pass',
field=preferences.aes_field.AESEncryptedField(blank=True, max_length=255, null=True),
),
migrations.AlterField(
model_name='assooption',
name='payment_id',
field=models.CharField(default='', max_length=255),
),
]

Some files were not shown because too many files have changed in this diff Show more