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

Merge branch 'master' into graph_topo

This commit is contained in:
chirac 2018-04-15 21:22:47 +02:00
commit 8e057db5ea
96 changed files with 4512 additions and 2178 deletions

View file

@ -29,20 +29,20 @@ from machines.models import (
IpType, IpType,
Extension, Extension,
IpList, IpList,
MachineType,
Domain, Domain,
Txt, Txt,
Mx, Mx,
Srv, Srv,
Service_link, Service_link,
Ns, Ns,
OuverturePortList,
OuverturePort, OuverturePort,
Ipv6List Ipv6List
) )
class ServiceLinkSerializer(serializers.ModelSerializer): class ServiceLinkSerializer(serializers.ModelSerializer):
""" Serializer for the ServiceLink objects """
name = serializers.CharField(source='service.service_type') name = serializers.CharField(source='service.service_type')
class Meta: class Meta:
@ -51,6 +51,8 @@ class ServiceLinkSerializer(serializers.ModelSerializer):
class MailingSerializer(serializers.ModelSerializer): class MailingSerializer(serializers.ModelSerializer):
""" Serializer to build Mailing objects """
name = serializers.CharField(source='pseudo') name = serializers.CharField(source='pseudo')
class Meta: class Meta:
@ -59,20 +61,27 @@ class MailingSerializer(serializers.ModelSerializer):
class MailingMemberSerializer(serializers.ModelSerializer): class MailingMemberSerializer(serializers.ModelSerializer):
""" Serializer fot the Adherent objects (who belong to a
Mailing) """
class Meta: class Meta:
model = Adherent model = Adherent
fields = ('email', 'name', 'surname', 'pseudo',) fields = ('email',)
class IpTypeField(serializers.RelatedField): class IpTypeField(serializers.RelatedField):
"""Serialisation d'une iptype, renvoie son evaluation str""" """ Serializer for an IpType object field """
def to_representation(self, value): def to_representation(self, value):
return value.type return value.type
def to_internal_value(self, data):
pass
class IpListSerializer(serializers.ModelSerializer): class IpListSerializer(serializers.ModelSerializer):
"""Serialisation d'une iplist, ip_type etant une foreign_key, """ Serializer for an Ipv4List obejct using the IpType serialization """
on evalue sa methode str"""
ip_type = IpTypeField(read_only=True) ip_type = IpTypeField(read_only=True)
class Meta: class Meta:
@ -81,16 +90,19 @@ class IpListSerializer(serializers.ModelSerializer):
class Ipv6ListSerializer(serializers.ModelSerializer): class Ipv6ListSerializer(serializers.ModelSerializer):
""" Serializer for an Ipv6List object """
class Meta: class Meta:
model = Ipv6List model = Ipv6List
fields = ('ipv6', 'slaac_ip') fields = ('ipv6', 'slaac_ip')
class InterfaceSerializer(serializers.ModelSerializer): class InterfaceSerializer(serializers.ModelSerializer):
"""Serialisation d'une interface, ipv4, domain et extension sont """ Serializer for an Interface object. Use SerializerMethodField
des foreign_key, on les override et on les evalue avec des fonctions to get ForeignKey values """
get_..."""
ipv4 = IpListSerializer(read_only=True) ipv4 = IpListSerializer(read_only=True)
# TODO : use serializer.RelatedField to avoid duplicate code
mac_address = serializers.SerializerMethodField('get_macaddress') mac_address = serializers.SerializerMethodField('get_macaddress')
domain = serializers.SerializerMethodField('get_dns') domain = serializers.SerializerMethodField('get_dns')
extension = serializers.SerializerMethodField('get_interface_extension') extension = serializers.SerializerMethodField('get_interface_extension')
@ -99,20 +111,29 @@ class InterfaceSerializer(serializers.ModelSerializer):
model = Interface model = Interface
fields = ('ipv4', 'mac_address', 'domain', 'extension') fields = ('ipv4', 'mac_address', 'domain', 'extension')
def get_dns(self, obj): @staticmethod
def get_dns(obj):
""" The name of the associated DNS object """
return obj.domain.name return obj.domain.name
def get_interface_extension(self, obj): @staticmethod
def get_interface_extension(obj):
""" The name of the associated Interface object """
return obj.domain.extension.name return obj.domain.extension.name
def get_macaddress(self, obj): @staticmethod
def get_macaddress(obj):
""" The string representation of the associated MAC address """
return str(obj.mac_address) return str(obj.mac_address)
class FullInterfaceSerializer(serializers.ModelSerializer): class FullInterfaceSerializer(serializers.ModelSerializer):
"""Serialisation complete d'une interface avec les ipv6 en plus""" """ Serializer for an Interface obejct. Use SerializerMethodField
to get ForeignKey values """
ipv4 = IpListSerializer(read_only=True) ipv4 = IpListSerializer(read_only=True)
ipv6 = Ipv6ListSerializer(read_only=True, many=True) ipv6 = Ipv6ListSerializer(read_only=True, many=True)
# TODO : use serializer.RelatedField to avoid duplicate code
mac_address = serializers.SerializerMethodField('get_macaddress') mac_address = serializers.SerializerMethodField('get_macaddress')
domain = serializers.SerializerMethodField('get_dns') domain = serializers.SerializerMethodField('get_dns')
extension = serializers.SerializerMethodField('get_interface_extension') extension = serializers.SerializerMethodField('get_interface_extension')
@ -121,26 +142,36 @@ class FullInterfaceSerializer(serializers.ModelSerializer):
model = Interface model = Interface
fields = ('ipv4', 'ipv6', 'mac_address', 'domain', 'extension') fields = ('ipv4', 'ipv6', 'mac_address', 'domain', 'extension')
def get_dns(self, obj): @staticmethod
def get_dns(obj):
""" The name of the associated DNS object """
return obj.domain.name return obj.domain.name
def get_interface_extension(self, obj): @staticmethod
def get_interface_extension(obj):
""" The name of the associated Extension object """
return obj.domain.extension.name return obj.domain.extension.name
def get_macaddress(self, obj): @staticmethod
def get_macaddress(obj):
""" The string representation of the associated MAC address """
return str(obj.mac_address) return str(obj.mac_address)
class ExtensionNameField(serializers.RelatedField): class ExtensionNameField(serializers.RelatedField):
"""Evaluation str d'un objet extension (.example.org)""" """ Serializer for Extension object field """
def to_representation(self, value): def to_representation(self, value):
return value.name return value.name
def to_internal_value(self, data):
pass
class TypeSerializer(serializers.ModelSerializer): class TypeSerializer(serializers.ModelSerializer):
"""Serialisation d'un iptype : extension et la liste des """ Serializer for an IpType object. Use SerializerMethodField to
ouvertures de port son evalués en get_... etant des get ForeignKey values """
foreign_key ou des relations manytomany"""
extension = ExtensionNameField(read_only=True) extension = ExtensionNameField(read_only=True)
ouverture_ports_tcp_in = serializers\ ouverture_ports_tcp_in = serializers\
.SerializerMethodField('get_port_policy_input_tcp') .SerializerMethodField('get_port_policy_input_tcp')
@ -158,7 +189,10 @@ class TypeSerializer(serializers.ModelSerializer):
'ouverture_ports_tcp_in', 'ouverture_ports_tcp_out', 'ouverture_ports_tcp_in', 'ouverture_ports_tcp_out',
'ouverture_ports_udp_in', 'ouverture_ports_udp_out',) 'ouverture_ports_udp_in', 'ouverture_ports_udp_out',)
def get_port_policy(self, obj, protocole, io): @staticmethod
def get_port_policy(obj, protocole, io):
""" Generic utility function to get the policy for a given
port, protocole and IN or OUT """
if obj.ouverture_ports is None: if obj.ouverture_ports is None:
return [] return []
return map( return map(
@ -196,14 +230,20 @@ class ExtensionSerializer(serializers.ModelSerializer):
model = Extension model = Extension
fields = ('name', 'origin', 'origin_v6', 'zone_entry', 'soa') fields = ('name', 'origin', 'origin_v6', 'zone_entry', 'soa')
def get_origin_ip(self, obj): @staticmethod
def get_origin_ip(obj):
""" The IP of the associated origin for the zone """
return obj.origin.ipv4 return obj.origin.ipv4
def get_zone_name(self, obj): @staticmethod
def get_zone_name(obj):
""" The name of the associated zone """
return str(obj.dns_entry) return str(obj.dns_entry)
def get_soa_data(self, obj): @staticmethod
return { 'mail': obj.soa.dns_soa_mail, 'param': obj.soa.dns_soa_param } def get_soa_data(obj):
""" The representation of the associated SOA """
return {'mail': obj.soa.dns_soa_mail, 'param': obj.soa.dns_soa_param}
class MxSerializer(serializers.ModelSerializer): class MxSerializer(serializers.ModelSerializer):
@ -217,13 +257,19 @@ class MxSerializer(serializers.ModelSerializer):
model = Mx model = Mx
fields = ('zone', 'priority', 'name', 'mx_entry') fields = ('zone', 'priority', 'name', 'mx_entry')
def get_entry_name(self, obj): @staticmethod
def get_entry_name(obj):
""" The name of the DNS MX entry """
return str(obj.name) return str(obj.name)
def get_zone_name(self, obj): @staticmethod
def get_zone_name(obj):
""" The name of the associated zone of the MX record """
return obj.zone.name return obj.zone.name
def get_mx_name(self, obj): @staticmethod
def get_mx_name(obj):
""" The string representation of the entry to add to the DNS """
return str(obj.dns_entry) return str(obj.dns_entry)
@ -237,10 +283,14 @@ class TxtSerializer(serializers.ModelSerializer):
model = Txt model = Txt
fields = ('zone', 'txt_entry', 'field1', 'field2') fields = ('zone', 'txt_entry', 'field1', 'field2')
def get_zone_name(self, obj): @staticmethod
def get_zone_name(obj):
""" The name of the associated zone """
return str(obj.zone.name) return str(obj.zone.name)
def get_txt_name(self, obj): @staticmethod
def get_txt_name(obj):
""" The string representation of the entry to add to the DNS """
return str(obj.dns_entry) return str(obj.dns_entry)
@ -263,10 +313,14 @@ class SrvSerializer(serializers.ModelSerializer):
'srv_entry' 'srv_entry'
) )
def get_extension_name(self, obj): @staticmethod
def get_extension_name(obj):
""" The name of the associated extension """
return str(obj.extension.name) return str(obj.extension.name)
def get_srv_name(self, obj): @staticmethod
def get_srv_name(obj):
""" The string representation of the entry to add to the DNS """
return str(obj.dns_entry) return str(obj.dns_entry)
@ -281,13 +335,19 @@ class NsSerializer(serializers.ModelSerializer):
model = Ns model = Ns
fields = ('zone', 'ns', 'ns_entry') fields = ('zone', 'ns', 'ns_entry')
def get_zone_name(self, obj): @staticmethod
def get_zone_name(obj):
""" The name of the associated zone """
return obj.zone.name return obj.zone.name
def get_domain_name(self, obj): @staticmethod
def get_domain_name(obj):
""" The name of the associated NS target """
return str(obj.ns) return str(obj.ns)
def get_text_name(self, obj): @staticmethod
def get_text_name(obj):
""" The string representation of the entry to add to the DNS """
return str(obj.dns_entry) return str(obj.dns_entry)
@ -302,13 +362,19 @@ class DomainSerializer(serializers.ModelSerializer):
model = Domain model = Domain
fields = ('name', 'extension', 'cname', 'cname_entry') fields = ('name', 'extension', 'cname', 'cname_entry')
def get_zone_name(self, obj): @staticmethod
def get_zone_name(obj):
""" The name of the associated zone """
return obj.extension.name return obj.extension.name
def get_alias_name(self, obj): @staticmethod
def get_alias_name(obj):
""" The name of the associated alias """
return str(obj.cname) return str(obj.cname)
def get_cname_name(self, obj): @staticmethod
def get_cname_name(obj):
""" The name of the associated CNAME target """
return str(obj.dns_entry) return str(obj.dns_entry)
@ -322,13 +388,19 @@ class ServicesSerializer(serializers.ModelSerializer):
model = Service_link model = Service_link
fields = ('server', 'service', 'need_regen') fields = ('server', 'service', 'need_regen')
def get_server_name(self, obj): @staticmethod
def get_server_name(obj):
""" The name of the associated server """
return str(obj.server.domain.name) return str(obj.server.domain.name)
def get_service_name(self, obj): @staticmethod
def get_service_name(obj):
""" The name of the service name """
return str(obj.service) return str(obj.service)
def get_regen_status(self, obj): @staticmethod
def get_regen_status(obj):
""" The string representation of the regen status """
return obj.need_regen() return obj.need_regen()
@ -337,24 +409,38 @@ class OuverturePortsSerializer(serializers.Serializer):
ipv4 = serializers.SerializerMethodField() ipv4 = serializers.SerializerMethodField()
ipv6 = serializers.SerializerMethodField() ipv6 = serializers.SerializerMethodField()
def create(self, validated_data):
""" Creates a new object based on the un-serialized data.
Used to implement an abstract inherited method """
pass
def update(self, instance, validated_data):
""" Updates an object based on the un-serialized data.
Used to implement an abstract inherited method """
pass
@staticmethod
def get_ipv4(): def get_ipv4():
return {i.ipv4.ipv4: """ The representation of the policy for the IPv4 addresses """
{ return {
"tcp_in":[j.tcp_ports_in() for j in i.port_lists.all()], i.ipv4.ipv4: {
"tcp_out":[j.tcp_ports_out()for j in i.port_lists.all()], "tcp_in": [j.tcp_ports_in() for j in i.port_lists.all()],
"udp_in":[j.udp_ports_in() for j in i.port_lists.all()], "tcp_out": [j.tcp_ports_out()for j in i.port_lists.all()],
"udp_out":[j.udp_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 for i in Interface.objects.all() if i.ipv4
} }
@staticmethod
def get_ipv6(): def get_ipv6():
return {i.ipv6: """ The representation of the policy for the IPv6 addresses """
{ return {
"tcp_in":[j.tcp_ports_in() for j in i.port_lists.all()], i.ipv6: {
"tcp_out":[j.tcp_ports_out()for j in i.port_lists.all()], "tcp_in": [j.tcp_ports_in() for j in i.port_lists.all()],
"udp_in":[j.udp_ports_in() for j in i.port_lists.all()], "tcp_out": [j.tcp_ports_out()for j in i.port_lists.all()],
"udp_out":[j.udp_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 for i in Interface.objects.all() if i.ipv6
} }

View file

@ -19,7 +19,10 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""api.tests
The tests for the API module.
"""
from django.test import TestCase # from django.test import TestCase
# Create your tests here. # Create your tests here.

View file

@ -32,7 +32,10 @@ from . import views
urlpatterns = [ urlpatterns = [
# Services # Services
url(r'^services/$', views.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+)/(?P<service_name>\w+)/regen/$',
views.services_server_service_regen
),
url(r'^services/(?P<server_name>\w+)/$', views.services_server), url(r'^services/(?P<server_name>\w+)/$', views.services_server),
# DNS # DNS
@ -56,7 +59,13 @@ urlpatterns = [
# Mailings # Mailings
url(r'^mailing/standard/$', views.mailing_standard), url(r'^mailing/standard/$', views.mailing_standard),
url(r'^mailing/standard/(?P<ml_name>\w+)/members/$', views.mailing_standard_ml_members), 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/$', views.mailing_club),
url(r'^mailing/club/(?P<ml_name>\w+)/members/$', views.mailing_club_ml_members), url(
r'^mailing/club/(?P<ml_name>\w+)/members/$',
views.mailing_club_ml_members
),
] ]

View file

@ -26,6 +26,7 @@ Set of various and usefull functions for the API app
from rest_framework.renderers import JSONRenderer from rest_framework.renderers import JSONRenderer
from django.http import HttpResponse from django.http import HttpResponse
class JSONResponse(HttpResponse): class JSONResponse(HttpResponse):
"""A JSON response that can be send as an HTTP response. """A JSON response that can be send as an HTTP response.
Usefull in case of REST API. Usefull in case of REST API.
@ -51,23 +52,23 @@ class JSONResponse(HttpResponse):
class JSONError(JSONResponse): class JSONError(JSONResponse):
"""A JSON response when the request failed. """A JSON response when the request failed.
""" """
def __init__(self, error_msg, data=None, **kwargs): def __init__(self, error_msg, data=None, **kwargs):
"""Initialise a JSONError object. """Initialise a JSONError object.
Args: Args:
error_msg: A message explaining where the error is. error_msg: A message explaining where the error is.
data: An optional field for further data to send along. data: An optional field for further data to send along.
Creates: Creates:
A JSONResponse containing a field `status` set to `error` and a field A JSONResponse containing a field `status` set to `error` and a
`reason` containing `error_msg`. If `data` argument has been given, field `reason` containing `error_msg`. If `data` argument has been
a field `data` containing it is added to the JSON response. given, a field `data` containing it is added to the JSON response.
""" """
response = { response = {
'status' : 'error', 'status': 'error',
'reason' : error_msg 'reason': error_msg
} }
if data is not None: if data is not None:
response['data'] = data response['data'] = data
@ -77,22 +78,22 @@ class JSONError(JSONResponse):
class JSONSuccess(JSONResponse): class JSONSuccess(JSONResponse):
"""A JSON response when the request suceeded. """A JSON response when the request suceeded.
""" """
def __init__(self, data=None, **kwargs): def __init__(self, data=None, **kwargs):
"""Initialise a JSONSucess object. """Initialise a JSONSucess object.
Args: Args:
error_msg: A message explaining where the error is. error_msg: A message explaining where the error is.
data: An optional field for further data to send along. data: An optional field for further data to send along.
Creates: Creates:
A JSONResponse containing a field `status` set to `sucess`. If `data` A JSONResponse containing a field `status` set to `sucess`. If
argument has been given, a field `data` containing it is added to the `data` argument has been given, a field `data` containing it is
JSON response. added to the JSON response.
""" """
response = { response = {
'status' : 'success', 'status': 'success',
} }
if data is not None: if data is not None:
response['data'] = data response['data'] = data
@ -103,12 +104,20 @@ def accept_method(methods):
"""Decorator to set a list of accepted request method. """Decorator to set a list of accepted request method.
Check if the method used is accepted. If not, send a NotAllowed response. Check if the method used is accepted. If not, send a NotAllowed response.
""" """
def decorator(view): def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
if request.method in methods: if request.method in methods:
return view(request, *args, **kwargs) return view(request, *args, **kwargs)
else: else:
return JSONError('Invalid request method. Request methods authorize are '+str(methods)) return JSONError(
'Invalid request method. Request methods authorize are ' +
str(methods)
)
return view(request, *args, **kwargs) return view(request, *args, **kwargs)
return wrapper return wrapper
return decorator return decorator

View file

@ -27,13 +27,42 @@ 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.contrib.auth.decorators import login_required, permission_required
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from re2o.utils import all_has_access, all_active_assigned_interfaces from re2o.utils import (
all_has_access,
all_active_assigned_interfaces,
filter_active_interfaces
)
from users.models import Club from users.models import Club
from machines.models import (Service_link, Service, Interface, Domain, from machines.models import (
OuverturePortList) Service_link,
Service,
Interface,
Domain,
IpType,
Mx,
Ns,
Txt,
Srv,
Extension,
OuverturePortList,
OuverturePort
)
from .serializers import * from .serializers import (
ServicesSerializer,
ServiceLinkSerializer,
FullInterfaceSerializer,
DomainSerializer,
TypeSerializer,
MxSerializer,
NsSerializer,
TxtSerializer,
SrvSerializer,
ExtensionSerializer,
InterfaceSerializer,
MailingMemberSerializer,
MailingSerializer
)
from .utils import JSONError, JSONSuccess, accept_method from .utils import JSONError, JSONSuccess, accept_method
@ -41,21 +70,26 @@ from .utils import JSONError, JSONSuccess, accept_method
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@accept_method(['GET']) @accept_method(['GET'])
def services(request): def services(_request):
"""The list of the different services and servers couples """The list of the different services and servers couples
Return: Return:
GET: GET:
A JSONSuccess response with a field `data` containing: A JSONSuccess response with a field `data` containing:
* a list of dictionnaries (one for each service-server couple) containing: * a list of dictionnaries (one for each service-server couple)
containing:
* a field `server`: the server name * a field `server`: the server name
* a field `service`: the service name * a field `service`: the service name
* a field `need_regen`: does the service need a regeneration ? * a field `need_regen`: does the service need a regeneration ?
""" """
service_link = Service_link.objects.all().select_related('server__domain').select_related('service')
service_link = (Service_link.objects.all()
.select_related('server__domain')
.select_related('service'))
seria = ServicesSerializer(service_link, many=True) seria = ServicesSerializer(service_link, many=True)
return JSONSuccess(seria.data) return JSONSuccess(seria.data)
@csrf_exempt @csrf_exempt
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@ -72,6 +106,7 @@ def services_server_service_regen(request, server_name, service_name):
POST: POST:
An empty JSONSuccess response. An empty JSONSuccess response.
""" """
query = Service_link.objects.filter( query = Service_link.objects.filter(
service__in=Service.objects.filter(service_type=service_name), service__in=Service.objects.filter(service_type=service_name),
server__in=Interface.objects.filter( server__in=Interface.objects.filter(
@ -80,7 +115,7 @@ def services_server_service_regen(request, server_name, service_name):
) )
if not query: if not query:
return JSONError("This service is not active for this server") return JSONError("This service is not active for this server")
service = query.first() service = query.first()
if request.method == 'GET': if request.method == 'GET':
return JSONSuccess({'need_regen': service.need_regen()}) return JSONSuccess({'need_regen': service.need_regen()})
@ -93,7 +128,7 @@ def services_server_service_regen(request, server_name, service_name):
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@accept_method(['GET']) @accept_method(['GET'])
def services_server(request, server_name): def services_server(_request, server_name):
"""The list of services attached to a specific server """The list of services attached to a specific server
Returns: Returns:
@ -102,6 +137,7 @@ def services_server(request, server_name):
* a list of dictionnaries (one for each service) containing: * a list of dictionnaries (one for each service) containing:
* a field `name`: the name of a service * a field `name`: the name of a service
""" """
query = Service_link.objects.filter( query = Service_link.objects.filter(
server__in=Interface.objects.filter( server__in=Interface.objects.filter(
domain__in=Domain.objects.filter(name=server_name) domain__in=Domain.objects.filter(name=server_name)
@ -109,9 +145,9 @@ def services_server(request, server_name):
) )
if not query: if not query:
return JSONError("This service is not active for this server") return JSONError("This service is not active for this server")
services = query.all() services_objects = query.all()
seria = ServiceLinkSerializer(services, many=True) seria = ServiceLinkSerializer(services_objects, many=True)
return JSONSuccess(seria.data) return JSONSuccess(seria.data)
@ -119,7 +155,7 @@ def services_server(request, server_name):
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@accept_method(['GET']) @accept_method(['GET'])
def dns_mac_ip_dns(request): def dns_mac_ip_dns(_request):
"""The list of all active interfaces with all the associated infos """The list of all active interfaces with all the associated infos
(MAC, IP, IpType, DNS name and associated zone extension) (MAC, IP, IpType, DNS name and associated zone extension)
@ -135,8 +171,10 @@ def dns_mac_ip_dns(request):
* a field `ip_type`: the name of the IpType of 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 `mac_address`: the MAC of this interface
* a field `domain`: the DNS name for this interface * a field `domain`: the DNS name for this interface
* a field `extension`: the extension for the DNS zone of this interface * a field `extension`: the extension for the DNS zone of this
interface
""" """
interfaces = all_active_assigned_interfaces(full=True) interfaces = all_active_assigned_interfaces(full=True)
seria = FullInterfaceSerializer(interfaces, many=True) seria = FullInterfaceSerializer(interfaces, many=True)
return JSONSuccess(seria.data) return JSONSuccess(seria.data)
@ -146,7 +184,7 @@ def dns_mac_ip_dns(request):
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@accept_method(['GET']) @accept_method(['GET'])
def dns_alias(request): def dns_alias(_request):
"""The list of all the alias used and the DNS info associated """The list of all the alias used and the DNS info associated
Returns: Returns:
@ -154,11 +192,23 @@ def dns_alias(request):
A JSON Success response with a field `data` containing: A JSON Success response with a field `data` containing:
* a list of dictionnaries (one for each alias) containing: * a list of dictionnaries (one for each alias) containing:
* a field `name`: the alias used * a field `name`: the alias used
* a field `cname`: the target of the alias (real name of the interface) * a field `cname`: the target of the alias (real name of the
* a field `cname_entry`: the entry to write in the DNS to have the alias interface)
* a field `extension`: the extension for the DNS zone of this 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')
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) seria = DomainSerializer(alias, many=True)
return JSONSuccess(seria.data) return JSONSuccess(seria.data)
@ -167,7 +217,7 @@ def dns_alias(request):
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@accept_method(['GET']) @accept_method(['GET'])
def accesspoint_ip_dns(request): def accesspoint_ip_dns(_request):
"""The list of all active interfaces with all the associated infos """The list of all active interfaces with all the associated infos
(MAC, IP, IpType, DNS name and associated zone extension) (MAC, IP, IpType, DNS name and associated zone extension)
@ -185,10 +235,12 @@ def accesspoint_ip_dns(request):
* a field `ip_type`: the name of the IpType of 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 `mac_address`: the MAC of this interface
* a field `domain`: the DNS name for this interface * a field `domain`: the DNS name for this interface
* a field `extension`: the extension for the DNS zone of this interface * a field `extension`: the extension for the DNS zone of this
interface
""" """
interfaces = all_active_assigned_interfaces(full=True)\
.filter(machine__accesspoint__isnull=False) interfaces = (all_active_assigned_interfaces(full=True)
.filter(machine__accesspoint__isnull=False))
seria = FullInterfaceSerializer(interfaces, many=True) seria = FullInterfaceSerializer(interfaces, many=True)
return JSONSuccess(seria.data) return JSONSuccess(seria.data)
@ -197,7 +249,7 @@ def accesspoint_ip_dns(request):
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@accept_method(['GET']) @accept_method(['GET'])
def dns_corresp(request): def dns_corresp(_request):
"""The list of the IpTypes possible with the infos about each """The list of the IpTypes possible with the infos about each
Returns: Returns:
@ -208,12 +260,14 @@ def dns_corresp(request):
* a field `extension`: the DNS extension associated * a field `extension`: the DNS extension associated
* a field `domain_ip_start`: the first ip to use for this type * 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 `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 `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_in`: the policy for TCP IN ports
* a field `ouverture_ports_tcp_out`: the policy for TCP OUT 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_in`: the policy for UDP IN ports
* a field `ouverture_ports_udp_out`: the policy for UDP OUT ports * a field `ouverture_ports_udp_out`: the policy for UDP OUT ports
""" """
ip_type = IpType.objects.all().select_related('extension') ip_type = IpType.objects.all().select_related('extension')
seria = TypeSerializer(ip_type, many=True) seria = TypeSerializer(ip_type, many=True)
return JSONSuccess(seria.data) return JSONSuccess(seria.data)
@ -223,7 +277,7 @@ def dns_corresp(request):
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@accept_method(['GET']) @accept_method(['GET'])
def dns_mx(request): def dns_mx(_request):
"""The list of MX record to add to the DNS """The list of MX record to add to the DNS
Returns: Returns:
@ -233,9 +287,13 @@ def dns_mx(request):
* a field `zone`: the extension for the concerned zone * a field `zone`: the extension for the concerned zone
* a field `priority`: the priority to use * a field `priority`: the priority to use
* a field `name`: the name of the target * a field `name`: the name of the target
* a field `mx_entry`: the full entry to add in the DNS for this MX record * 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')
mx = (Mx.objects.all()
.select_related('zone')
.select_related('name__extension'))
seria = MxSerializer(mx, many=True) seria = MxSerializer(mx, many=True)
return JSONSuccess(seria.data) return JSONSuccess(seria.data)
@ -244,7 +302,7 @@ def dns_mx(request):
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@accept_method(['GET']) @accept_method(['GET'])
def dns_ns(request): def dns_ns(_request):
"""The list of NS record to add to the DNS """The list of NS record to add to the DNS
Returns: Returns:
@ -253,9 +311,18 @@ def dns_ns(request):
* a list of dictionnaries (one for each NS record) containing: * a list of dictionnaries (one for each NS record) containing:
* a field `zone`: the extension for the concerned zone * a field `zone`: the extension for the concerned zone
* a field `ns`: the DNS name for the NS server targeted * 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 * 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')
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) seria = NsSerializer(ns, many=True)
return JSONSuccess(seria.data) return JSONSuccess(seria.data)
@ -264,7 +331,7 @@ def dns_ns(request):
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@accept_method(['GET']) @accept_method(['GET'])
def dns_txt(request): def dns_txt(_request):
"""The list of TXT record to add to the DNS """The list of TXT record to add to the DNS
Returns: Returns:
@ -274,8 +341,10 @@ def dns_txt(request):
* a field `zone`: the extension for the concerned zone * a field `zone`: the extension for the concerned zone
* a field `field1`: the first field in the record (target) * a field `field1`: the first field in the record (target)
* a field `field2`: the second field in the record (value) * 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 * a field `txt_entry`: the full entry to add in the DNS for this
TXT record
""" """
txt = Txt.objects.all().select_related('zone') txt = Txt.objects.all().select_related('zone')
seria = TxtSerializer(txt, many=True) seria = TxtSerializer(txt, many=True)
return JSONSuccess(seria.data) return JSONSuccess(seria.data)
@ -285,7 +354,7 @@ def dns_txt(request):
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@accept_method(['GET']) @accept_method(['GET'])
def dns_srv(request): def dns_srv(_request):
"""The list of SRV record to add to the DNS """The list of SRV record to add to the DNS
Returns: Returns:
@ -300,9 +369,13 @@ def dns_srv(request):
* a field `weight`: the weight for same priority entries * a field `weight`: the weight for same priority entries
* a field `port`: the port targeted * a field `port`: the port targeted
* a field `target`: the interface targeted by this service * 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 * 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')
srv = (Srv.objects.all()
.select_related('extension')
.select_related('target__extension'))
seria = SrvSerializer(srv, many=True) seria = SrvSerializer(srv, many=True)
return JSONSuccess(seria.data) return JSONSuccess(seria.data)
@ -311,8 +384,8 @@ def dns_srv(request):
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@accept_method(['GET']) @accept_method(['GET'])
def dns_zones(request): def dns_zones(_request):
"""The list of the zones managed """The list of the zones managed
Returns: Returns:
GET: GET:
@ -320,21 +393,27 @@ def dns_zones(request):
* a list of dictionnaries (one for each zone) containing: * a list of dictionnaries (one for each zone) containing:
* a field `name`: the extension for the zone * a field `name`: the extension for the zone
* a field `origin`: the server IPv4 for the orgin of 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 `origin_v6`: `null` if ipv6 is deactivated else the
server IPv6 for the origin of the zone
* a field `soa` containing: * a field `soa` containing:
* a field `mail` containing the mail to contact in case of problem with the zone * a field `mail` containing the mail to contact in case of
* a field `param` containing the full soa paramters to use in the DNS for this zone problem with the zone
* a field `zone_entry`: the full entry to add in the DNS for the origin of 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') zones = Extension.objects.all().select_related('origin')
seria = ExtensionSerializer(zones, many=True) seria = ExtensionSerializer(zones, many=True)
return JSONSuccess(seria.data) return JSONSuccess(seria.data)
@csrf_exempt @csrf_exempt
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@accept_method(['GET']) @accept_method(['GET'])
def firewall_ouverture_ports(request): def firewall_ouverture_ports(_request):
"""The list of the ports authorized to be openned by the firewall """The list of the ports authorized to be openned by the firewall
Returns: Returns:
@ -359,37 +438,73 @@ def firewall_ouverture_ports(request):
* a field `udp_out` containing: * a field `udp_out` containing:
* a list of port number where ipv6 udp out should be ok * 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'): r = {'ipv4': {}, 'ipv6': {}}
for o in (OuverturePortList.objects.all()
.prefetch_related('ouvertureport_set')
.prefetch_related('interface_set', 'interface_set__ipv4')):
pl = { pl = {
"tcp_in":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.TCP, io=OuverturePort.IN))), "tcp_in": set(map(
"tcp_out":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.TCP, io=OuverturePort.OUT))), str,
"udp_in":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.UDP, io=OuverturePort.IN))), o.ouvertureport_set.filter(
"udp_out":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.UDP, io=OuverturePort.OUT))), 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): for i in filter_active_interfaces(o.interface_set):
if i.may_have_port_open(): if i.may_have_port_open():
d = r['ipv4'].get(i.ipv4.ipv4, {}) d = r['ipv4'].get(i.ipv4.ipv4, {})
d["tcp_in"] = d.get("tcp_in",set()).union(pl["tcp_in"]) d["tcp_in"] = (d.get("tcp_in", set())
d["tcp_out"] = d.get("tcp_out",set()).union(pl["tcp_out"]) .union(pl["tcp_in"]))
d["udp_in"] = d.get("udp_in",set()).union(pl["udp_in"]) d["tcp_out"] = (d.get("tcp_out", set())
d["udp_out"] = d.get("udp_out",set()).union(pl["udp_out"]) .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 r['ipv4'][i.ipv4.ipv4] = d
if i.ipv6(): if i.ipv6():
for ipv6 in i.ipv6(): for ipv6 in i.ipv6():
d = r['ipv6'].get(ipv6.ipv6, {}) d = r['ipv6'].get(ipv6.ipv6, {})
d["tcp_in"] = d.get("tcp_in",set()).union(pl["tcp_in"]) d["tcp_in"] = (d.get("tcp_in", set())
d["tcp_out"] = d.get("tcp_out",set()).union(pl["tcp_out"]) .union(pl["tcp_in"]))
d["udp_in"] = d.get("udp_in",set()).union(pl["udp_in"]) d["tcp_out"] = (d.get("tcp_out", set())
d["udp_out"] = d.get("udp_out",set()).union(pl["udp_out"]) .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 r['ipv6'][ipv6.ipv6] = d
return JSONSuccess(r) return JSONSuccess(r)
@csrf_exempt @csrf_exempt
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@accept_method(['GET']) @accept_method(['GET'])
def dhcp_mac_ip(request): def dhcp_mac_ip(_request):
"""The list of all active interfaces with all the associated infos """The list of all active interfaces with all the associated infos
(MAC, IP, IpType, DNS name and associated zone extension) (MAC, IP, IpType, DNS name and associated zone extension)
@ -402,8 +517,10 @@ def dhcp_mac_ip(request):
* a field `ip_type`: the name of the IpType of 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 `mac_address`: the MAC of this interface
* a field `domain`: the DNS name for this interface * a field `domain`: the DNS name for this interface
* a field `extension`: the extension for the DNS zone of this interface * a field `extension`: the extension for the DNS zone of this
interface
""" """
interfaces = all_active_assigned_interfaces() interfaces = all_active_assigned_interfaces()
seria = InterfaceSerializer(interfaces, many=True) seria = InterfaceSerializer(interfaces, many=True)
return JSONSuccess(seria.data) return JSONSuccess(seria.data)
@ -413,7 +530,7 @@ def dhcp_mac_ip(request):
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@accept_method(['GET']) @accept_method(['GET'])
def mailing_standard(request): def mailing_standard(_request):
"""All the available standard mailings. """All the available standard mailings.
Returns: Returns:
@ -422,15 +539,17 @@ def mailing_standard(request):
* a list of dictionnaries (one for each mailing) containing: * a list of dictionnaries (one for each mailing) containing:
* a field `name`: the name of a mailing * a field `name`: the name of a mailing
""" """
return JSONSuccess([ return JSONSuccess([
{'name': 'adherents'} {'name': 'adherents'}
]) ])
@csrf_exempt @csrf_exempt
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@accept_method(['GET']) @accept_method(['GET'])
def mailing_standard_ml_members(request): def mailing_standard_ml_members(_request, ml_name):
"""All the members of a specific standard mailing """All the members of a specific standard mailing
Returns: Returns:
@ -442,6 +561,7 @@ def mailing_standard_ml_members(request):
* a field `surname`: the surname of the member * a field `surname`: the surname of the member
* a field `pseudo`: the pseudo of the member * a field `pseudo`: the pseudo of the member
""" """
# All with active connextion # All with active connextion
if ml_name == 'adherents': if ml_name == 'adherents':
members = all_has_access().values('email').distinct() members = all_has_access().values('email').distinct()
@ -456,7 +576,7 @@ def mailing_standard_ml_members(request):
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@accept_method(['GET']) @accept_method(['GET'])
def mailing_club(request): def mailing_club(_request):
"""All the available club mailings. """All the available club mailings.
Returns: Returns:
@ -465,15 +585,17 @@ def mailing_club(request):
* a list of dictionnaries (one for each mailing) containing: * a list of dictionnaries (one for each mailing) containing:
* a field `name` indicating the name of a mailing * a field `name` indicating the name of a mailing
""" """
clubs = Club.objects.filter(mailing=True).values('pseudo') clubs = Club.objects.filter(mailing=True).values('pseudo')
seria = MailingSerializer(clubs, many=True) seria = MailingSerializer(clubs, many=True)
return JSONSuccess(seria.data) return JSONSuccess(seria.data)
@csrf_exempt @csrf_exempt
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
@accept_method(['GET']) @accept_method(['GET'])
def mailing_club_ml_members(request): def mailing_club_ml_members(_request, ml_name):
"""All the members of a specific club mailing """All the members of a specific club mailing
Returns: Returns:
@ -485,6 +607,7 @@ def mailing_club_ml_members(request):
* a field `surname`: the surname of the member * a field `surname`: the surname of the member
* a field `pseudo`: the pseudo of the member * a field `pseudo`: the pseudo of the member
""" """
try: try:
club = Club.objects.get(mailing=True, pseudo=ml_name) club = Club.objects.get(mailing=True, pseudo=ml_name)
except Club.DoesNotExist: except Club.DoesNotExist:

View file

@ -20,5 +20,8 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""cotisations
The app in charge of all the members's cotisations
"""
from .acl import * from .acl import *

View file

@ -27,6 +27,7 @@ Here are defined some functions to check acl on the application.
""" """
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
def can_view(user): def can_view(user):
"""Check if an user can view the application. """Check if an user can view the application.
@ -38,4 +39,7 @@ def can_view(user):
viewing is granted and msg is a message (can be None). viewing is granted and msg is a message (can be None).
""" """
can = user.has_module_perms('cotisations') can = user.has_module_perms('cotisations')
return can, None if can else _("You don't have the rights to see this application.") if can:
return can, None
else:
return can, _("You don't have the rights to see this application.")

View file

@ -20,6 +20,9 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""cotisations.admin
The objects, fields and datastructures visible in the Django admin view
"""
from __future__ import unicode_literals from __future__ import unicode_literals

View file

@ -20,7 +20,7 @@
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
""" """
Forms for the 'cotisation' app of re2o. It highly depends on Forms for the 'cotisation' app of re2o. It highly depends on
:cotisations:models and is mainly used by :cotisations:views. :cotisations:models and is mainly used by :cotisations:views.
The following forms are mainly used to create, edit or delete The following forms are mainly used to create, edit or delete
@ -38,16 +38,15 @@ from __future__ import unicode_literals
from django import forms from django import forms
from django.db.models import Q from django.db.models import Q
from django.forms import ModelForm, Form from django.forms import ModelForm, Form
from django.core.validators import MinValueValidator,MaxValueValidator from django.core.validators import MinValueValidator
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _l from django.utils.translation import ugettext_lazy as _l
from .models import Article, Paiement, Facture, Banque
from preferences.models import OptionalUser from preferences.models import OptionalUser
from users.models import User
from re2o.field_permissions import FieldPermissionFormMixin from re2o.field_permissions import FieldPermissionFormMixin
from re2o.mixins import FormRevMixin from re2o.mixins import FormRevMixin
from .models import Article, Paiement, Facture, Banque
class NewFactureForm(FormRevMixin, ModelForm): class NewFactureForm(FormRevMixin, ModelForm):
""" """
@ -109,12 +108,16 @@ class CreditSoldeForm(NewFactureForm):
montant = forms.DecimalField(max_digits=5, decimal_places=2, required=True) montant = forms.DecimalField(max_digits=5, decimal_places=2, required=True)
class SelectUserArticleForm(FormRevMixin, Form): class SelectUserArticleForm(
FormRevMixin, Form):
""" """
Form used to select an article during the creation of an invoice for a member. Form used to select an article during the creation of an invoice for a
member.
""" """
article = forms.ModelChoiceField( article = forms.ModelChoiceField(
queryset=Article.objects.filter(Q(type_user='All') | Q(type_user='Adherent')), queryset=Article.objects.filter(
Q(type_user='All') | Q(type_user='Adherent')
),
label=_l("Article"), label=_l("Article"),
required=True required=True
) )
@ -127,10 +130,13 @@ class SelectUserArticleForm(FormRevMixin, Form):
class SelectClubArticleForm(Form): class SelectClubArticleForm(Form):
""" """
Form used to select an article during the creation of an invoice for a club. Form used to select an article during the creation of an invoice for a
club.
""" """
article = forms.ModelChoiceField( article = forms.ModelChoiceField(
queryset=Article.objects.filter(Q(type_user='All') | Q(type_user='Club')), queryset=Article.objects.filter(
Q(type_user='All') | Q(type_user='Club')
),
label=_l("Article"), label=_l("Article"),
required=True required=True
) )
@ -140,6 +146,7 @@ class SelectClubArticleForm(Form):
required=True required=True
) )
# TODO : change Facture to Invoice # TODO : change Facture to Invoice
class NewFactureFormPdf(Form): class NewFactureFormPdf(Form):
""" """
@ -147,9 +154,18 @@ class NewFactureFormPdf(Form):
""" """
paid = forms.BooleanField(label=_l("Paid"), required=False) paid = forms.BooleanField(label=_l("Paid"), required=False)
# TODO : change dest field to recipient # TODO : change dest field to recipient
dest = forms.CharField(required=True, max_length=255, label=_l("Recipient")) dest = forms.CharField(
required=True,
max_length=255,
label=_l("Recipient")
)
# TODO : change chambre field to address # TODO : change chambre field to address
chambre = forms.CharField(required=False, max_length=10, label=_l("Address")) chambre = forms.CharField(
required=False,
max_length=10,
label=_l("Address")
)
# TODO : change Facture to Invoice # TODO : change Facture to Invoice
class EditFactureForm(FieldPermissionFormMixin, NewFactureForm): class EditFactureForm(FieldPermissionFormMixin, NewFactureForm):
@ -295,6 +311,11 @@ class NewFactureSoldeForm(NewFactureForm):
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(NewFactureSoldeForm, self).__init__(
*args,
prefix=prefix,
**kwargs
)
self.fields['cheque'].required = False self.fields['cheque'].required = False
self.fields['banque'].required = False self.fields['banque'].required = False
self.fields['cheque'].label = _('Cheque number') self.fields['cheque'].label = _('Cheque number')
@ -313,7 +334,6 @@ class NewFactureSoldeForm(NewFactureForm):
# TODO : change paiement to payment and baque to bank # TODO : change paiement to payment and baque to bank
fields = ['paiement', 'banque'] fields = ['paiement', 'banque']
def clean(self): def clean(self):
cleaned_data = super(NewFactureSoldeForm, self).clean() cleaned_data = super(NewFactureSoldeForm, self).clean()
# TODO : change paiement to payment # TODO : change paiement to payment
@ -342,7 +362,7 @@ class RechargeForm(FormRevMixin, Form):
value = forms.FloatField( value = forms.FloatField(
label=_l("Amount"), label=_l("Amount"),
min_value=0.01, min_value=0.01,
validators = [] validators=[]
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -350,6 +370,10 @@ class RechargeForm(FormRevMixin, Form):
super(RechargeForm, self).__init__(*args, **kwargs) super(RechargeForm, self).__init__(*args, **kwargs)
def clean_value(self): def clean_value(self):
"""
Returns a cleaned vlaue from the received form by validating
the value is well inside the possible limits
"""
value = self.cleaned_data['value'] value = self.cleaned_data['value']
if value < OptionalUser.get_cached_value('min_online_payment'): if value < OptionalUser.get_cached_value('min_online_payment'):
raise forms.ValidationError( raise forms.ValidationError(
@ -360,7 +384,8 @@ class RechargeForm(FormRevMixin, Form):
) )
} }
) )
if value + self.user.solde > OptionalUser.get_cached_value('max_solde'): if value + self.user.solde > \
OptionalUser.get_cached_value('max_solde'):
raise forms.ValidationError( raise forms.ValidationError(
_("Requested amount is too high. Your balance can't exceed \ _("Requested amount is too high. Your balance can't exceed \
%(max_online_balance)s .") % { %(max_online_balance)s .") % {

View file

@ -0,0 +1,151 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-04-14 18:56
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cotisations', '0028_auto_20171231_0007'),
]
operations = [
migrations.AlterModelOptions(
name='article',
options={'permissions': (('view_article', "Can see an article's details"),), 'verbose_name': 'Article', 'verbose_name_plural': 'Articles'},
),
migrations.AlterModelOptions(
name='banque',
options={'permissions': (('view_banque', "Can see a bank's details"),), 'verbose_name': 'Bank', 'verbose_name_plural': 'Banks'},
),
migrations.AlterModelOptions(
name='cotisation',
options={'permissions': (('view_cotisation', "Can see a cotisation's details"), ('change_all_cotisation', 'Can edit the previous cotisations'))},
),
migrations.AlterModelOptions(
name='facture',
options={'permissions': (('change_facture_control', 'Can change the "controlled" state'), ('change_facture_pdf', 'Can create a custom PDF invoice'), ('view_facture', "Can see an invoice's details"), ('change_all_facture', 'Can edit all the previous invoices')), 'verbose_name': 'Invoice', 'verbose_name_plural': 'Invoices'},
),
migrations.AlterModelOptions(
name='paiement',
options={'permissions': (('view_paiement', "Can see a payement's details"),), 'verbose_name': 'Payment method', 'verbose_name_plural': 'Payment methods'},
),
migrations.AlterModelOptions(
name='vente',
options={'permissions': (('view_vente', "Can see a purchase's details"), ('change_all_vente', 'Can edit all the previous purchases')), 'verbose_name': 'Purchase', 'verbose_name_plural': 'Purchases'},
),
migrations.AlterField(
model_name='article',
name='duration',
field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Duration (in whole month)'),
),
migrations.AlterField(
model_name='article',
name='name',
field=models.CharField(max_length=255, verbose_name='Designation'),
),
migrations.AlterField(
model_name='article',
name='prix',
field=models.DecimalField(decimal_places=2, max_digits=5, verbose_name='Unitary price'),
),
migrations.AlterField(
model_name='article',
name='type_cotisation',
field=models.CharField(blank=True, choices=[('Connexion', 'Connexion'), ('Adhesion', 'Membership'), ('All', 'Both of them')], default=None, max_length=255, null=True, verbose_name='Type of cotisation'),
),
migrations.AlterField(
model_name='article',
name='type_user',
field=models.CharField(choices=[('Adherent', 'Member'), ('Club', 'Club'), ('All', 'Both of them')], default='All', max_length=255, verbose_name='Type of users concerned'),
),
migrations.AlterField(
model_name='banque',
name='name',
field=models.CharField(max_length=255, verbose_name='Name'),
),
migrations.AlterField(
model_name='cotisation',
name='date_end',
field=models.DateTimeField(verbose_name='Ending date'),
),
migrations.AlterField(
model_name='cotisation',
name='date_start',
field=models.DateTimeField(verbose_name='Starting date'),
),
migrations.AlterField(
model_name='cotisation',
name='type_cotisation',
field=models.CharField(choices=[('Connexion', 'Connexion'), ('Adhesion', 'Membership'), ('All', 'Both of them')], default='All', max_length=255, verbose_name='Type of cotisation'),
),
migrations.AlterField(
model_name='cotisation',
name='vente',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='cotisations.Vente', verbose_name='Purchase'),
),
migrations.AlterField(
model_name='facture',
name='cheque',
field=models.CharField(blank=True, max_length=255, verbose_name='Cheque number'),
),
migrations.AlterField(
model_name='facture',
name='control',
field=models.BooleanField(default=False, verbose_name='Controlled'),
),
migrations.AlterField(
model_name='facture',
name='date',
field=models.DateTimeField(auto_now_add=True, verbose_name='Date'),
),
migrations.AlterField(
model_name='facture',
name='valid',
field=models.BooleanField(default=True, verbose_name='Validated'),
),
migrations.AlterField(
model_name='paiement',
name='moyen',
field=models.CharField(max_length=255, verbose_name='Method'),
),
migrations.AlterField(
model_name='paiement',
name='type_paiement',
field=models.IntegerField(choices=[(0, 'Standard'), (1, 'Cheque')], default=0, verbose_name='Payment type'),
),
migrations.AlterField(
model_name='vente',
name='duration',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Duration (in whole month)'),
),
migrations.AlterField(
model_name='vente',
name='facture',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cotisations.Facture', verbose_name='Invoice'),
),
migrations.AlterField(
model_name='vente',
name='name',
field=models.CharField(max_length=255, verbose_name='Article'),
),
migrations.AlterField(
model_name='vente',
name='number',
field=models.IntegerField(validators=[django.core.validators.MinValueValidator(1)], verbose_name='Amount'),
),
migrations.AlterField(
model_name='vente',
name='prix',
field=models.DecimalField(decimal_places=2, max_digits=5, verbose_name='Price'),
),
migrations.AlterField(
model_name='vente',
name='type_cotisation',
field=models.CharField(blank=True, choices=[('Connexion', 'Connexion'), ('Adhesion', 'Membership'), ('All', 'Both of them')], max_length=255, null=True, verbose_name='Type of cotisation'),
),
]

View file

@ -34,17 +34,16 @@ from __future__ import unicode_literals
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q, Max
from django.db.models.signals import post_save, post_delete from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver from django.dispatch import receiver
from django.forms import ValidationError from django.forms import ValidationError
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
from django.db.models import Max
from django.utils import timezone from django.utils import timezone
from machines.models import regen
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _l from django.utils.translation import ugettext_lazy as _l
from machines.models import regen
from re2o.field_permissions import FieldPermissionModelMixin from re2o.field_permissions import FieldPermissionModelMixin
from re2o.mixins import AclMixin, RevMixin from re2o.mixins import AclMixin, RevMixin
@ -54,7 +53,7 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
""" """
The model for an invoice. It reprensents the fact that a user paid for The model for an invoice. It reprensents the fact that a user paid for
something (it can be multiple article paid at once). something (it can be multiple article paid at once).
An invoice is linked to : An invoice is linked to :
* one or more purchases (one for each article sold that time) * one or more purchases (one for each article sold that time)
* a user (the one who bought those articles) * a user (the one who bought those articles)
@ -105,11 +104,16 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
abstract = False abstract = False
permissions = ( permissions = (
# TODO : change facture to invoice # TODO : change facture to invoice
('change_facture_control', _l("Can change the \"controlled\" state")), ('change_facture_control',
# TODO : seems more likely to be call create_facture_pdf or create_invoice_pdf _l("Can change the \"controlled\" state")),
('change_facture_pdf', _l("Can create a custom PDF invoice")), # TODO : seems more likely to be call create_facture_pdf
('view_facture', _l("Can see an invoice's details")), # or create_invoice_pdf
('change_all_facture', _l("Can edit all the previous invoices")), ('change_facture_pdf',
_l("Can create a custom PDF invoice")),
('view_facture',
_l("Can see an invoice's details")),
('change_all_facture',
_l("Can edit all the previous invoices")),
) )
verbose_name = _l("Invoice") verbose_name = _l("Invoice")
verbose_name_plural = _l("Invoices") verbose_name_plural = _l("Invoices")
@ -159,11 +163,14 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
def can_edit(self, user_request, *args, **kwargs): def can_edit(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.change_facture'): if not user_request.has_perm('cotisations.change_facture'):
return False, _("You don't have the right to edit an invoice.") return False, _("You don't have the right to edit an invoice.")
elif not user_request.has_perm('cotisations.change_all_facture') and not self.user.can_edit(user_request, *args, **kwargs)[0]: elif not user_request.has_perm('cotisations.change_all_facture') and \
return False, _("You don't have the right to edit this user's invoices.") not self.user.can_edit(user_request, *args, **kwargs)[0]:
elif not user_request.has_perm('cotisations.change_all_facture') and\ return False, _("You don't have the right to edit this user's "
(self.control or not self.valid): "invoices.")
return False, _("You don't have the right to edit an invoice already controlled or invalidated.") elif not user_request.has_perm('cotisations.change_all_facture') and \
(self.control or not self.valid):
return False, _("You don't have the right to edit an invoice "
"already controlled or invalidated.")
else: else:
return True, None return True, None
@ -171,33 +178,45 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
if not user_request.has_perm('cotisations.delete_facture'): if not user_request.has_perm('cotisations.delete_facture'):
return False, _("You don't have the right to delete an invoice.") return False, _("You don't have the right to delete an invoice.")
if not self.user.can_edit(user_request, *args, **kwargs)[0]: if not self.user.can_edit(user_request, *args, **kwargs)[0]:
return False, _("You don't have the right to delete this user's invoices.") return False, _("You don't have the right to delete this user's "
"invoices.")
if self.control or not self.valid: if self.control or not self.valid:
return False, _("You don't have the right to delete an invoice already controlled or invalidated.") return False, _("You don't have the right to delete an invoice "
"already controlled or invalidated.")
else: else:
return True, None return True, None
def can_view(self, user_request, *args, **kwargs): def can_view(self, user_request, *_args, **_kwargs):
if not user_request.has_perm('cotisations.view_facture') and\ if not user_request.has_perm('cotisations.view_facture') and \
self.user != user_request: self.user != user_request:
return False, _("You don't have the right to see someone else's invoices history.") return False, _("You don't have the right to see someone else's "
"invoices history.")
elif not self.valid: elif not self.valid:
return False, _("The invoice has been invalidated.") return False, _("The invoice has been invalidated.")
else: else:
return True, None return True, None
@staticmethod @staticmethod
def can_change_control(user_request, *args, **kwargs): def can_change_control(user_request, *_args, **_kwargs):
return user_request.has_perm('cotisations.change_facture_control'), _("You don't have the right to edit the \"controlled\" state.") """ Returns True if the user can change the 'controlled' status of
this invoice """
return (
user_request.has_perm('cotisations.change_facture_control'),
_("You don't have the right to edit the \"controlled\" state.")
)
@staticmethod @staticmethod
def can_change_pdf(user_request, *args, **kwargs): def can_change_pdf(user_request, *_args, **_kwargs):
return user_request.has_perm('cotisations.change_facture_pdf'), _("You don't have the right to edit an invoice.") """ Returns True if the user can change this invoice """
return (
user_request.has_perm('cotisations.change_facture_pdf'),
_("You don't have the right to edit an invoice.")
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Facture, self).__init__(*args, **kwargs) super(Facture, self).__init__(*args, **kwargs)
self.field_permissions = { self.field_permissions = {
'control' : self.can_change_control, 'control': self.can_change_control,
} }
def __str__(self): def __str__(self):
@ -205,7 +224,7 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
@receiver(post_save, sender=Facture) @receiver(post_save, sender=Facture)
def facture_post_save(sender, **kwargs): def facture_post_save(**kwargs):
""" """
Synchronise the LDAP user after an invoice has been saved. Synchronise the LDAP user after an invoice has been saved.
""" """
@ -215,7 +234,7 @@ def facture_post_save(sender, **kwargs):
@receiver(post_delete, sender=Facture) @receiver(post_delete, sender=Facture)
def facture_post_delete(sender, **kwargs): def facture_post_delete(**kwargs):
""" """
Synchronise the LDAP user after an invoice has been deleted. Synchronise the LDAP user after an invoice has been deleted.
""" """
@ -228,7 +247,7 @@ class Vente(RevMixin, AclMixin, models.Model):
""" """
The model defining a purchase. It consist of one type of article being The model defining a purchase. It consist of one type of article being
sold. In particular there may be multiple purchases in a single invoice. sold. In particular there may be multiple purchases in a single invoice.
It's reprensentated by: It's reprensentated by:
* an amount (the number of items sold) * an amount (the number of items sold)
* an invoice (whose the purchase is part of) * an invoice (whose the purchase is part of)
@ -289,7 +308,6 @@ class Vente(RevMixin, AclMixin, models.Model):
verbose_name = _l("Purchase") verbose_name = _l("Purchase")
verbose_name_plural = _l("Purchases") verbose_name_plural = _l("Purchases")
# TODO : change prix_total to total_price # TODO : change prix_total to total_price
def prix_total(self): def prix_total(self):
""" """
@ -323,11 +341,13 @@ class Vente(RevMixin, AclMixin, models.Model):
facture__in=Facture.objects.filter( facture__in=Facture.objects.filter(
user=self.facture.user user=self.facture.user
).exclude(valid=False)) ).exclude(valid=False))
).filter(Q(type_cotisation='All') | Q(type_cotisation=self.type_cotisation) ).filter(
Q(type_cotisation='All') |
Q(type_cotisation=self.type_cotisation)
).filter( ).filter(
date_start__lt=date_start date_start__lt=date_start
).aggregate(Max('date_end'))['date_end__max'] ).aggregate(Max('date_end'))['date_end__max']
elif self.type_cotisation=="Adhesion": elif self.type_cotisation == "Adhesion":
end_cotisation = self.facture.user.end_adhesion() end_cotisation = self.facture.user.end_adhesion()
else: else:
end_cotisation = self.facture.user.end_connexion() end_cotisation = self.facture.user.end_connexion()
@ -357,11 +377,16 @@ class Vente(RevMixin, AclMixin, models.Model):
def can_edit(self, user_request, *args, **kwargs): def can_edit(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.change_vente'): if not user_request.has_perm('cotisations.change_vente'):
return False, _("You don't have the right to edit the purchases.") return False, _("You don't have the right to edit the purchases.")
elif not user_request.has_perm('cotisations.change_all_facture') and not self.facture.user.can_edit(user_request, *args, **kwargs)[0]: elif (not user_request.has_perm('cotisations.change_all_facture') and
return False, _("You don't have the right to edit this user's purchases.") not self.facture.user.can_edit(
elif not user_request.has_perm('cotisations.change_all_vente') and\ user_request, *args, **kwargs
(self.facture.control or not self.facture.valid): )[0]):
return False, _("You don't have the right to edit a purchase already controlled or invalidated.") return False, _("You don't have the right to edit this user's "
"purchases.")
elif (not user_request.has_perm('cotisations.change_all_vente') and
(self.facture.control or not self.facture.valid)):
return False, _("You don't have the right to edit a purchase "
"already controlled or invalidated.")
else: else:
return True, None return True, None
@ -369,16 +394,19 @@ class Vente(RevMixin, AclMixin, models.Model):
if not user_request.has_perm('cotisations.delete_vente'): if not user_request.has_perm('cotisations.delete_vente'):
return False, _("You don't have the right to delete a purchase.") return False, _("You don't have the right to delete a purchase.")
if not self.facture.user.can_edit(user_request, *args, **kwargs)[0]: if not self.facture.user.can_edit(user_request, *args, **kwargs)[0]:
return False, _("You don't have the right to delete this user's purchases.") return False, _("You don't have the right to delete this user's "
"purchases.")
if self.facture.control or not self.facture.valid: if self.facture.control or not self.facture.valid:
return False, _("You don't have the right to delete a purchase already controlled or invalidated.") return False, _("You don't have the right to delete a purchase "
"already controlled or invalidated.")
else: else:
return True, None return True, None
def can_view(self, user_request, *args, **kwargs): def can_view(self, user_request, *_args, **_kwargs):
if not user_request.has_perm('cotisations.view_vente') and\ if (not user_request.has_perm('cotisations.view_vente') and
self.facture.user != user_request: self.facture.user != user_request):
return False, _("You don't have the right to see someone else's purchase history.") return False, _("You don't have the right to see someone "
"else's purchase history.")
else: else:
return True, None return True, None
@ -388,7 +416,7 @@ class Vente(RevMixin, AclMixin, models.Model):
# TODO : change vente to purchase # TODO : change vente to purchase
@receiver(post_save, sender=Vente) @receiver(post_save, sender=Vente)
def vente_post_save(sender, **kwargs): def vente_post_save(**kwargs):
""" """
Creates a 'cotisation' related object if needed and synchronise the Creates a 'cotisation' related object if needed and synchronise the
LDAP user when a purchase has been saved. LDAP user when a purchase has been saved.
@ -406,7 +434,7 @@ def vente_post_save(sender, **kwargs):
# TODO : change vente to purchase # TODO : change vente to purchase
@receiver(post_delete, sender=Vente) @receiver(post_delete, sender=Vente)
def vente_post_delete(sender, **kwargs): def vente_post_delete(**kwargs):
""" """
Synchronise the LDAP user after a purchase has been deleted. Synchronise the LDAP user after a purchase has been deleted.
""" """
@ -418,12 +446,14 @@ def vente_post_delete(sender, **kwargs):
class Article(RevMixin, AclMixin, models.Model): class Article(RevMixin, AclMixin, models.Model):
""" """
The definition of an article model. It represents an type of object that can be sold to the user. The definition of an article model. It represents a type of object
that can be sold to the user.
It's represented by: It's represented by:
* a name * a name
* a price * a price
* a cotisation type (indicating if this article reprensents a cotisation or not) * a cotisation type (indicating if this article reprensents a
cotisation or not)
* a duration (if it is a cotisation) * a duration (if it is a cotisation)
* a type of user (indicating what kind of user can buy this article) * a type of user (indicating what kind of user can buy this article)
""" """
@ -513,8 +543,8 @@ class Banque(RevMixin, AclMixin, models.Model):
permissions = ( permissions = (
('view_banque', _l("Can see a bank's details")), ('view_banque', _l("Can see a bank's details")),
) )
verbose_name=_l("Bank") verbose_name = _l("Bank")
verbose_name_plural=_l("Banks") verbose_name_plural = _l("Banks")
def __str__(self): def __str__(self):
return self.name return self.name
@ -530,7 +560,7 @@ class Paiement(RevMixin, AclMixin, models.Model):
* a type (used for the type 'cheque' which implies the use of a bank * a type (used for the type 'cheque' which implies the use of a bank
and an account number in related models) and an account number in related models)
""" """
PAYMENT_TYPES = ( PAYMENT_TYPES = (
(0, _l("Standard")), (0, _l("Standard")),
(1, _l("Cheque")), (1, _l("Cheque")),
@ -619,27 +649,32 @@ class Cotisation(RevMixin, AclMixin, models.Model):
('change_all_cotisation', _l("Can edit the previous cotisations")), ('change_all_cotisation', _l("Can edit the previous cotisations")),
) )
def can_edit(self, user_request, *args, **kwargs): def can_edit(self, user_request, *_args, **_kwargs):
if not user_request.has_perm('cotisations.change_cotisation'): if not user_request.has_perm('cotisations.change_cotisation'):
return False, _("You don't have the right to edit a cotisation.") return False, _("You don't have the right to edit a cotisation.")
elif not user_request.has_perm('cotisations.change_all_cotisation') and\ elif not user_request.has_perm('cotisations.change_all_cotisation') \
(self.vente.facture.control or not self.vente.facture.valid): and (self.vente.facture.control or
return False, _("You don't have the right to edit a cotisation already controlled or invalidated.") not self.vente.facture.valid):
return False, _("You don't have the right to edit a cotisation "
"already controlled or invalidated.")
else: else:
return True, None return True, None
def can_delete(self, user_request, *args, **kwargs): def can_delete(self, user_request, *_args, **_kwargs):
if not user_request.has_perm('cotisations.delete_cotisation'): if not user_request.has_perm('cotisations.delete_cotisation'):
return False, _("You don't have the right to delete a cotisation.") return False, _("You don't have the right to delete a "
"cotisation.")
if self.vente.facture.control or not self.vente.facture.valid: if self.vente.facture.control or not self.vente.facture.valid:
return False, _("You don't have the right to delete a cotisation already controlled or invalidated.") return False, _("You don't have the right to delete a cotisation "
"already controlled or invalidated.")
else: else:
return True, None return True, None
def can_view(self, user_request, *args, **kwargs): def can_view(self, user_request, *_args, **_kwargs):
if not user_request.has_perm('cotisations.view_cotisation') and\ if not user_request.has_perm('cotisations.view_cotisation') and\
self.vente.facture.user != user_request: self.vente.facture.user != user_request:
return False, _("You don't have the right to see someone else's cotisation history.") return False, _("You don't have the right to see someone else's "
"cotisation history.")
else: else:
return True, None return True, None
@ -648,7 +683,7 @@ class Cotisation(RevMixin, AclMixin, models.Model):
@receiver(post_save, sender=Cotisation) @receiver(post_save, sender=Cotisation)
def cotisation_post_save(sender, **kwargs): def cotisation_post_save(**_kwargs):
""" """
Mark some services as needing a regeneration after the edition of a Mark some services as needing a regeneration after the edition of a
cotisation. Indeed the membership status may have changed. cotisation. Indeed the membership status may have changed.
@ -659,13 +694,11 @@ def cotisation_post_save(sender, **kwargs):
regen('mailing') regen('mailing')
# TODO : should be name cotisation_post_delete
@receiver(post_delete, sender=Cotisation) @receiver(post_delete, sender=Cotisation)
def vente_post_delete(sender, **kwargs): def cotisation_post_delete(**_kwargs):
""" """
Mark some services as needing a regeneration after the deletion of a Mark some services as needing a regeneration after the deletion of a
cotisation. Indeed the membership status may have changed. cotisation. Indeed the membership status may have changed.
""" """
cotisation = kwargs['instance']
regen('mac_ip_list') regen('mac_ip_list')
regen('mailing') regen('mailing')

View file

@ -2,6 +2,9 @@
Here are defined some views dedicated to online payement. Here are defined some views dedicated to online payement.
""" """
from collections import OrderedDict
from django.urls import reverse from django.urls import reverse
from django.shortcuts import redirect, get_object_or_404 from django.shortcuts import redirect, get_object_or_404
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
@ -11,12 +14,11 @@ from django.utils.datastructures import MultiValueDictKeyError
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.http import HttpResponse, HttpResponseBadRequest from django.http import HttpResponse, HttpResponseBadRequest
from collections import OrderedDict
from preferences.models import AssoOption from preferences.models import AssoOption
from .models import Facture from .models import Facture
from .payment_utils.comnpay import Payment as ComnpayPayment from .payment_utils.comnpay import Payment as ComnpayPayment
@csrf_exempt @csrf_exempt
@login_required @login_required
def accept_payment(request, factureid): def accept_payment(request, factureid):
@ -30,7 +32,10 @@ def accept_payment(request, factureid):
'amount': facture.prix() 'amount': facture.prix()
} }
) )
return redirect(reverse('users:profil', kwargs={'userid':request.user.id})) return redirect(reverse(
'users:profil',
kwargs={'userid': request.user.id}
))
@csrf_exempt @csrf_exempt
@ -43,7 +48,11 @@ def refuse_payment(request):
request, request,
_("The payment has been refused.") _("The payment has been refused.")
) )
return redirect(reverse('users:profil', kwargs={'userid':request.user.id})) return redirect(reverse(
'users:profil',
kwargs={'userid': request.user.id}
))
@csrf_exempt @csrf_exempt
def ipn(request): def ipn(request):
@ -105,7 +114,7 @@ def comnpay(facture, request):
str(AssoOption.get_cached_value('payment_pass')), str(AssoOption.get_cached_value('payment_pass')),
'https://' + host + reverse( 'https://' + host + reverse(
'cotisations:accept_payment', 'cotisations:accept_payment',
kwargs={'factureid':facture.id} kwargs={'factureid': facture.id}
), ),
'https://' + host + reverse('cotisations:refuse_payment'), 'https://' + host + reverse('cotisations:refuse_payment'),
'https://' + host + reverse('cotisations:ipn'), 'https://' + host + reverse('cotisations:ipn'),
@ -113,20 +122,20 @@ def comnpay(facture, request):
"D" "D"
) )
r = { r = {
'action' : 'https://secure.homologation.comnpay.com', 'action': 'https://secure.homologation.comnpay.com',
'method' : 'POST', 'method': 'POST',
'content' : p.buildSecretHTML( 'content': p.buildSecretHTML(
"Rechargement du solde", "Rechargement du solde",
facture.prix(), facture.prix(),
idTransaction=str(facture.id) idTransaction=str(facture.id)
), ),
'amount' : facture.prix, 'amount': facture.prix,
} }
return r return r
# The payment systems supported by re2o # The payment systems supported by re2o
PAYMENT_SYSTEM = { PAYMENT_SYSTEM = {
'COMNPAY' : comnpay, 'COMNPAY': comnpay,
'NONE' : None 'NONE': None
} }

View file

@ -1,21 +1,22 @@
"""cotisations.payment_utils.comnpay
The module in charge of handling the negociation with Comnpay
for online payment
"""
import time import time
from random import randrange from random import randrange
import base64 import base64
import hashlib import hashlib
from collections import OrderedDict from collections import OrderedDict
from itertools import chain
class Payment(): class Payment():
""" The class representing a transaction with all the functions
used during the negociation
"""
vad_number = "" def __init__(self, vad_number="", secret_key="", urlRetourOK="",
secret_key = "" urlRetourNOK="", urlIPN="", source="", typeTr="D"):
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.vad_number = vad_number
self.secret_key = secret_key self.secret_key = secret_key
self.urlRetourOK = urlRetourOK self.urlRetourOK = urlRetourOK
@ -23,46 +24,63 @@ class Payment():
self.urlIPN = urlIPN self.urlIPN = urlIPN
self.source = source self.source = source
self.typeTr = typeTr self.typeTr = typeTr
self.idTransaction = ""
def buildSecretHTML(self, produit="Produit", montant="0.00", idTransaction=""): def buildSecretHTML(self, produit="Produit", montant="0.00",
idTransaction=""):
""" Build an HTML hidden form with the different parameters for the
transaction
"""
if idTransaction == "": if idTransaction == "":
self.idTransaction = str(time.time())+self.vad_number+str(randrange(999)) self.idTransaction = str(time.time())
self.idTransaction += self.vad_number
self.idTransaction += str(randrange(999))
else: else:
self.idTransaction = idTransaction self.idTransaction = idTransaction
array_tpe = OrderedDict( array_tpe = OrderedDict(
montant= str(montant), montant=str(montant),
idTPE= self.vad_number, idTPE=self.vad_number,
idTransaction= self.idTransaction, idTransaction=self.idTransaction,
devise= "EUR", devise="EUR",
lang= 'fr', lang='fr',
nom_produit= produit, nom_produit=produit,
source= self.source, source=self.source,
urlRetourOK= self.urlRetourOK, urlRetourOK=self.urlRetourOK,
urlRetourNOK= self.urlRetourNOK, urlRetourNOK=self.urlRetourNOK,
typeTr= str(self.typeTr) typeTr=str(self.typeTr)
) )
if self.urlIPN!="": if self.urlIPN != "":
array_tpe['urlIPN'] = self.urlIPN array_tpe['urlIPN'] = self.urlIPN
array_tpe['key'] = self.secret_key; array_tpe['key'] = self.secret_key
strWithKey = base64.b64encode(bytes('|'.join(array_tpe.values()), 'utf-8')) strWithKey = base64.b64encode(bytes(
'|'.join(array_tpe.values()),
'utf-8'
))
del array_tpe["key"] del array_tpe["key"]
array_tpe['sec'] = hashlib.sha512(strWithKey).hexdigest() array_tpe['sec'] = hashlib.sha512(strWithKey).hexdigest()
ret = "" ret = ""
for key in array_tpe: for key in array_tpe:
ret += '<input type="hidden" name="'+key+'" value="'+array_tpe[key]+'"/>' ret += '<input type="hidden" name="{k}" value="{v}"/>'.format(
k=key,
v=array_tpe[key]
)
return ret return ret
def validSec(self, values, secret_key): @staticmethod
def validSec(values, secret_key):
""" Check if the secret value is correct """
if "sec" in values: if "sec" in values:
sec = values['sec'] sec = values['sec']
del values["sec"] del values["sec"]
strWithKey = hashlib.sha512(base64.b64encode(bytes('|'.join(values.values()) +"|"+secret_key, 'utf-8'))).hexdigest() strWithKey = hashlib.sha512(base64.b64encode(bytes(
'|'.join(values.values()) + "|" + secret_key,
'utf-8'
))).hexdigest()
return strWithKey.upper() == sec.upper() return strWithKey.upper() == sec.upper()
else: else:
return False return False

View file

@ -34,6 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<form class="form" method="post"> <form class="form" method="post">
{% csrf_token %} {% csrf_token %}
{% if articlesformset %}
<h3>{% trans "Invoice's articles" %}</h3> <h3>{% trans "Invoice's articles" %}</h3>
<div id="form_set" class="form-group"> <div id="form_set" class="form-group">
{{ articlesformset.management_form }} {{ articlesformset.management_form }}
@ -54,11 +55,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Total price : <span id="total_price">0,00</span> Total price : <span id="total_price">0,00</span>
{% endblocktrans %} {% endblocktrans %}
</p> </p>
{% endif %}
{% bootstrap_form factureform %} {% bootstrap_form factureform %}
{% bootstrap_button action_name button_type='submit' icon='star' %} {% bootstrap_button action_name button_type='submit' icon='star' %}
</form> </form>
{% if articlesformset %}
<script type="text/javascript"> <script type="text/javascript">
var prices = {}; var prices = {};
{% for article in articles %} {% for article in articles %}
@ -133,6 +135,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
update_price(); update_price();
}); });
</script> </script>
{% endif %}
{% endblock %} {% endblock %}

View file

@ -19,7 +19,10 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""cotisations.tests
The tests for the Cotisations module.
"""
from django.test import TestCase # from django.test import TestCase
# Create your tests here. # Create your tests here.

View file

@ -24,40 +24,44 @@ Module in charge of rendering some LaTex templates.
Used to generated PDF invoice. Used to generated PDF invoice.
""" """
import tempfile
from subprocess import Popen, PIPE
import os
from datetime import datetime
from django.template.loader import get_template from django.template.loader import get_template
from django.template import Context from django.template import Context
from django.http import HttpResponse from django.http import HttpResponse
from django.conf import settings from django.conf import settings
from django.utils.text import slugify from django.utils.text import slugify
import tempfile
from subprocess import Popen, PIPE
import os
TEMP_PREFIX = getattr(settings, 'TEX_TEMP_PREFIX', 'render_tex-') TEMP_PREFIX = getattr(settings, 'TEX_TEMP_PREFIX', 'render_tex-')
CACHE_PREFIX = getattr(settings, 'TEX_CACHE_PREFIX', 'render-tex') CACHE_PREFIX = getattr(settings, 'TEX_CACHE_PREFIX', 'render-tex')
CACHE_TIMEOUT = getattr(settings, 'TEX_CACHE_TIMEOUT', 86400) # 1 day CACHE_TIMEOUT = getattr(settings, 'TEX_CACHE_TIMEOUT', 86400) # 1 day
def render_invoice(request, ctx={}): def render_invoice(_request, ctx={}):
""" """
Render an invoice using some available information such as the current Render an invoice using some available information such as the current
date, the user, the articles, the prices, ... date, the user, the articles, the prices, ...
""" """
filename = '_'.join([ filename = '_'.join([
'invoice', 'invoice',
slugify(ctx['asso_name']), slugify(ctx.get('asso_name', "")),
slugify(ctx['recipient_name']), slugify(ctx.get('recipient_name', "")),
str(ctx['DATE'].year), str(ctx.get('DATE', datetime.now()).year),
str(ctx['DATE'].month), str(ctx.get('DATE', datetime.now()).month),
str(ctx['DATE'].day), str(ctx.get('DATE', datetime.now()).day),
]) ])
r = render_tex(request, 'cotisations/factures.tex', ctx) r = render_tex(_request, 'cotisations/factures.tex', ctx)
r['Content-Disposition'] = ''.join(['attachment; filename="',filename,'.pdf"']) r['Content-Disposition'] = 'attachment; filename="{name}.pdf"'.format(
name=filename
)
return r return r
def render_tex(request, template, ctx={}):
def render_tex(_request, template, ctx={}):
""" """
Creates a PDF from a LaTex templates using pdflatex. Creates a PDF from a LaTex templates using pdflatex.
Writes it in a temporary directory and send back an HTTP response for Writes it in a temporary directory and send back an HTTP response for
@ -66,13 +70,13 @@ def render_tex(request, template, ctx={}):
context = Context(ctx) context = Context(ctx)
template = get_template(template) template = get_template(template)
rendered_tpl = template.render(context).encode('utf-8') rendered_tpl = template.render(context).encode('utf-8')
with tempfile.TemporaryDirectory() as tempdir: with tempfile.TemporaryDirectory() as tempdir:
for i in range(2): for i in range(2):
process = Popen( process = Popen(
['pdflatex', '-output-directory', tempdir], ['pdflatex', '-output-directory', tempdir],
stdin = PIPE, stdin=PIPE,
stdout = PIPE, stdout=PIPE,
) )
process.communicate(rendered_tpl) process.communicate(rendered_tpl)
with open(os.path.join(tempdir, 'texput.pdf'), 'rb') as f: with open(os.path.join(tempdir, 'texput.pdf'), 'rb') as f:

View file

@ -19,6 +19,9 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""cotisations.urls
The defined URLs for the Cotisations app
"""
from __future__ import unicode_literals from __future__ import unicode_literals
@ -29,107 +32,131 @@ from . import views
from . import payment from . import payment
urlpatterns = [ urlpatterns = [
url(r'^new_facture/(?P<userid>[0-9]+)$', url(
r'^new_facture/(?P<userid>[0-9]+)$',
views.new_facture, views.new_facture,
name='new-facture' name='new-facture'
), ),
url(r'^edit_facture/(?P<factureid>[0-9]+)$', url(
r'^edit_facture/(?P<factureid>[0-9]+)$',
views.edit_facture, views.edit_facture,
name='edit-facture' name='edit-facture'
), ),
url(r'^del_facture/(?P<factureid>[0-9]+)$', url(
r'^del_facture/(?P<factureid>[0-9]+)$',
views.del_facture, views.del_facture,
name='del-facture' name='del-facture'
), ),
url(r'^facture_pdf/(?P<factureid>[0-9]+)$', url(
r'^facture_pdf/(?P<factureid>[0-9]+)$',
views.facture_pdf, views.facture_pdf,
name='facture-pdf' name='facture-pdf'
), ),
url(r'^new_facture_pdf/$', url(
r'^new_facture_pdf/$',
views.new_facture_pdf, views.new_facture_pdf,
name='new-facture-pdf' name='new-facture-pdf'
), ),
url(r'^credit_solde/(?P<userid>[0-9]+)$', url(
r'^credit_solde/(?P<userid>[0-9]+)$',
views.credit_solde, views.credit_solde,
name='credit-solde' name='credit-solde'
), ),
url(r'^add_article/$', url(
r'^add_article/$',
views.add_article, views.add_article,
name='add-article' name='add-article'
), ),
url(r'^edit_article/(?P<articleid>[0-9]+)$', url(
r'^edit_article/(?P<articleid>[0-9]+)$',
views.edit_article, views.edit_article,
name='edit-article' name='edit-article'
), ),
url(r'^del_article/$', url(
r'^del_article/$',
views.del_article, views.del_article,
name='del-article' name='del-article'
), ),
url(r'^add_paiement/$', url(
r'^add_paiement/$',
views.add_paiement, views.add_paiement,
name='add-paiement' name='add-paiement'
), ),
url(r'^edit_paiement/(?P<paiementid>[0-9]+)$', url(
r'^edit_paiement/(?P<paiementid>[0-9]+)$',
views.edit_paiement, views.edit_paiement,
name='edit-paiement' name='edit-paiement'
), ),
url(r'^del_paiement/$', url(
r'^del_paiement/$',
views.del_paiement, views.del_paiement,
name='del-paiement' name='del-paiement'
), ),
url(r'^add_banque/$', url(
r'^add_banque/$',
views.add_banque, views.add_banque,
name='add-banque' name='add-banque'
), ),
url(r'^edit_banque/(?P<banqueid>[0-9]+)$', url(
r'^edit_banque/(?P<banqueid>[0-9]+)$',
views.edit_banque, views.edit_banque,
name='edit-banque' name='edit-banque'
), ),
url(r'^del_banque/$', url(
r'^del_banque/$',
views.del_banque, views.del_banque,
name='del-banque' name='del-banque'
), ),
url(r'^index_article/$', url(
r'^index_article/$',
views.index_article, views.index_article,
name='index-article' name='index-article'
), ),
url(r'^index_banque/$', url(
r'^index_banque/$',
views.index_banque, views.index_banque,
name='index-banque' name='index-banque'
), ),
url(r'^index_paiement/$', url(
r'^index_paiement/$',
views.index_paiement, views.index_paiement,
name='index-paiement' name='index-paiement'
), ),
url( url(
r'history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$', r'history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
re2o.views.history, re2o.views.history,
name='history', name='history',
kwargs={'application':'cotisations'}, kwargs={'application': 'cotisations'},
), ),
url(r'^control/$', url(
r'^control/$',
views.control, views.control,
name='control' name='control'
), ),
url(r'^new_facture_solde/(?P<userid>[0-9]+)$', url(
r'^new_facture_solde/(?P<userid>[0-9]+)$',
views.new_facture_solde, views.new_facture_solde,
name='new_facture_solde' name='new_facture_solde'
), ),
url(r'^recharge/$', url(
r'^recharge/$',
views.recharge, views.recharge,
name='recharge' name='recharge'
), ),
url(r'^payment/accept/(?P<factureid>[0-9]+)$', url(
r'^payment/accept/(?P<factureid>[0-9]+)$',
payment.accept_payment, payment.accept_payment,
name='accept_payment' name='accept_payment'
), ),
url(r'^payment/refuse/$', url(
r'^payment/refuse/$',
payment.refuse_payment, payment.refuse_payment,
name='refuse_payment' name='refuse_payment'
), ),
url(r'^payment/ipn/$', url(
r'^payment/ipn/$',
payment.ipn, payment.ipn,
name='ipn' name='ipn'
), ),
url(r'^$', views.index, name='index'), url(r'^$', views.index, name='index'),
] ]

View file

@ -23,22 +23,23 @@
# App de gestion des users pour re2o # App de gestion des users pour re2o
# Goulven Kermarec, Gabriel Détraz # Goulven Kermarec, Gabriel Détraz
# Gplv2 # Gplv2
"""cotisations.views
The different views used in the Cotisations module
"""
from __future__ import unicode_literals from __future__ import unicode_literals
import os import os
from django.urls import reverse from django.urls import reverse
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.core.validators import MaxValueValidator from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib import messages from django.contrib import messages
from django.db.models import ProtectedError from django.db.models import ProtectedError
from django.db import transaction
from django.db.models import Q from django.db.models import Q
from django.forms import modelformset_factory, formset_factory from django.forms import modelformset_factory, formset_factory
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.debug import sensitive_variables
# Import des models, forms et fonctions re2o # Import des models, forms et fonctions re2o
from reversion import revisions as reversion from reversion import revisions as reversion
from users.models import User from users.models import User
@ -70,14 +71,12 @@ from .forms import (
SelectUserArticleForm, SelectUserArticleForm,
SelectClubArticleForm, SelectClubArticleForm,
CreditSoldeForm, CreditSoldeForm,
NewFactureSoldeForm,
RechargeForm RechargeForm
) )
from . import payment as online_payment from . import payment as online_payment
from .tex import render_invoice from .tex import render_invoice
@login_required @login_required
@can_create(Facture) @can_create(Facture)
@can_edit(User) @can_edit(User)
@ -102,9 +101,13 @@ def new_facture(request, user, userid):
# Building the invocie form and the article formset # Building the invocie form and the article formset
invoice_form = NewFactureForm(request.POST or None, instance=invoice) invoice_form = NewFactureForm(request.POST or None, instance=invoice)
if request.user.is_class_club: if request.user.is_class_club:
article_formset = formset_factory(SelectClubArticleForm)(request.POST or None) article_formset = formset_factory(SelectClubArticleForm)(
request.POST or None
)
else: else:
article_formset = formset_factory(SelectUserArticleForm)(request.POST or None) article_formset = formset_factory(SelectUserArticleForm)(
request.POST or None
)
if invoice_form.is_valid() and article_formset.is_valid(): if invoice_form.is_valid() and article_formset.is_valid():
new_invoice_instance = invoice_form.save(commit=False) new_invoice_instance = invoice_form.save(commit=False)
@ -118,15 +121,18 @@ def new_facture(request, user, userid):
# the authorized minimum (negative_balance) # the authorized minimum (negative_balance)
if user_balance: if user_balance:
# TODO : change Paiement to Payment # TODO : change Paiement to Payment
if new_invoice_instance.paiement == Paiement.objects.get_or_create( if new_invoice_instance.paiement == (
moyen='solde' Paiement.objects.get_or_create(moyen='solde')[0]
)[0]: ):
total_price = 0 total_price = 0
for art_item in articles: for art_item in articles:
if art_item.cleaned_data: if art_item.cleaned_data:
total_price += art_item.cleaned_data['article']\ total_price += (
.prix*art_item.cleaned_data['quantity'] art_item.cleaned_data['article'].prix *
if float(user.solde) - float(total_price) < negative_balance: art_item.cleaned_data['quantity']
)
if (float(user.solde) - float(total_price)
< negative_balance):
messages.error( messages.error(
request, request,
_("Your balance is too low for this operation.") _("Your balance is too low for this operation.")
@ -194,7 +200,7 @@ def new_facture(request, user, userid):
@can_change(Facture, 'pdf') @can_change(Facture, 'pdf')
def new_facture_pdf(request): def new_facture_pdf(request):
""" """
View used to generate a custom PDF invoice. It's mainly used to View used to generate a custom PDF invoice. It's mainly used to
get invoices that are not taken into account, for the administrative get invoices that are not taken into account, for the administrative
point of view. point of view.
""" """
@ -205,9 +211,13 @@ def new_facture_pdf(request):
# Building the invocie form and the article formset # Building the invocie form and the article formset
invoice_form = NewFactureFormPdf(request.POST or None) invoice_form = NewFactureFormPdf(request.POST or None)
if request.user.is_class_club: if request.user.is_class_club:
articles_formset = formset_factory(SelectClubArticleForm)(request.POST or None) articles_formset = formset_factory(SelectClubArticleForm)(
request.POST or None
)
else: else:
articles_formset = formset_factory(SelectUserArticleForm)(request.POST or None) articles_formset = formset_factory(SelectUserArticleForm)(
request.POST or None
)
if invoice_form.is_valid() and articles_formset.is_valid(): if invoice_form.is_valid() and articles_formset.is_valid():
# Get the article list and build an list out of it # Get the article list and build an list out of it
# contiaining (article_name, article_price, quantity, total_price) # contiaining (article_name, article_price, quantity, total_price)
@ -253,7 +263,7 @@ def new_facture_pdf(request):
# TODO : change facture to invoice # TODO : change facture to invoice
@login_required @login_required
@can_view(Facture) @can_view(Facture)
def facture_pdf(request, facture, factureid): def facture_pdf(request, facture, **_kwargs):
""" """
View used to generate a PDF file from an existing invoice in database View used to generate a PDF file from an existing invoice in database
Creates a line for each Purchase (thus article sold) and generate the Creates a line for each Purchase (thus article sold) and generate the
@ -296,14 +306,18 @@ def facture_pdf(request, facture, factureid):
# TODO : change facture to invoice # TODO : change facture to invoice
@login_required @login_required
@can_edit(Facture) @can_edit(Facture)
def edit_facture(request, facture, factureid): def edit_facture(request, facture, **_kwargs):
""" """
View used to edit an existing invoice. View used to edit an existing invoice.
Articles can be added or remove to the invoice and quantity Articles can be added or remove to the invoice and quantity
can be set as desired. This is also the view used to invalidate can be set as desired. This is also the view used to invalidate
an invoice. an invoice.
""" """
invoice_form = EditFactureForm(request.POST or None, instance=facture, user=request.user) invoice_form = EditFactureForm(
request.POST or None,
instance=facture,
user=request.user
)
purchases_objects = Vente.objects.filter(facture=facture) purchases_objects = Vente.objects.filter(facture=facture)
purchase_form_set = modelformset_factory( purchase_form_set = modelformset_factory(
Vente, Vente,
@ -311,7 +325,10 @@ def edit_facture(request, facture, factureid):
extra=0, extra=0,
max_num=len(purchases_objects) max_num=len(purchases_objects)
) )
purchase_form = purchase_form_set(request.POST or None, queryset=purchases_objects) purchase_form = purchase_form_set(
request.POST or None,
queryset=purchases_objects
)
if invoice_form.is_valid() and purchase_form.is_valid(): if invoice_form.is_valid() and purchase_form.is_valid():
if invoice_form.changed_data: if invoice_form.changed_data:
invoice_form.save() invoice_form.save()
@ -330,7 +347,7 @@ def edit_facture(request, facture, factureid):
# TODO : change facture to invoice # TODO : change facture to invoice
@login_required @login_required
@can_delete(Facture) @can_delete(Facture)
def del_facture(request, facture, factureid): def del_facture(request, facture, **_kwargs):
""" """
View used to delete an existing invocie. View used to delete an existing invocie.
""" """
@ -351,7 +368,7 @@ def del_facture(request, facture, factureid):
@login_required @login_required
@can_create(Facture) @can_create(Facture)
@can_edit(User) @can_edit(User)
def credit_solde(request, user, userid): def credit_solde(request, user, **_kwargs):
""" """
View used to edit the balance of a user. View used to edit the balance of a user.
Can be use either to increase or decrease a user's balance. Can be use either to increase or decrease a user's balance.
@ -375,7 +392,7 @@ def credit_solde(request, user, userid):
) )
return redirect(reverse('cotisations:index')) return redirect(reverse('cotisations:index'))
return form({ return form({
'factureform': facture, 'factureform': invoice,
'action_name': _("Edit") 'action_name': _("Edit")
}, 'cotisations/facture.html', request) }, 'cotisations/facture.html', request)
@ -385,7 +402,7 @@ def credit_solde(request, user, userid):
def add_article(request): def add_article(request):
""" """
View used to add an article. View used to add an article.
.. note:: If a purchase has already been sold, the price are calculated .. note:: If a purchase has already been sold, the price are calculated
once and for all. That means even if the price of an article is edited once and for all. That means even if the price of an article is edited
later, it won't change the invoice. That is really important to keep later, it won't change the invoice. That is really important to keep
@ -408,7 +425,7 @@ def add_article(request):
@login_required @login_required
@can_edit(Article) @can_edit(Article)
def edit_article(request, article_instance, articleid): def edit_article(request, article_instance, **_kwargs):
""" """
View used to edit an article. View used to edit an article.
""" """
@ -472,7 +489,7 @@ def add_paiement(request):
# TODO : chnage paiement to Payment # TODO : chnage paiement to Payment
@login_required @login_required
@can_edit(Paiement) @can_edit(Paiement)
def edit_paiement(request, paiement_instance, paiementid): def edit_paiement(request, paiement_instance, **_kwargs):
""" """
View used to edit a payment method. View used to edit a payment method.
""" """
@ -550,7 +567,7 @@ def add_banque(request):
# TODO : change banque to bank # TODO : change banque to bank
@login_required @login_required
@can_edit(Banque) @can_edit(Banque)
def edit_banque(request, banque_instance, banqueid): def edit_banque(request, banque_instance, **_kwargs):
""" """
View used to edit a bank. View used to edit a bank.
""" """
@ -613,7 +630,8 @@ def control(request):
View used to control the invoices all at once. View used to control the invoices all at once.
""" """
pagination_number = GeneralOption.get_cached_value('pagination_number') pagination_number = GeneralOption.get_cached_value('pagination_number')
invoice_list = Facture.objects.select_related('user').select_related('paiement') invoice_list = (Facture.objects.select_related('user').
select_related('paiement'))
invoice_list = SortTable.sort( invoice_list = SortTable.sort(
invoice_list, invoice_list,
request.GET.get('col'), request.GET.get('col'),
@ -725,9 +743,13 @@ def new_facture_solde(request, userid):
Q(type_user='All') | Q(type_user=request.user.class_name) Q(type_user='All') | Q(type_user=request.user.class_name)
) )
if request.user.is_class_club: if request.user.is_class_club:
article_formset = formset_factory(SelectClubArticleForm)(request.POST or None) article_formset = formset_factory(SelectClubArticleForm)(
request.POST or None
)
else: else:
article_formset = formset_factory(SelectUserArticleForm)(request.POST or None) article_formset = formset_factory(SelectUserArticleForm)(
request.POST or None
)
if article_formset.is_valid(): if article_formset.is_valid():
articles = article_formset articles = article_formset
@ -826,7 +848,9 @@ def recharge(request):
refill_form = RechargeForm(request.POST or None, user=request.user) refill_form = RechargeForm(request.POST or None, user=request.user)
if refill_form.is_valid(): if refill_form.is_valid():
invoice = Facture(user=request.user) invoice = Facture(user=request.user)
payment, _created = Paiement.objects.get_or_create(moyen='Rechargement en ligne') payment, _created = Paiement.objects.get_or_create(
moyen='Rechargement en ligne'
)
invoice.paiement = payment invoice.paiement = payment
invoice.valid = False invoice.valid = False
invoice.save() invoice.save()
@ -837,7 +861,9 @@ def recharge(request):
number=1 number=1
) )
purchase.save() purchase.save()
content = online_payment.PAYMENT_SYSTEM[AssoOption.get_cached_value('payment')](invoice, request) content = online_payment.PAYMENT_SYSTEM[
AssoOption.get_cached_value('payment')
](invoice, request)
return render(request, 'cotisations/payment.html', content) return render(request, 'cotisations/payment.html', content)
return form({ return form({
'rechargeform': refill_form 'rechargeform': refill_form

View file

@ -1,4 +1,4 @@
# *- mode: python; coding: utf-8 -*- # -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il # 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 # se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics. # quelques clics.
@ -35,13 +35,18 @@ https://github.com/FreeRADIUS/freeradius-server/blob/master/src/modules/rlm_pyth
Inspiré du travail de Daniel Stan au Crans Inspiré du travail de Daniel Stan au Crans
""" """
import os
import sys
import logging import logging
import netaddr import radiusd # Module magique freeradius (radiusd.py is dummy)
import radiusd # Module magique freeradius (radiusd.py is dummy)
import binascii
import hashlib
import os, sys
from django.core.wsgi import get_wsgi_application
from django.db.models import Q
from machines.models import Interface, IpList, Nas, Domain
from topologie.models import Port, Switch
from users.models import User
from preferences.models import OptionalTopologie
proj_path = "/var/www/re2o/" proj_path = "/var/www/re2o/"
# This is so Django knows where to find stuff. # This is so Django knows where to find stuff.
@ -52,28 +57,17 @@ sys.path.append(proj_path)
os.chdir(proj_path) os.chdir(proj_path)
# This is so models get loaded. # This is so models get loaded.
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application() application = get_wsgi_application()
import argparse
from django.db.models import Q
from machines.models import Interface, IpList, Nas, Domain
from topologie.models import Room, Port, Switch
from users.models import User
from preferences.models import OptionalTopologie
options, created = OptionalTopologie.objects.get_or_create() options, created = OptionalTopologie.objects.get_or_create()
VLAN_NOK = options.vlan_decision_nok.vlan_id VLAN_NOK = options.vlan_decision_nok.vlan_id
VLAN_OK = options.vlan_decision_ok.vlan_id VLAN_OK = options.vlan_decision_ok.vlan_id
#: Serveur radius de test (pas la prod) #: Serveur radius de test (pas la prod)
TEST_SERVER = bool(os.getenv('DBG_FREERADIUS', False)) TEST_SERVER = bool(os.getenv('DBG_FREERADIUS', False))
## -*- Logging -*- # Logging
class RadiusdHandler(logging.Handler): class RadiusdHandler(logging.Handler):
"""Handler de logs pour freeradius""" """Handler de logs pour freeradius"""
@ -87,6 +81,7 @@ class RadiusdHandler(logging.Handler):
rad_sig = radiusd.L_DBG rad_sig = radiusd.L_DBG
radiusd.radlog(rad_sig, record.msg) radiusd.radlog(rad_sig, record.msg)
# Initialisation d'un logger (pour logguer unifié) # Initialisation d'un logger (pour logguer unifié)
logger = logging.getLogger('auth.py') logger = logging.getLogger('auth.py')
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
@ -95,10 +90,11 @@ handler = RadiusdHandler()
handler.setFormatter(formatter) handler.setFormatter(formatter)
logger.addHandler(handler) logger.addHandler(handler)
def radius_event(fun): def radius_event(fun):
"""Décorateur pour les fonctions d'interfaces avec radius. """Décorateur pour les fonctions d'interfaces avec radius.
Une telle fonction prend un uniquement argument, qui est une liste de tuples Une telle fonction prend un uniquement argument, qui est une liste de
(clé, valeur) et renvoie un triplet dont les composantes sont : tuples (clé, valeur) et renvoie un triplet dont les composantes sont :
* le code de retour (voir radiusd.RLM_MODULE_* ) * le code de retour (voir radiusd.RLM_MODULE_* )
* un tuple de couples (clé, valeur) pour les valeurs de réponse (accès ok * un tuple de couples (clé, valeur) pour les valeurs de réponse (accès ok
et autres trucs du genre) et autres trucs du genre)
@ -109,7 +105,8 @@ def radius_event(fun):
tuples en entrée en un dictionnaire.""" tuples en entrée en un dictionnaire."""
def new_f(auth_data): def new_f(auth_data):
if type(auth_data) == dict: """ The function transforming the tuples as dict """
if isinstance(auth_data, dict):
data = auth_data data = auth_data
else: else:
data = dict() data = dict()
@ -118,8 +115,8 @@ def radius_event(fun):
# Ex: Calling-Station-Id: "une_adresse_mac" # Ex: Calling-Station-Id: "une_adresse_mac"
data[key] = value.replace('"', '') data[key] = value.replace('"', '')
try: try:
# TODO s'assurer ici que les tuples renvoyés sont bien des (str,str) # TODO s'assurer ici que les tuples renvoyés sont bien des
# rlm_python ne digère PAS les unicodes # (str,str) : rlm_python ne digère PAS les unicodes
return fun(data) return fun(data)
except Exception as err: except Exception as err:
logger.error('Failed %r on data %r' % (err, auth_data)) logger.error('Failed %r on data %r' % (err, auth_data))
@ -128,7 +125,6 @@ def radius_event(fun):
return new_f return new_f
@radius_event @radius_event
def instantiate(*_): def instantiate(*_):
"""Utile pour initialiser les connexions ldap une première fois (otherwise, """Utile pour initialiser les connexions ldap une première fois (otherwise,
@ -137,12 +133,15 @@ def instantiate(*_):
if TEST_SERVER: if TEST_SERVER:
logger.info(u'DBG_FREERADIUS is enabled') logger.info(u'DBG_FREERADIUS is enabled')
@radius_event @radius_event
def authorize(data): def authorize(data):
"""On test si on connait le calling nas: """On test si on connait le calling nas:
- si le nas est inconnue, on suppose que c'est une requète 802.1X, on la traite - si le nas est inconnue, on suppose que c'est une requète 802.1X, on la
traite
- si le nas est connu, on applique 802.1X si le mode est activé - si le nas est connu, on applique 802.1X si le mode est activé
- si le nas est connu et si il s'agit d'un nas auth par mac, on repond accept en authorize - si le nas est connu et si il s'agit d'un nas auth par mac, on repond
accept en authorize
""" """
# Pour les requetes proxifiees, on split # Pour les requetes proxifiees, on split
nas = data.get('NAS-IP-Address', data.get('NAS-Identifier', None)) nas = data.get('NAS-IP-Address', data.get('NAS-Identifier', None))
@ -155,30 +154,40 @@ def authorize(data):
user = data.get('User-Name', '').decode('utf-8', errors='replace') user = data.get('User-Name', '').decode('utf-8', errors='replace')
user = user.split('@', 1)[0] user = user.split('@', 1)[0]
mac = data.get('Calling-Station-Id', '') mac = data.get('Calling-Station-Id', '')
result, log, password = check_user_machine_and_register(nas_type, user, mac) result, log, password = check_user_machine_and_register(
nas_type,
user,
mac
)
logger.info(log.encode('utf-8')) logger.info(log.encode('utf-8'))
logger.info(user.encode('utf-8')) logger.info(user.encode('utf-8'))
if not result: if not result:
return radiusd.RLM_MODULE_REJECT return radiusd.RLM_MODULE_REJECT
else: else:
return (radiusd.RLM_MODULE_UPDATED, return (
(), radiusd.RLM_MODULE_UPDATED,
( (),
(str("NT-Password"), str(password)), (
), (str("NT-Password"), str(password)),
),
) )
else: else:
return (radiusd.RLM_MODULE_UPDATED, return (
(), radiusd.RLM_MODULE_UPDATED,
( (),
("Auth-Type", "Accept"), (
), ("Auth-Type", "Accept"),
),
) )
@radius_event @radius_event
def post_auth(data): def post_auth(data):
""" Function called after the user is authenticated
"""
nas = data.get('NAS-IP-Address', data.get('NAS-Identifier', None)) nas = data.get('NAS-IP-Address', data.get('NAS-Identifier', None))
nas_instance = find_nas_from_request(nas) nas_instance = find_nas_from_request(nas)
# Toutes les reuquètes non proxifiées # Toutes les reuquètes non proxifiées
@ -187,61 +196,89 @@ def post_auth(data):
return radiusd.RLM_MODULE_OK return radiusd.RLM_MODULE_OK
nas_type = Nas.objects.filter(nas_type=nas_instance.type).first() nas_type = Nas.objects.filter(nas_type=nas_instance.type).first()
if not nas_type: if not nas_type:
logger.info(u"Type de nas non enregistré dans la bdd!".encode('utf-8')) logger.info(
u"Type de nas non enregistré dans la bdd!".encode('utf-8')
)
return radiusd.RLM_MODULE_OK return radiusd.RLM_MODULE_OK
mac = data.get('Calling-Station-Id', None) mac = data.get('Calling-Station-Id', None)
# Switch et bornes héritent de machine et peuvent avoir plusieurs interfaces filles # Switch et bornes héritent de machine et peuvent avoir plusieurs
# interfaces filles
nas_machine = nas_instance.machine nas_machine = nas_instance.machine
# Si il s'agit d'un switch # Si il s'agit d'un switch
if hasattr(nas_machine, 'switch'): if hasattr(nas_machine, 'switch'):
port = data.get('NAS-Port-Id', data.get('NAS-Port', None)) port = data.get('NAS-Port-Id', data.get('NAS-Port', None))
#Pour les infrastructures possédant des switchs Juniper : # Pour les infrastructures possédant des switchs Juniper :
#On vérifie si le switch fait partie d'un stack Juniper # On vérifie si le switch fait partie d'un stack Juniper
instance_stack = nas_machine.switch.stack instance_stack = nas_machine.switch.stack
if instance_stack: if instance_stack:
# Si c'est le cas, on resélectionne le bon switch dans la stack # Si c'est le cas, on resélectionne le bon switch dans la stack
id_stack_member = port.split("-")[1].split('/')[0] id_stack_member = port.split("-")[1].split('/')[0]
nas_machine = Switch.objects.filter(stack=instance_stack).filter(stack_member_id=id_stack_member).prefetch_related('interface_set__domain__extension').first() nas_machine = (Switch.objects
# On récupère le numéro du port sur l'output de freeradius. La ligne suivante fonctionne pour cisco, HP et Juniper .filter(stack=instance_stack)
.filter(stack_member_id=id_stack_member)
.prefetch_related(
'interface_set__domain__extension'
)
.first())
# On récupère le numéro du port sur l'output de freeradius.
# La ligne suivante fonctionne pour cisco, HP et Juniper
port = port.split(".")[0].split('/')[-1][-2:] port = port.split(".")[0].split('/')[-1][-2:]
out = decide_vlan_and_register_switch(nas_machine, nas_type, port, mac) out = decide_vlan_and_register_switch(nas_machine, nas_type, port, mac)
sw_name, room, reason, vlan_id = out sw_name, room, reason, vlan_id = out
log_message = '(fil) %s -> %s [%s%s]' % \ log_message = '(fil) %s -> %s [%s%s]' % (
(sw_name + u":" + port + u"/" + unicode(room), mac, vlan_id, (reason and u': ' + reason).encode('utf-8')) sw_name + u":" + port + u"/" + str(room),
mac,
vlan_id,
(reason and u': ' + reason).encode('utf-8')
)
logger.info(log_message) logger.info(log_message)
# Filaire # Filaire
return (radiusd.RLM_MODULE_UPDATED, return (
radiusd.RLM_MODULE_UPDATED,
( (
("Tunnel-Type", "VLAN"), ("Tunnel-Type", "VLAN"),
("Tunnel-Medium-Type", "IEEE-802"), ("Tunnel-Medium-Type", "IEEE-802"),
("Tunnel-Private-Group-Id", '%d' % int(vlan_id)), ("Tunnel-Private-Group-Id", '%d' % int(vlan_id)),
), ),
() ()
) )
else: else:
return radiusd.RLM_MODULE_OK return radiusd.RLM_MODULE_OK
# TODO : remove this function
@radius_event @radius_event
def dummy_fun(_): def dummy_fun(_):
"""Do nothing, successfully. (C'est pour avoir un truc à mettre)""" """Do nothing, successfully. (C'est pour avoir un truc à mettre)"""
return radiusd.RLM_MODULE_OK return radiusd.RLM_MODULE_OK
def detach(_=None): def detach(_=None):
"""Appelé lors du déchargement du module (enfin, normalement)""" """Appelé lors du déchargement du module (enfin, normalement)"""
print "*** goodbye from auth.py ***" print("*** goodbye from auth.py ***")
return radiusd.RLM_MODULE_OK return radiusd.RLM_MODULE_OK
def find_nas_from_request(nas_id): def find_nas_from_request(nas_id):
nas = Interface.objects.filter(Q(domain=Domain.objects.filter(name=nas_id)) | Q(ipv4=IpList.objects.filter(ipv4=nas_id))).select_related('type').select_related('machine__switch__stack') """ Get the nas object from its ID """
nas = (Interface.objects
.filter(
Q(domain=Domain.objects.filter(name=nas_id)) |
Q(ipv4=IpList.objects.filter(ipv4=nas_id))
)
.select_related('type')
.select_related('machine__switch__stack'))
return nas.first() return nas.first()
def check_user_machine_and_register(nas_type, username, mac_address): def check_user_machine_and_register(nas_type, username, mac_address):
""" Verifie le username et la mac renseignee. L'enregistre si elle est inconnue. """Verifie le username et la mac renseignee. L'enregistre si elle est
inconnue.
Renvoie le mot de passe ntlm de l'user si tout est ok Renvoie le mot de passe ntlm de l'user si tout est ok
Utilise pour les authentifications en 802.1X""" Utilise pour les authentifications en 802.1X"""
interface = Interface.objects.filter(mac_address=mac_address).first() interface = Interface.objects.filter(mac_address=mac_address).first()
@ -252,7 +289,10 @@ def check_user_machine_and_register(nas_type, username, mac_address):
return (False, u"Adhérent non cotisant", '') return (False, u"Adhérent non cotisant", '')
if interface: if interface:
if interface.machine.user != user: if interface.machine.user != user:
return (False, u"Machine enregistrée sur le compte d'un autre user...", '') return (False,
u"Machine enregistrée sur le compte d'un autre "
"user...",
'')
elif not interface.is_active: elif not interface.is_active:
return (False, u"Machine desactivée", '') return (False, u"Machine desactivée", '')
elif not interface.ipv4: elif not interface.ipv4:
@ -264,7 +304,9 @@ def check_user_machine_and_register(nas_type, username, mac_address):
if nas_type.autocapture_mac: if nas_type.autocapture_mac:
result, reason = user.autoregister_machine(mac_address, nas_type) result, reason = user.autoregister_machine(mac_address, nas_type)
if result: if result:
return (True, u'Access Ok, Capture de la mac...', user.pwd_ntlm) return (True,
u'Access Ok, Capture de la mac...',
user.pwd_ntlm)
else: else:
return (False, u'Erreur dans le register mac %s' % reason, '') return (False, u'Erreur dans le register mac %s' % reason, '')
else: else:
@ -273,8 +315,10 @@ def check_user_machine_and_register(nas_type, username, mac_address):
return (False, u"Machine inconnue", '') return (False, u"Machine inconnue", '')
def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, mac_address): def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
"""Fonction de placement vlan pour un switch en radius filaire auth par mac. mac_address):
"""Fonction de placement vlan pour un switch en radius filaire auth par
mac.
Plusieurs modes : Plusieurs modes :
- nas inconnu, port inconnu : on place sur le vlan par defaut VLAN_OK - nas inconnu, port inconnu : on place sur le vlan par defaut VLAN_OK
- pas de radius sur le port : VLAN_OK - pas de radius sur le port : VLAN_OK
@ -292,7 +336,8 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, mac_addr
- interface inconnue : - interface inconnue :
- register mac désactivé : VLAN_NOK - register mac désactivé : VLAN_NOK
- register mac activé : - register mac activé :
- dans la chambre associé au port, pas d'user ou non à jour : VLAN_NOK - dans la chambre associé au port, pas d'user ou non à
jour : VLAN_NOK
- user à jour, autocapture de la mac et VLAN_OK - user à jour, autocapture de la mac et VLAN_OK
""" """
# Get port from switch and port number # Get port from switch and port number
@ -303,8 +348,13 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, mac_addr
sw_name = str(nas_machine) sw_name = str(nas_machine)
port = Port.objects.filter(switch=Switch.objects.filter(machine_ptr=nas_machine), port=port_number).first() port = (Port.objects
#Si le port est inconnu, on place sur le vlan defaut .filter(
switch=Switch.objects.filter(machine_ptr=nas_machine),
port=port_number
)
.first())
# Si le port est inconnu, on place sur le vlan defaut
if not port: if not port:
return (sw_name, "Chambre inconnue", u'Port inconnu', VLAN_OK) return (sw_name, "Chambre inconnue", u'Port inconnu', VLAN_OK)
@ -316,7 +366,10 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, mac_addr
DECISION_VLAN = VLAN_OK DECISION_VLAN = VLAN_OK
if port.radius == 'NO': if port.radius == 'NO':
return (sw_name, "", u"Pas d'authentification sur ce port" + extra_log, DECISION_VLAN) return (sw_name,
"",
u"Pas d'authentification sur ce port" + extra_log,
DECISION_VLAN)
if port.radius == 'BLOQ': if port.radius == 'BLOQ':
return (sw_name, port.room, u'Port desactive', VLAN_NOK) return (sw_name, port.room, u'Port desactive', VLAN_NOK)
@ -326,7 +379,9 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, mac_addr
if not room: if not room:
return (sw_name, "Inconnue", u'Chambre inconnue', VLAN_NOK) return (sw_name, "Inconnue", u'Chambre inconnue', VLAN_NOK)
room_user = User.objects.filter(Q(club__room=port.room) | Q(adherent__room=port.room)) room_user = User.objects.filter(
Q(club__room=port.room) | Q(adherent__room=port.room)
)
if not room_user: if not room_user:
return (sw_name, room, u'Chambre non cotisante', VLAN_NOK) return (sw_name, room, u'Chambre non cotisante', VLAN_NOK)
for user in room_user: for user in room_user:
@ -336,35 +391,78 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, mac_addr
if port.radius == 'COMMON' or port.radius == 'STRICT': if port.radius == 'COMMON' or port.radius == 'STRICT':
# Authentification par mac # Authentification par mac
interface = Interface.objects.filter(mac_address=mac_address).select_related('machine__user').select_related('ipv4').first() interface = (Interface.objects
.filter(mac_address=mac_address)
.select_related('machine__user')
.select_related('ipv4')
.first())
if not interface: if not interface:
room = port.room room = port.room
# On essaye de register la mac # On essaye de register la mac
if not nas_type.autocapture_mac: if not nas_type.autocapture_mac:
return (sw_name, "", u'Machine inconnue', VLAN_NOK) return (sw_name, "", u'Machine inconnue', VLAN_NOK)
elif not room: elif not room:
return (sw_name, "Inconnue", u'Chambre et machine inconnues', VLAN_NOK) return (sw_name,
"Inconnue",
u'Chambre et machine inconnues',
VLAN_NOK)
else: else:
if not room_user: if not room_user:
room_user = User.objects.filter(Q(club__room=port.room) | Q(adherent__room=port.room)) room_user = User.objects.filter(
Q(club__room=port.room) | Q(adherent__room=port.room)
)
if not room_user: if not room_user:
return (sw_name, room, u'Machine et propriétaire de la chambre inconnus', VLAN_NOK) return (sw_name,
room,
u'Machine et propriétaire de la chambre '
'inconnus',
VLAN_NOK)
elif room_user.count() > 1: elif room_user.count() > 1:
return (sw_name, room, u'Machine inconnue, il y a au moins 2 users dans la chambre/local -> ajout de mac automatique impossible', VLAN_NOK) return (sw_name,
room,
u'Machine inconnue, il y a au moins 2 users '
'dans la chambre/local -> ajout de mac '
'automatique impossible',
VLAN_NOK)
elif not room_user.first().has_access(): elif not room_user.first().has_access():
return (sw_name, room, u'Machine inconnue et adhérent non cotisant', VLAN_NOK) return (sw_name,
room,
u'Machine inconnue et adhérent non cotisant',
VLAN_NOK)
else: else:
result, reason = room_user.first().autoregister_machine(mac_address, nas_type) result, reason = (room_user
.first()
.autoregister_machine(
mac_address,
nas_type
))
if result: if result:
return (sw_name, room, u'Access Ok, Capture de la mac...' + extra_log, DECISION_VLAN) return (sw_name,
room,
u'Access Ok, Capture de la mac: ' + extra_log,
DECISION_VLAN)
else: else:
return (sw_name, room, u'Erreur dans le register mac %s' % reason + unicode(mac_address), VLAN_NOK) return (sw_name,
room,
u'Erreur dans le register mac %s' % (
reason + str(mac_address)
),
VLAN_NOK)
else: else:
room = port.room room = port.room
if not interface.is_active: if not interface.is_active:
return (sw_name, room, u'Machine non active / adherent non cotisant', VLAN_NOK) return (sw_name,
room,
u'Machine non active / adherent non cotisant',
VLAN_NOK)
elif not interface.ipv4: elif not interface.ipv4:
interface.assign_ipv4() interface.assign_ipv4()
return (sw_name, room, u"Ok, Reassignation de l'ipv4" + extra_log, DECISION_VLAN) return (sw_name,
room,
u"Ok, Reassignation de l'ipv4" + extra_log,
DECISION_VLAN)
else: else:
return (sw_name, room, u'Machine OK' + extra_log, DECISION_VLAN) return (sw_name,
room,
u'Machine OK' + extra_log,
DECISION_VLAN)

View file

@ -20,5 +20,8 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""logs
The app in charge of the stats and logs of what's happening in re2o
"""
from .acl import * from .acl import *

View file

@ -26,6 +26,7 @@
Here are defined some functions to check acl on the application. Here are defined some functions to check acl on the application.
""" """
def can_view(user): def can_view(user):
"""Check if an user can view the application. """Check if an user can view the application.

View file

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

View file

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

View file

@ -20,4 +20,3 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

View file

@ -20,12 +20,16 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""logs.templatetags.logs_extra
A templatetag to get the class name for a given object
"""
from django import template from django import template
register = template.Library() register = template.Library()
@register.filter @register.filter
def classname(obj): def classname(obj):
""" Returns the object class name """
return obj.__class__.__name__ return obj.__class__.__name__

View file

@ -19,7 +19,10 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""logs.tests
The tests for the Logs module.
"""
from django.test import TestCase # from django.test import TestCase
# Create your tests here. # Create your tests here.

View file

@ -46,8 +46,6 @@ from django.db.models import Count, Max
from reversion.models import Revision from reversion.models import Revision
from reversion.models import Version, ContentType from reversion.models import Version, ContentType
from time import time
from users.models import ( from users.models import (
User, User,
ServiceUser, ServiceUser,
@ -109,15 +107,6 @@ from re2o.acl import (
from re2o.utils import all_active_assigned_interfaces_count from re2o.utils import all_active_assigned_interfaces_count
from re2o.utils import all_active_interfaces_count, SortTable from re2o.utils import all_active_interfaces_count, SortTable
STATS_DICT = {
0: ["Tout", 36],
1: ["1 mois", 1],
2: ["2 mois", 2],
3: ["6 mois", 6],
4: ["1 an", 12],
5: ["2 an", 24],
}
@login_required @login_required
@can_view_app('logs') @can_view_app('logs')
@ -227,66 +216,99 @@ def stats_general(request):
_all_baned = all_baned() _all_baned = all_baned()
_all_whitelisted = all_whitelisted() _all_whitelisted = all_whitelisted()
_all_active_interfaces_count = all_active_interfaces_count() _all_active_interfaces_count = all_active_interfaces_count()
_all_active_assigned_interfaces_count = all_active_assigned_interfaces_count() _all_active_assigned_interfaces_count = \
all_active_assigned_interfaces_count()
stats = [ stats = [
[["Categorie", "Nombre d'utilisateurs (total club et adhérents)", "Nombre d'adhérents", "Nombre de clubs"], { [ # First set of data (about users)
'active_users': [ [ # Headers
"Users actifs", "Categorie",
User.objects.filter(state=User.STATE_ACTIVE).count(), "Nombre d'utilisateurs (total club et adhérents)",
Adherent.objects.filter(state=Adherent.STATE_ACTIVE).count(), "Nombre d'adhérents",
Club.objects.filter(state=Club.STATE_ACTIVE).count()], "Nombre de clubs"
'inactive_users': [ ],
"Users désactivés", { # Data
User.objects.filter(state=User.STATE_DISABLED).count(), 'active_users': [
Adherent.objects.filter(state=Adherent.STATE_DISABLED).count(), "Users actifs",
Club.objects.filter(state=Club.STATE_DISABLED).count()], User.objects.filter(state=User.STATE_ACTIVE).count(),
'archive_users': [ (Adherent.objects
"Users archivés", .filter(state=Adherent.STATE_ACTIVE)
User.objects.filter(state=User.STATE_ARCHIVE).count(), .count()),
Adherent.objects.filter(state=Adherent.STATE_ARCHIVE).count(), Club.objects.filter(state=Club.STATE_ACTIVE).count()
Club.objects.filter(state=Club.STATE_ARCHIVE).count()], ],
'adherent_users': [ 'inactive_users': [
"Cotisant à l'association", "Users désactivés",
_all_adherent.count(), User.objects.filter(state=User.STATE_DISABLED).count(),
_all_adherent.exclude(adherent__isnull=True).count(), (Adherent.objects
_all_adherent.exclude(club__isnull=True).count()], .filter(state=Adherent.STATE_DISABLED)
'connexion_users': [ .count()),
"Utilisateurs bénéficiant d'une connexion", Club.objects.filter(state=Club.STATE_DISABLED).count()
_all_has_access.count(), ],
_all_has_access.exclude(adherent__isnull=True).count(), 'archive_users': [
_all_has_access.exclude(club__isnull=True).count()], "Users archivés",
'ban_users': [ User.objects.filter(state=User.STATE_ARCHIVE).count(),
"Utilisateurs bannis", (Adherent.objects
_all_baned.count(), .filter(state=Adherent.STATE_ARCHIVE)
_all_baned.exclude(adherent__isnull=True).count(), .count()),
_all_baned.exclude(club__isnull=True).count()], Club.objects.filter(state=Club.STATE_ARCHIVE).count()
'whitelisted_user': [ ],
"Utilisateurs bénéficiant d'une connexion gracieuse", 'adherent_users': [
_all_whitelisted.count(), "Cotisant à l'association",
_all_whitelisted.exclude(adherent__isnull=True).count(), _all_adherent.count(),
_all_whitelisted.exclude(club__isnull=True).count()], _all_adherent.exclude(adherent__isnull=True).count(),
'actives_interfaces': [ _all_adherent.exclude(club__isnull=True).count()
"Interfaces actives (ayant accès au reseau)", ],
_all_active_interfaces_count.count(), 'connexion_users': [
_all_active_interfaces_count.exclude( "Utilisateurs bénéficiant d'une connexion",
machine__user__adherent__isnull=True _all_has_access.count(),
).count(), _all_has_access.exclude(adherent__isnull=True).count(),
_all_active_interfaces_count.exclude( _all_has_access.exclude(club__isnull=True).count()
machine__user__club__isnull=True ],
).count()], 'ban_users': [
'actives_assigned_interfaces': [ "Utilisateurs bannis",
"Interfaces actives et assignées ipv4", _all_baned.count(),
_all_active_assigned_interfaces_count.count(), _all_baned.exclude(adherent__isnull=True).count(),
_all_active_assigned_interfaces_count.exclude( _all_baned.exclude(club__isnull=True).count()
machine__user__adherent__isnull=True ],
).count(), 'whitelisted_user': [
_all_active_assigned_interfaces_count.exclude( "Utilisateurs bénéficiant d'une connexion gracieuse",
machine__user__club__isnull=True _all_whitelisted.count(),
).count()] _all_whitelisted.exclude(adherent__isnull=True).count(),
}], _all_whitelisted.exclude(club__isnull=True).count()
[["Range d'ip", "Vlan", "Nombre d'ip totales", "Ip assignées", ],
"Ip assignées à une machine active", "Ip non assignées"], ip_dict] 'actives_interfaces': [
"Interfaces actives (ayant accès au reseau)",
_all_active_interfaces_count.count(),
(_all_active_interfaces_count
.exclude(machine__user__adherent__isnull=True)
.count()),
(_all_active_interfaces_count
.exclude(machine__user__club__isnull=True)
.count())
],
'actives_assigned_interfaces': [
"Interfaces actives et assignées ipv4",
_all_active_assigned_interfaces_count.count(),
(_all_active_assigned_interfaces_count
.exclude(machine__user__adherent__isnull=True)
.count()),
(_all_active_assigned_interfaces_count
.exclude(machine__user__club__isnull=True)
.count())
]
}
],
[ # Second set of data (about ip adresses)
[ # Headers
"Range d'ip",
"Vlan",
"Nombre d'ip totales",
"Ip assignées",
"Ip assignées à une machine active",
"Ip non assignées"
],
ip_dict # Data already prepared
] ]
]
return render(request, 'logs/stats_general.html', {'stats_list': stats}) return render(request, 'logs/stats_general.html', {'stats_list': stats})
@ -313,11 +335,26 @@ def stats_models(request):
'whitelist': [Whitelist.PRETTY_NAME, Whitelist.objects.count()] 'whitelist': [Whitelist.PRETTY_NAME, Whitelist.objects.count()]
}, },
'Cotisations': { 'Cotisations': {
'factures': [Facture._meta.verbose_name.title(), Facture.objects.count()], 'factures': [
'vente': [Vente._meta.verbose_name.title(), Vente.objects.count()], Facture._meta.verbose_name.title(),
'cotisation': [Cotisation._meta.verbose_name.title(), Cotisation.objects.count()], Facture.objects.count()
'article': [Article._meta.verbose_name.title(), Article.objects.count()], ],
'banque': [Banque._meta.verbose_name.title(), Banque.objects.count()], 'vente': [
Vente._meta.verbose_name.title(),
Vente.objects.count()
],
'cotisation': [
Cotisation._meta.verbose_name.title(),
Cotisation.objects.count()
],
'article': [
Article._meta.verbose_name.title(),
Article.objects.count()
],
'banque': [
Banque._meta.verbose_name.title(),
Banque.objects.count()
],
}, },
'Machines': { 'Machines': {
'machine': [Machine.PRETTY_NAME, Machine.objects.count()], 'machine': [Machine.PRETTY_NAME, Machine.objects.count()],
@ -370,12 +407,6 @@ def stats_users(request):
nombre de machines par user, d'etablissements par user, nombre de machines par user, d'etablissements par user,
de moyens de paiements par user, de banque par user, de moyens de paiements par user, de banque par user,
de bannissement par user, etc""" de bannissement par user, etc"""
onglet = request.GET.get('onglet')
try:
_search_field = STATS_DICT[onglet]
except KeyError:
_search_field = STATS_DICT[0]
onglet = 0
stats = { stats = {
'Utilisateur': { 'Utilisateur': {
'Machines': User.objects.annotate( 'Machines': User.objects.annotate(
@ -410,11 +441,7 @@ def stats_users(request):
).order_by('-num')[:10], ).order_by('-num')[:10],
}, },
} }
return render(request, 'logs/stats_users.html', { return render(request, 'logs/stats_users.html', {'stats_list': stats})
'stats_list': stats,
'stats_dict': STATS_DICT,
'active_field': onglet
})
@login_required @login_required
@ -432,14 +459,21 @@ def stats_actions(request):
} }
return render(request, 'logs/stats_users.html', {'stats_list': stats}) return render(request, 'logs/stats_users.html', {'stats_list': stats})
@login_required @login_required
@can_view_app('users') @can_view_app('users')
def stats_droits(request): def stats_droits(request):
"""Affiche la liste des droits et les users ayant chaque droit""" """Affiche la liste des droits et les users ayant chaque droit"""
depart=time() stats_list = {}
stats_list={}
for droit in ListRight.objects.all().select_related('group_ptr'):
stats_list[droit]=droit.user_set.all().annotate(num=Count('revision'),last=Max('revision__date_created'))
return render(request, 'logs/stats_droits.html', {'stats_list': stats_list}) for droit in ListRight.objects.all().select_related('group_ptr'):
stats_list[droit] = droit.user_set.all().annotate(
num=Count('revision'),
last=Max('revision__date_created')
)
return render(
request,
'logs/stats_droits.html',
{'stats_list': stats_list}
)

View file

@ -20,5 +20,8 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""machines
The app in charge of everything related to the machines, the interface, ...
"""
from .acl import * from .acl import *

View file

@ -26,6 +26,7 @@
Here are defined some functions to check acl on the application. Here are defined some functions to check acl on the application.
""" """
def can_view(user): def can_view(user):
"""Check if an user can view the application. """Check if an user can view the application.

View file

@ -20,6 +20,9 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""machines.admin
The objects, fields and datastructures visible in the Django admin view
"""
from __future__ import unicode_literals from __future__ import unicode_literals
@ -44,74 +47,92 @@ from .models import (
class MachineAdmin(VersionAdmin): class MachineAdmin(VersionAdmin):
""" Admin view of a Machine object """
pass pass
class Ipv6ListAdmin(VersionAdmin): class Ipv6ListAdmin(VersionAdmin):
""" Admin view of a Ipv6List object """
pass pass
class IpTypeAdmin(VersionAdmin): class IpTypeAdmin(VersionAdmin):
""" Admin view of a IpType object """
pass pass
class MachineTypeAdmin(VersionAdmin): class MachineTypeAdmin(VersionAdmin):
""" Admin view of a MachineType object """
pass pass
class VlanAdmin(VersionAdmin): class VlanAdmin(VersionAdmin):
""" Admin view of a Vlan object """
pass pass
class ExtensionAdmin(VersionAdmin): class ExtensionAdmin(VersionAdmin):
""" Admin view of a Extension object """
pass pass
class SOAAdmin(VersionAdmin): class SOAAdmin(VersionAdmin):
""" Admin view of a SOA object """
pass pass
class MxAdmin(VersionAdmin): class MxAdmin(VersionAdmin):
""" Admin view of a MX object """
pass pass
class NsAdmin(VersionAdmin): class NsAdmin(VersionAdmin):
""" Admin view of a NS object """
pass pass
class TxtAdmin(VersionAdmin): class TxtAdmin(VersionAdmin):
""" Admin view of a TXT object """
pass pass
class SrvAdmin(VersionAdmin): class SrvAdmin(VersionAdmin):
""" Admin view of a SRV object """
pass pass
class NasAdmin(VersionAdmin): class NasAdmin(VersionAdmin):
""" Admin view of a Nas object """
pass pass
class IpListAdmin(VersionAdmin): class IpListAdmin(VersionAdmin):
""" Admin view of a Ipv4List object """
pass pass
class OuverturePortAdmin(VersionAdmin): class OuverturePortAdmin(VersionAdmin):
""" Admin view of a OuverturePort object """
pass pass
class OuverturePortListAdmin(VersionAdmin): class OuverturePortListAdmin(VersionAdmin):
""" Admin view of a OuverturePortList object """
pass pass
class InterfaceAdmin(VersionAdmin): class InterfaceAdmin(VersionAdmin):
list_display = ('machine','type','mac_address','ipv4','details') """ Admin view of a Interface object """
list_display = ('machine', 'type', 'mac_address', 'ipv4', 'details')
class DomainAdmin(VersionAdmin): class DomainAdmin(VersionAdmin):
""" Admin view of a Domain object """
list_display = ('interface_parent', 'name', 'extension', 'cname') list_display = ('interface_parent', 'name', 'extension', 'cname')
class ServiceAdmin(VersionAdmin): class ServiceAdmin(VersionAdmin):
""" Admin view of a ServiceAdmin object """
list_display = ('service_type', 'min_time_regen', 'regular_time_regen') list_display = ('service_type', 'min_time_regen', 'regular_time_regen')
@ -133,5 +154,3 @@ admin.site.register(Ipv6List, Ipv6ListAdmin)
admin.site.register(Nas, NasAdmin) admin.site.register(Nas, NasAdmin)
admin.site.register(OuverturePort, OuverturePortAdmin) admin.site.register(OuverturePort, OuverturePortAdmin)
admin.site.register(OuverturePortList, OuverturePortListAdmin) admin.site.register(OuverturePortList, OuverturePortListAdmin)

View file

@ -94,7 +94,8 @@ class EditInterfaceForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
self.fields['type'].label = 'Type de machine' self.fields['type'].label = 'Type de machine'
self.fields['type'].empty_label = "Séléctionner un type de machine" self.fields['type'].empty_label = "Séléctionner un type de machine"
if "ipv4" in self.fields: if "ipv4" in self.fields:
self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" self.fields['ipv4'].empty_label = ("Assignation automatique de "
"l'ipv4")
self.fields['ipv4'].queryset = IpList.objects.filter( self.fields['ipv4'].queryset = IpList.objects.filter(
interface__isnull=True interface__isnull=True
) )
@ -136,10 +137,10 @@ class AliasForm(FormRevMixin, ModelForm):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
user = kwargs.pop('user') user = kwargs.pop('user')
super(AliasForm, self).__init__(*args, prefix=prefix, **kwargs) super(AliasForm, self).__init__(*args, prefix=prefix, **kwargs)
can_use_all, reason = Extension.can_use_all(user) can_use_all, _reason = Extension.can_use_all(user)
if not can_use_all: if not can_use_all:
self.fields['extension'].queryset = Extension.objects.filter( self.fields['extension'].queryset = Extension.objects.filter(
need_infra=False need_infra=False
) )
@ -328,6 +329,7 @@ class MxForm(FormRevMixin, ModelForm):
interface_parent=None interface_parent=None
).select_related('extension') ).select_related('extension')
class DelMxForm(FormRevMixin, Form): class DelMxForm(FormRevMixin, Form):
"""Suppression d'un ou plusieurs MX""" """Suppression d'un ou plusieurs MX"""
mx = forms.ModelMultipleChoiceField( mx = forms.ModelMultipleChoiceField(
@ -472,10 +474,14 @@ class ServiceForm(FormRevMixin, ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(ServiceForm, self).__init__(*args, prefix=prefix, **kwargs) super(ServiceForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['servers'].queryset = Interface.objects.all()\ self.fields['servers'].queryset = (Interface.objects.all()
.select_related('domain__extension') .select_related(
'domain__extension'
))
def save(self, commit=True): def save(self, commit=True):
# TODO : None of the parents of ServiceForm use the commit
# parameter in .save()
instance = super(ServiceForm, self).save(commit=False) instance = super(ServiceForm, self).save(commit=False)
if commit: if commit:
instance.save() instance.save()

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-04-15 10:52
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('machines', '0077_auto_20180409_2243'),
]
operations = [
migrations.AlterField(
model_name='srv',
name='priority',
field=models.PositiveIntegerField(default=0, help_text="La priorité du serveur cible (valeur entière non négative, plus elle est faible, plus ce serveur sera utilisé s'il est disponible)", validators=[django.core.validators.MaxValueValidator(65535)]),
),
]

View file

@ -20,14 +20,17 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""machines.models
The models definitions for the Machines app
"""
from __future__ import unicode_literals from __future__ import unicode_literals
from datetime import timedelta from datetime import timedelta
import re import re
from netaddr import mac_bare, EUI, IPSet, IPRange, IPNetwork, IPAddress
from ipaddress import IPv6Address from ipaddress import IPv6Address
from itertools import chain from itertools import chain
from netaddr import mac_bare, EUI, IPSet, IPRange, IPNetwork, IPAddress
from django.db import models from django.db import models
from django.db.models.signals import post_save, post_delete from django.db.models.signals import post_save, post_delete
@ -40,7 +43,7 @@ from django.core.validators import MaxValueValidator
from macaddress.fields import MACAddressField from macaddress.fields import MACAddressField
from re2o.field_permissions import FieldPermissionModelMixin from re2o.field_permissions import FieldPermissionModelMixin
from re2o.mixins import AclMixin, RevMixin from re2o.mixins import AclMixin, RevMixin
import users.models import users.models
import preferences.models import preferences.models
@ -63,23 +66,30 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model):
class Meta: class Meta:
permissions = ( permissions = (
("view_machine", "Peut voir un objet machine quelquonque"), ("view_machine", "Peut voir un objet machine quelquonque"),
("change_machine_user", "Peut changer le propriétaire d'une machine"), ("change_machine_user",
"Peut changer le propriétaire d'une machine"),
) )
def get_instance(machineid, *args, **kwargs): @classmethod
def get_instance(cls, machineid, *_args, **_kwargs):
"""Get the Machine instance with machineid. """Get the Machine instance with machineid.
:param userid: The id :param userid: The id
:return: The user :return: The user
""" """
return Machine.objects.get(pk=machineid) return cls.objects.get(pk=machineid)
def linked_objects(self): def linked_objects(self):
"""Return linked objects : machine and domain. """Return linked objects : machine and domain.
Usefull in history display""" Usefull in history display"""
return chain(self.interface_set.all(), Domain.objects.filter(interface_parent__in=self.interface_set.all())) return chain(
self.interface_set.all(),
Domain.objects.filter(
interface_parent__in=self.interface_set.all()
)
)
@staticmethod @staticmethod
def can_change_user(user_request, *args, **kwargs): def can_change_user(user_request, *_args, **_kwargs):
"""Checks if an user is allowed to change the user who owns a """Checks if an user is allowed to change the user who owns a
Machine. Machine.
@ -90,18 +100,22 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model):
A tuple with a boolean stating if edition is allowed and an A tuple with a boolean stating if edition is allowed and an
explanation message. explanation message.
""" """
return user_request.has_perm('machines.change_machine_user'), "Vous ne pouvez pas modifier l'utilisateur de la machine." return (user_request.has_perm('machines.change_machine_user'),
"Vous ne pouvez pas modifier l'utilisateur de la machine.")
def can_view_all(user_request, *args, **kwargs): @staticmethod
def can_view_all(user_request, *_args, **_kwargs):
"""Vérifie qu'on peut bien afficher l'ensemble des machines, """Vérifie qu'on peut bien afficher l'ensemble des machines,
droit particulier correspondant droit particulier correspondant
:param user_request: instance user qui fait l'edition :param user_request: instance user qui fait l'edition
:return: True ou False avec la raison de l'échec le cas échéant""" :return: True ou False avec la raison de l'échec le cas échéant"""
if not user_request.has_perm('machines.view_machine'): if not user_request.has_perm('machines.view_machine'):
return False, u"Vous ne pouvez pas afficher l'ensemble des machines sans permission" return False, (u"Vous ne pouvez pas afficher l'ensemble des "
"machines sans permission")
return True, None return True, None
def can_create(user_request, userid, *args, **kwargs): @staticmethod
def can_create(user_request, userid, *_args, **_kwargs):
"""Vérifie qu'un user qui fait la requète peut bien créer la machine """Vérifie qu'un user qui fait la requète peut bien créer la machine
et n'a pas atteint son quota, et crée bien une machine à lui et n'a pas atteint son quota, et crée bien une machine à lui
:param user_request: Utilisateur qui fait la requête :param user_request: Utilisateur qui fait la requête
@ -111,17 +125,21 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model):
user = users.models.User.objects.get(pk=userid) user = users.models.User.objects.get(pk=userid)
except users.models.User.DoesNotExist: except users.models.User.DoesNotExist:
return False, u"Utilisateur inexistant" return False, u"Utilisateur inexistant"
max_lambdauser_interfaces = preferences.models.OptionalMachine.get_cached_value('max_lambdauser_interfaces') max_lambdauser_interfaces = (preferences.models.OptionalMachine
.get_cached_value(
'max_lambdauser_interfaces'
))
if not user_request.has_perm('machines.add_machine'): if not user_request.has_perm('machines.add_machine'):
if not preferences.models.OptionalMachine.get_cached_value('create_machine'): if not (preferences.models.OptionalMachine
.get_cached_value('create_machine')):
return False, u"Vous ne pouvez pas ajouter une machine" return False, u"Vous ne pouvez pas ajouter une machine"
if user != user_request: if user != user_request:
return False, u"Vous ne pouvez pas ajouter une machine à un\ return False, (u"Vous ne pouvez pas ajouter une machine à un "
autre user que vous sans droit" "autre user que vous sans droit")
if user.user_interfaces().count() >= max_lambdauser_interfaces: if user.user_interfaces().count() >= max_lambdauser_interfaces:
return False, u"Vous avez atteint le maximum d'interfaces\ return False, (u"Vous avez atteint le maximum d'interfaces "
autorisées que vous pouvez créer vous même (%s) "\ "autorisées que vous pouvez créer vous même "
% max_lambdauser_interfaces "(%s) " % max_lambdauser_interfaces)
return True, None return True, None
def can_edit(self, user_request, *args, **kwargs): def can_edit(self, user_request, *args, **kwargs):
@ -131,9 +149,15 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model):
:param user_request: instance user qui fait l'edition :param user_request: instance user qui fait l'edition
:return: True ou False avec la raison le cas échéant""" :return: True ou False avec la raison le cas échéant"""
if self.user != user_request: if self.user != user_request:
if not user_request.has_perm('machines.change_interface') or not self.user.can_edit(self.user, user_request, *args, **kwargs)[0]: if (not user_request.has_perm('machines.change_interface') or
return False, u"Vous ne pouvez pas éditer une machine\ not self.user.can_edit(
d'un autre user que vous sans droit" self.user,
user_request,
*args,
**kwargs
)[0]):
return False, (u"Vous ne pouvez pas éditer une machine "
"d'un autre user que vous sans droit")
return True, None return True, None
def can_delete(self, user_request, *args, **kwargs): def can_delete(self, user_request, *args, **kwargs):
@ -143,26 +167,33 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model):
:param user_request: instance user qui fait l'edition :param user_request: instance user qui fait l'edition
:return: True ou False avec la raison de l'échec le cas échéant""" :return: True ou False avec la raison de l'échec le cas échéant"""
if self.user != user_request: if self.user != user_request:
if not user_request.has_perm('machines.change_interface') or not self.user.can_edit(self.user, user_request, *args, **kwargs)[0]: if (not user_request.has_perm('machines.change_interface') or
return False, u"Vous ne pouvez pas éditer une machine\ not self.user.can_edit(
d'un autre user que vous sans droit" self.user,
user_request,
*args,
**kwargs
)[0]):
return False, (u"Vous ne pouvez pas éditer une machine "
"d'un autre user que vous sans droit")
return True, None return True, None
def can_view(self, user_request, *args, **kwargs): def can_view(self, user_request, *_args, **_kwargs):
"""Vérifie qu'on peut bien voir cette instance particulière (soit """Vérifie qu'on peut bien voir cette instance particulière (soit
machine de soi, soit droit particulier machine de soi, soit droit particulier
:param self: instance machine à éditer :param self: instance machine à éditer
:param user_request: instance user qui fait l'edition :param user_request: instance user qui fait l'edition
:return: True ou False avec la raison de l'échec le cas échéant""" :return: True ou False avec la raison de l'échec le cas échéant"""
if not user_request.has_perm('machines.view_machine') and self.user != user_request: if (not user_request.has_perm('machines.view_machine') and
return False, u"Vous n'avez pas droit de voir les machines autre\ self.user != user_request):
que les vôtres" return False, (u"Vous n'avez pas droit de voir les machines autre "
"que les vôtres")
return True, None return True, None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Machine, self).__init__(*args, **kwargs) super(Machine, self).__init__(*args, **kwargs)
self.field_permissions = { self.field_permissions = {
'user' : self.can_change_user, 'user': self.can_change_user,
} }
def __str__(self): def __str__(self):
@ -184,7 +215,8 @@ class MachineType(RevMixin, AclMixin, models.Model):
class Meta: class Meta:
permissions = ( permissions = (
("view_machinetype", "Peut voir un objet machinetype"), ("view_machinetype", "Peut voir un objet machinetype"),
("use_all_machinetype", "Peut utiliser n'importe quel type de machine"), ("use_all_machinetype",
"Peut utiliser n'importe quel type de machine"),
) )
def all_interfaces(self): def all_interfaces(self):
@ -192,7 +224,8 @@ class MachineType(RevMixin, AclMixin, models.Model):
machinetype""" machinetype"""
return Interface.objects.filter(type=self) return Interface.objects.filter(type=self)
def can_use_all(user_request, *args, **kwargs): @staticmethod
def can_use_all(user_request, *_args, **_kwargs):
"""Check if an user can use every MachineType. """Check if an user can use every MachineType.
Args: Args:
@ -202,7 +235,8 @@ class MachineType(RevMixin, AclMixin, models.Model):
message is acces is not allowed. message is acces is not allowed.
""" """
if not user_request.has_perm('machines.use_all_machinetype'): if not user_request.has_perm('machines.use_all_machinetype'):
return False, u"Vous n'avez pas le droit d'utiliser tout types de machines" return False, (u"Vous n'avez pas le droit d'utiliser tout types "
"de machines")
return True, None return True, None
def __str__(self): def __str__(self):
@ -300,7 +334,11 @@ class IpType(RevMixin, AclMixin, models.Model):
if not self.prefix_v6: if not self.prefix_v6:
return return
else: else:
for ipv6 in Ipv6List.objects.filter(interface__in=Interface.objects.filter(type__in=MachineType.objects.filter(ip_type=self))): for ipv6 in Ipv6List.objects.filter(
interface__in=Interface.objects.filter(
type__in=MachineType.objects.filter(ip_type=self)
)
):
ipv6.check_and_replace_prefix(prefix=self.prefix_v6) ipv6.check_and_replace_prefix(prefix=self.prefix_v6)
def clean(self): def clean(self):
@ -329,8 +367,10 @@ class IpType(RevMixin, AclMixin, models.Model):
self.clean() self.clean()
super(IpType, self).save(*args, **kwargs) super(IpType, self).save(*args, **kwargs)
def can_use_all(user_request, *args, **kwargs): @staticmethod
"""Superdroit qui permet d'utiliser toutes les extensions sans restrictions def can_use_all(user_request, *_args, **_kwargs):
"""Superdroit qui permet d'utiliser toutes les extensions sans
restrictions
:param user_request: instance user qui fait l'edition :param user_request: instance user qui fait l'edition
:return: True ou False avec la raison de l'échec le cas échéant""" :return: True ou False avec la raison de l'échec le cas échéant"""
return user_request.has_perm('machines.use_all_iptype'), None return user_request.has_perm('machines.use_all_iptype'), None
@ -409,17 +449,17 @@ class SOA(RevMixin, AclMixin, models.Model):
help_text='Email du contact pour la zone' help_text='Email du contact pour la zone'
) )
refresh = models.PositiveIntegerField( refresh = models.PositiveIntegerField(
default=86400, # 24 hours default=86400, # 24 hours
help_text='Secondes avant que les DNS secondaires doivent demander le\ help_text='Secondes avant que les DNS secondaires doivent demander le\
serial du DNS primaire pour détecter une modification' serial du DNS primaire pour détecter une modification'
) )
retry = models.PositiveIntegerField( retry = models.PositiveIntegerField(
default=7200, # 2 hours default=7200, # 2 hours
help_text='Secondes avant que les DNS secondaires fassent une nouvelle\ help_text='Secondes avant que les DNS secondaires fassent une nouvelle\
demande de serial en cas de timeout du DNS primaire' demande de serial en cas de timeout du DNS primaire'
) )
expire = models.PositiveIntegerField( expire = models.PositiveIntegerField(
default=3600000, # 1000 hours default=3600000, # 1000 hours
help_text='Secondes après lesquelles les DNS secondaires arrêtent de\ help_text='Secondes après lesquelles les DNS secondaires arrêtent de\
de répondre aux requêtes en cas de timeout du DNS primaire' de répondre aux requêtes en cas de timeout du DNS primaire'
) )
@ -469,8 +509,10 @@ class SOA(RevMixin, AclMixin, models.Model):
extensions . extensions .
/!\ Ne jamais supprimer ou renommer cette fonction car elle est /!\ Ne jamais supprimer ou renommer cette fonction car elle est
utilisée dans les migrations de la BDD. """ utilisée dans les migrations de la BDD. """
return cls.objects.get_or_create(name="SOA to edit", mail="postmaser@example.com")[0].pk return cls.objects.get_or_create(
name="SOA to edit",
mail="postmaser@example.com"
)[0].pk
class Extension(RevMixin, AclMixin, models.Model): class Extension(RevMixin, AclMixin, models.Model):
@ -521,8 +563,10 @@ class Extension(RevMixin, AclMixin, models.Model):
entry += "@ IN AAAA " + str(self.origin_v6) entry += "@ IN AAAA " + str(self.origin_v6)
return entry return entry
def can_use_all(user_request, *args, **kwargs): @staticmethod
"""Superdroit qui permet d'utiliser toutes les extensions sans restrictions def can_use_all(user_request, *_args, **_kwargs):
"""Superdroit qui permet d'utiliser toutes les extensions sans
restrictions
:param user_request: instance user qui fait l'edition :param user_request: instance user qui fait l'edition
:return: True ou False avec la raison de l'échec le cas échéant""" :return: True ou False avec la raison de l'échec le cas échéant"""
return user_request.has_perm('machines.use_all_extension'), None return user_request.has_perm('machines.use_all_extension'), None
@ -555,7 +599,10 @@ class Mx(RevMixin, AclMixin, models.Model):
def dns_entry(self): def dns_entry(self):
"""Renvoie l'entrée DNS complète pour un MX à mettre dans les """Renvoie l'entrée DNS complète pour un MX à mettre dans les
fichiers de zones""" fichiers de zones"""
return "@ IN MX " + str(self.priority).ljust(3) + " " + str(self.name) return "@ IN MX {prior} {name}".format(
prior=str(self.priority).ljust(3),
name=str(self.name)
)
def __str__(self): def __str__(self):
return str(self.zone) + ' ' + str(self.priority) + ' ' + str(self.name) return str(self.zone) + ' ' + str(self.priority) + ' ' + str(self.name)
@ -606,12 +653,13 @@ class Txt(RevMixin, AclMixin, models.Model):
class Srv(RevMixin, AclMixin, models.Model): class Srv(RevMixin, AclMixin, models.Model):
""" A SRV record """
PRETTY_NAME = "Enregistrement Srv" PRETTY_NAME = "Enregistrement Srv"
TCP = 'TCP' TCP = 'TCP'
UDP = 'UDP' UDP = 'UDP'
service = models.CharField(max_length=31) service = models.CharField(max_length=31)
protocole = models.CharField( protocole = models.CharField(
max_length=3, max_length=3,
choices=( choices=(
@ -628,9 +676,9 @@ class Srv(RevMixin, AclMixin, models.Model):
priority = models.PositiveIntegerField( priority = models.PositiveIntegerField(
default=0, default=0,
validators=[MaxValueValidator(65535)], validators=[MaxValueValidator(65535)],
help_text="La priorité du serveur cible (valeur entière non négative,\ help_text=("La priorité du serveur cible (valeur entière non "
plus elle est faible, plus ce serveur sera utilisé s'il est disponible)" "négative, plus elle est faible, plus ce serveur sera "
"utilisé s'il est disponible)")
) )
weight = models.PositiveIntegerField( weight = models.PositiveIntegerField(
default=0, default=0,
@ -667,7 +715,7 @@ class Srv(RevMixin, AclMixin, models.Model):
str(self.port) + ' ' + str(self.target) + '.' str(self.port) + ' ' + str(self.target) + '.'
class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model): class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
""" Une interface. Objet clef de l'application machine : """ Une interface. Objet clef de l'application machine :
- une address mac unique. Possibilité de la rendre unique avec le - une address mac unique. Possibilité de la rendre unique avec le
typemachine typemachine
@ -692,7 +740,8 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
class Meta: class Meta:
permissions = ( permissions = (
("view_interface", "Peut voir un objet interface"), ("view_interface", "Peut voir un objet interface"),
("change_interface_machine", "Peut changer le propriétaire d'une interface"), ("change_interface_machine",
"Peut changer le propriétaire d'une interface"),
) )
@cached_property @cached_property
@ -719,7 +768,10 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
prefix_v6 = self.type.ip_type.prefix_v6 prefix_v6 = self.type.ip_type.prefix_v6
if not prefix_v6: if not prefix_v6:
return None return None
return IPv6Address(IPv6Address(prefix_v6).exploded[:20] + IPv6Address(self.id).exploded[20:]) return IPv6Address(
IPv6Address(prefix_v6).exploded[:20] +
IPv6Address(self.id).exploded[20:]
)
def sync_ipv6_dhcpv6(self): def sync_ipv6_dhcpv6(self):
"""Affecte une ipv6 dhcpv6 calculée à partir de l'id de la machine""" """Affecte une ipv6 dhcpv6 calculée à partir de l'id de la machine"""
@ -741,7 +793,9 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
ipv6_slaac = self.ipv6_slaac ipv6_slaac = self.ipv6_slaac
if not ipv6_slaac: if not ipv6_slaac:
return return
ipv6_object = Ipv6List.objects.filter(interface=self, slaac_ip=True).first() ipv6_object = (Ipv6List.objects
.filter(interface=self, slaac_ip=True)
.first())
if not ipv6_object: if not ipv6_object:
ipv6_object = Ipv6List(interface=self, slaac_ip=True) ipv6_object = Ipv6List(interface=self, slaac_ip=True)
if ipv6_object.ipv6 != str(ipv6_slaac): if ipv6_object.ipv6 != str(ipv6_slaac):
@ -750,19 +804,24 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
def sync_ipv6(self): def sync_ipv6(self):
"""Cree et met à jour l'ensemble des ipv6 en fonction du mode choisi""" """Cree et met à jour l'ensemble des ipv6 en fonction du mode choisi"""
if preferences.models.OptionalMachine.get_cached_value('ipv6_mode') == 'SLAAC': if (preferences.models.OptionalMachine
.get_cached_value('ipv6_mode') == 'SLAAC'):
self.sync_ipv6_slaac() self.sync_ipv6_slaac()
elif preferences.models.OptionalMachine.get_cached_value('ipv6_mode') == 'DHCPV6': elif (preferences.models.OptionalMachine
.get_cached_value('ipv6_mode') == 'DHCPV6'):
self.sync_ipv6_dhcpv6() self.sync_ipv6_dhcpv6()
else: else:
return return
def ipv6(self): def ipv6(self):
""" Renvoie le queryset de la liste des ipv6 """ Renvoie le queryset de la liste des ipv6
On renvoie l'ipv6 slaac que si le mode slaac est activé (et non dhcpv6)""" On renvoie l'ipv6 slaac que si le mode slaac est activé
if preferences.models.OptionalMachine.get_cached_value('ipv6_mode') == 'SLAAC': (et non dhcpv6)"""
if (preferences.models.OptionalMachine
.get_cached_value('ipv6_mode') == 'SLAAC'):
return self.ipv6list.all() return self.ipv6list.all()
elif preferences.models.OptionalMachine.get_cached_value('ipv6_mode') == 'DHCPV6': elif (preferences.models.OptionalMachine
.get_cached_value('ipv6_mode') == 'DHCPV6'):
return self.ipv6list.filter(slaac_ip=False) return self.ipv6list.filter(slaac_ip=False)
else: else:
return None return None
@ -789,7 +848,7 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
# instance. # instance.
# But in our case, it's impossible to create a type value so we raise # But in our case, it's impossible to create a type value so we raise
# the error. # the error.
if not hasattr(self, 'type') : if not hasattr(self, 'type'):
raise ValidationError("Le type d'ip choisi n'est pas valide") raise ValidationError("Le type d'ip choisi n'est pas valide")
self.filter_macaddress() self.filter_macaddress()
self.mac_address = str(EUI(self.mac_address)) or None self.mac_address = str(EUI(self.mac_address)) or None
@ -825,7 +884,8 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
correspondent pas") correspondent pas")
super(Interface, self).save(*args, **kwargs) super(Interface, self).save(*args, **kwargs)
def can_create(user_request, machineid, *args, **kwargs): @staticmethod
def can_create(user_request, machineid, *_args, **_kwargs):
"""Verifie que l'user a les bons droits infra pour créer """Verifie que l'user a les bons droits infra pour créer
une interface, ou bien que la machine appartient bien à l'user une interface, ou bien que la machine appartient bien à l'user
:param macineid: Id de la machine parente de l'interface :param macineid: Id de la machine parente de l'interface
@ -836,21 +896,29 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
except Machine.DoesNotExist: except Machine.DoesNotExist:
return False, u"Machine inexistante" return False, u"Machine inexistante"
if not user_request.has_perm('machines.add_interface'): if not user_request.has_perm('machines.add_interface'):
if not preferences.models.OptionalMachine.get_cached_value('create_machine'): if not (preferences.models.OptionalMachine
.get_cached_value('create_machine')):
return False, u"Vous ne pouvez pas ajouter une machine" return False, u"Vous ne pouvez pas ajouter une machine"
max_lambdauser_interfaces = preferences.models.OptionalMachine.get_cached_value('max_lambdauser_interfaces') max_lambdauser_interfaces = (preferences.models.OptionalMachine
.get_cached_value(
'max_lambdauser_interfaces'
))
if machine.user != user_request: if machine.user != user_request:
return False, u"Vous ne pouvez pas ajouter une interface à une\ return False, u"Vous ne pouvez pas ajouter une interface à une\
machine d'un autre user que vous sans droit" machine d'un autre user que vous sans droit"
if machine.user.user_interfaces().count() >= max_lambdauser_interfaces: if (machine.user.user_interfaces().count() >=
max_lambdauser_interfaces):
return False, u"Vous avez atteint le maximum d'interfaces\ return False, u"Vous avez atteint le maximum d'interfaces\
autorisées que vous pouvez créer vous même (%s) "\ autorisées que vous pouvez créer vous même (%s) "\
% max_lambdauser_interfaces % max_lambdauser_interfaces
return True, None return True, None
@staticmethod @staticmethod
def can_change_machine(user_request, *args, **kwargs): def can_change_machine(user_request, *_args, **_kwargs):
return user_request.has_perm('machines.change_interface_machine'), "Droit requis pour changer la machine" """Check if a user can change the machine associated with an
Interface object """
return (user_request.has_perm('machines.change_interface_machine'),
"Droit requis pour changer la machine")
def can_edit(self, user_request, *args, **kwargs): def can_edit(self, user_request, *args, **kwargs):
"""Verifie que l'user a les bons droits infra pour editer """Verifie que l'user a les bons droits infra pour editer
@ -859,9 +927,14 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
:param user_request: Utilisateur qui fait la requête :param user_request: Utilisateur qui fait la requête
:return: soit True, soit False avec la raison de l'échec""" :return: soit True, soit False avec la raison de l'échec"""
if self.machine.user != user_request: if self.machine.user != user_request:
if not user_request.has_perm('machines.change_interface') or not self.machine.user.can_edit(user_request, *args, **kwargs)[0]: if (not user_request.has_perm('machines.change_interface') or
return False, u"Vous ne pouvez pas éditer une machine\ not self.machine.user.can_edit(
d'un autre user que vous sans droit" user_request,
*args,
**kwargs
)[0]):
return False, (u"Vous ne pouvez pas éditer une machine "
"d'un autre user que vous sans droit")
return True, None return True, None
def can_delete(self, user_request, *args, **kwargs): def can_delete(self, user_request, *args, **kwargs):
@ -871,26 +944,32 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
:param user_request: Utilisateur qui fait la requête :param user_request: Utilisateur qui fait la requête
:return: soit True, soit False avec la raison de l'échec""" :return: soit True, soit False avec la raison de l'échec"""
if self.machine.user != user_request: if self.machine.user != user_request:
if not user_request.has_perm('machines.change_interface') or not self.machine.user.can_edit(user_request, *args, **kwargs)[0]: if (not user_request.has_perm('machines.change_interface') or
return False, u"Vous ne pouvez pas éditer une machine\ not self.machine.user.can_edit(
d'un autre user que vous sans droit" user_request,
*args,
**kwargs
)[0]):
return False, (u"Vous ne pouvez pas éditer une machine "
"d'un autre user que vous sans droit")
return True, None return True, None
def can_view(self, user_request, *args, **kwargs): def can_view(self, user_request, *_args, **_kwargs):
"""Vérifie qu'on peut bien voir cette instance particulière avec """Vérifie qu'on peut bien voir cette instance particulière avec
droit view objet ou qu'elle appartient à l'user droit view objet ou qu'elle appartient à l'user
:param self: instance interface à voir :param self: instance interface à voir
:param user_request: instance user qui fait l'edition :param user_request: instance user qui fait l'edition
:return: True ou False avec la raison de l'échec le cas échéant""" :return: True ou False avec la raison de l'échec le cas échéant"""
if not user_request.has_perm('machines.view_interface') and self.machine.user != user_request: if (not user_request.has_perm('machines.view_interface') and
return False, u"Vous n'avez pas le droit de voir des machines autre\ self.machine.user != user_request):
que les vôtres" return False, (u"Vous n'avez pas le droit de voir des machines "
"autre que les vôtres")
return True, None return True, None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Interface, self).__init__(*args, **kwargs) super(Interface, self).__init__(*args, **kwargs)
self.field_permissions = { self.field_permissions = {
'machine' : self.can_change_machine, 'machine': self.can_change_machine,
} }
def __str__(self): def __str__(self):
@ -915,22 +994,29 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
""" A list of IPv6 """
PRETTY_NAME = 'Enregistrements Ipv6 des machines' PRETTY_NAME = 'Enregistrements Ipv6 des machines'
ipv6 = models.GenericIPAddressField( ipv6 = models.GenericIPAddressField(
protocol='IPv6', protocol='IPv6',
unique=True unique=True
) )
interface = models.ForeignKey('Interface', on_delete=models.CASCADE, related_name='ipv6list') interface = models.ForeignKey(
'Interface',
on_delete=models.CASCADE,
related_name='ipv6list'
)
slaac_ip = models.BooleanField(default=False) slaac_ip = models.BooleanField(default=False)
class Meta: class Meta:
permissions = ( permissions = (
("view_ipv6list", "Peut voir un objet ipv6"), ("view_ipv6list", "Peut voir un objet ipv6"),
("change_ipv6list_slaac_ip", "Peut changer la valeur slaac sur une ipv6"), ("change_ipv6list_slaac_ip",
"Peut changer la valeur slaac sur une ipv6"),
) )
def can_create(user_request, interfaceid, *args, **kwargs): @staticmethod
def can_create(user_request, interfaceid, *_args, **_kwargs):
"""Verifie que l'user a les bons droits infra pour créer """Verifie que l'user a les bons droits infra pour créer
une ipv6, ou possède l'interface associée une ipv6, ou possède l'interface associée
:param interfaceid: Id de l'interface associée à cet objet domain :param interfaceid: Id de l'interface associée à cet objet domain
@ -947,8 +1033,10 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
return True, None return True, None
@staticmethod @staticmethod
def can_change_slaac_ip(user_request, *args, **kwargs): def can_change_slaac_ip(user_request, *_args, **_kwargs):
return user_request.has_perm('machines.change_ipv6list_slaac_ip'), "Droit requis pour changer la valeur slaac ip" """ Check if a user can change the slaac value """
return (user_request.has_perm('machines.change_ipv6list_slaac_ip'),
"Droit requis pour changer la valeur slaac ip")
def can_edit(self, user_request, *args, **kwargs): def can_edit(self, user_request, *args, **kwargs):
"""Verifie que l'user a les bons droits infra pour editer """Verifie que l'user a les bons droits infra pour editer
@ -957,9 +1045,14 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
:param user_request: Utilisateur qui fait la requête :param user_request: Utilisateur qui fait la requête
:return: soit True, soit False avec la raison de l'échec""" :return: soit True, soit False avec la raison de l'échec"""
if self.interface.machine.user != user_request: if self.interface.machine.user != user_request:
if not user_request.has_perm('machines.change_ipv6list') or not self.interface.machine.user.can_edit(user_request, *args, **kwargs)[0]: if (not user_request.has_perm('machines.change_ipv6list') or
return False, u"Vous ne pouvez pas éditer une machine\ not self.interface.machine.user.can_edit(
d'un autre user que vous sans droit" user_request,
*args,
**kwargs
)[0]):
return False, (u"Vous ne pouvez pas éditer une machine "
"d'un autre user que vous sans droit")
return True, None return True, None
def can_delete(self, user_request, *args, **kwargs): def can_delete(self, user_request, *args, **kwargs):
@ -969,26 +1062,32 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
:param user_request: Utilisateur qui fait la requête :param user_request: Utilisateur qui fait la requête
:return: soit True, soit False avec la raison de l'échec""" :return: soit True, soit False avec la raison de l'échec"""
if self.interface.machine.user != user_request: if self.interface.machine.user != user_request:
if not user_request.has_perm('machines.change_ipv6list') or not self.interface.machine.user.can_edit(user_request, *args, **kwargs)[0]: if (not user_request.has_perm('machines.change_ipv6list') or
return False, u"Vous ne pouvez pas éditer une machine\ not self.interface.machine.user.can_edit(
d'un autre user que vous sans droit" user_request,
*args,
**kwargs
)[0]):
return False, (u"Vous ne pouvez pas éditer une machine "
"d'un autre user que vous sans droit")
return True, None return True, None
def can_view(self, user_request, *args, **kwargs): def can_view(self, user_request, *_args, **_kwargs):
"""Vérifie qu'on peut bien voir cette instance particulière avec """Vérifie qu'on peut bien voir cette instance particulière avec
droit view objet ou qu'elle appartient à l'user droit view objet ou qu'elle appartient à l'user
:param self: instance interface à voir :param self: instance interface à voir
:param user_request: instance user qui fait l'edition :param user_request: instance user qui fait l'edition
:return: True ou False avec la raison de l'échec le cas échéant""" :return: True ou False avec la raison de l'échec le cas échéant"""
if not user_request.has_perm('machines.view_ipv6list') and self.interface.machine.user != user_request: if (not user_request.has_perm('machines.view_ipv6list') and
return False, u"Vous n'avez pas le droit de voir des machines autre\ self.interface.machine.user != user_request):
que les vôtres" return False, (u"Vous n'avez pas le droit de voir des machines "
"autre que les vôtres")
return True, None return True, None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Ipv6List, self).__init__(*args, **kwargs) super(Ipv6List, self).__init__(*args, **kwargs)
self.field_permissions = { self.field_permissions = {
'slaac_ip' : self.can_change_slaac_ip, 'slaac_ip': self.can_change_slaac_ip,
} }
def check_and_replace_prefix(self, prefix=None): def check_and_replace_prefix(self, prefix=None):
@ -996,17 +1095,27 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
prefix_v6 = prefix or self.interface.type.ip_type.prefix_v6 prefix_v6 = prefix or self.interface.type.ip_type.prefix_v6
if not prefix_v6: if not prefix_v6:
return return
if IPv6Address(self.ipv6).exploded[:20] != IPv6Address(prefix_v6).exploded[:20]: if (IPv6Address(self.ipv6).exploded[:20] !=
self.ipv6 = IPv6Address(IPv6Address(prefix_v6).exploded[:20] + IPv6Address(self.ipv6).exploded[20:]) IPv6Address(prefix_v6).exploded[:20]):
self.ipv6 = IPv6Address(
IPv6Address(prefix_v6).exploded[:20] +
IPv6Address(self.ipv6).exploded[20:]
)
self.save() self.save()
def clean(self, *args, **kwargs): def clean(self, *args, **kwargs):
if self.slaac_ip and Ipv6List.objects.filter(interface=self.interface, slaac_ip=True).exclude(id=self.id): if self.slaac_ip and (Ipv6List.objects
.filter(interface=self.interface, slaac_ip=True)
.exclude(id=self.id)):
raise ValidationError("Une ip slaac est déjà enregistrée") raise ValidationError("Une ip slaac est déjà enregistrée")
prefix_v6 = self.interface.type.ip_type.prefix_v6 prefix_v6 = self.interface.type.ip_type.prefix_v6
if prefix_v6: if prefix_v6:
if IPv6Address(self.ipv6).exploded[:20] != IPv6Address(prefix_v6).exploded[:20]: if (IPv6Address(self.ipv6).exploded[:20] !=
raise ValidationError("Le prefixv6 est incorrect et ne correspond pas au type associé à la machine") IPv6Address(prefix_v6).exploded[:20]):
raise ValidationError(
"Le prefixv6 est incorrect et ne correspond pas au type "
"associé à la machine"
)
super(Ipv6List, self).clean(*args, **kwargs) super(Ipv6List, self).clean(*args, **kwargs)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -1072,7 +1181,7 @@ class Domain(RevMixin, AclMixin, models.Model):
if self.cname == self: if self.cname == self:
raise ValidationError("On ne peut créer un cname sur lui même") raise ValidationError("On ne peut créer un cname sur lui même")
HOSTNAME_LABEL_PATTERN = re.compile( HOSTNAME_LABEL_PATTERN = re.compile(
"(?!-)[A-Z\d-]+(?<!-)$", r"(?!-)[A-Z\d-]+(?<!-)$",
re.IGNORECASE re.IGNORECASE
) )
dns = self.name.lower() dns = self.name.lower()
@ -1089,7 +1198,10 @@ class Domain(RevMixin, AclMixin, models.Model):
def dns_entry(self): def dns_entry(self):
""" Une entrée DNS""" """ Une entrée DNS"""
if self.cname: if self.cname:
return str(self.name).ljust(15) + " IN CNAME " + str(self.cname) + "." return "{name} IN CNAME {cname}.".format(
name=str(self.name).ljust(15),
cname=str(self.cname)
)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" Empèche le save sans extension valide. """ Empèche le save sans extension valide.
@ -1111,7 +1223,8 @@ class Domain(RevMixin, AclMixin, models.Model):
else: else:
return self.cname.get_parent_interface() return self.cname.get_parent_interface()
def can_create(user_request, interfaceid, *args, **kwargs): @staticmethod
def can_create(user_request, interfaceid, *_args, **_kwargs):
"""Verifie que l'user a les bons droits infra pour créer """Verifie que l'user a les bons droits infra pour créer
un domain, ou possède l'interface associée un domain, ou possède l'interface associée
:param interfaceid: Id de l'interface associée à cet objet domain :param interfaceid: Id de l'interface associée à cet objet domain
@ -1122,54 +1235,58 @@ class Domain(RevMixin, AclMixin, models.Model):
except Interface.DoesNotExist: except Interface.DoesNotExist:
return False, u"Interface inexistante" return False, u"Interface inexistante"
if not user_request.has_perm('machines.add_domain'): if not user_request.has_perm('machines.add_domain'):
max_lambdauser_aliases = preferences.models.OptionalMachine.get_cached_value('max_lambdauser_aliases') max_lambdauser_aliases = (preferences.models.OptionalMachine
.get_cached_value(
'max_lambdauser_aliases'
))
if interface.machine.user != user_request: if interface.machine.user != user_request:
return False, u"Vous ne pouvez pas ajouter un alias à une\ return False, (u"Vous ne pouvez pas ajouter un alias à une "
machine d'un autre user que vous sans droit" "machine d'un autre user que vous sans droit")
if Domain.objects.filter( if Domain.objects.filter(
cname__in=Domain.objects.filter( cname__in=Domain.objects.filter(
interface_parent__in=interface.machine.user.user_interfaces() interface_parent__in=(interface.machine.user
.user_interfaces())
) )
).count() >= max_lambdauser_aliases: ).count() >= max_lambdauser_aliases:
return False, u"Vous avez atteint le maximum d'alias\ return False, (u"Vous avez atteint le maximum d'alias "
autorisés que vous pouvez créer vous même (%s) "\ "autorisés que vous pouvez créer vous même "
% max_lambdauser_aliases "(%s) " % max_lambdauser_aliases)
return True, None return True, None
def can_edit(self, user_request, *args, **kwargs): def can_edit(self, user_request, *_args, **_kwargs):
"""Verifie que l'user a les bons droits pour editer """Verifie que l'user a les bons droits pour editer
cette instance domain cette instance domain
:param self: Instance domain à editer :param self: Instance domain à editer
:param user_request: Utilisateur qui fait la requête :param user_request: Utilisateur qui fait la requête
:return: soit True, soit False avec la raison de l'échec""" :return: soit True, soit False avec la raison de l'échec"""
if not user_request.has_perm('machines.change_domain') and\ if (not user_request.has_perm('machines.change_domain') and
self.get_source_interface.machine.user != user_request: self.get_source_interface.machine.user != user_request):
return False, u"Vous ne pouvez pas editer un alias à une machine\ return False, (u"Vous ne pouvez pas editer un alias à une machine "
d'un autre user que vous sans droit" "d'un autre user que vous sans droit")
return True, None return True, None
def can_delete(self, user_request, *args, **kwargs): def can_delete(self, user_request, *_args, **_kwargs):
"""Verifie que l'user a les bons droits delete object pour del """Verifie que l'user a les bons droits delete object pour del
cette instance domain, ou qu'elle lui appartient cette instance domain, ou qu'elle lui appartient
:param self: Instance domain à del :param self: Instance domain à del
:param user_request: Utilisateur qui fait la requête :param user_request: Utilisateur qui fait la requête
:return: soit True, soit False avec la raison de l'échec""" :return: soit True, soit False avec la raison de l'échec"""
if not user_request.has_perm('machines.delete_domain') and\ if (not user_request.has_perm('machines.delete_domain') and
self.get_source_interface.machine.user != user_request: self.get_source_interface.machine.user != user_request):
return False, u"Vous ne pouvez pas supprimer un alias à une machine\ return False, (u"Vous ne pouvez pas supprimer un alias à une "
d'un autre user que vous sans droit" "machine d'un autre user que vous sans droit")
return True, None return True, None
def can_view(self, user_request, *args, **kwargs): def can_view(self, user_request, *_args, **_kwargs):
"""Vérifie qu'on peut bien voir cette instance particulière avec """Vérifie qu'on peut bien voir cette instance particulière avec
droit view objet ou qu'elle appartient à l'user droit view objet ou qu'elle appartient à l'user
:param self: instance domain à voir :param self: instance domain à voir
:param user_request: instance user qui fait l'edition :param user_request: instance user qui fait l'edition
:return: True ou False avec la raison de l'échec le cas échéant""" :return: True ou False avec la raison de l'échec le cas échéant"""
if not user_request.has_perm('machines.view_domain') and\ if (not user_request.has_perm('machines.view_domain') and
self.get_source_interface.machine.user != user_request: self.get_source_interface.machine.user != user_request):
return False, u"Vous n'avez pas le droit de voir des machines autre\ return False, (u"Vous n'avez pas le droit de voir des machines "
que les vôtres" "autre que les vôtres")
return True, None return True, None
def __str__(self): def __str__(self):
@ -1177,6 +1294,7 @@ class Domain(RevMixin, AclMixin, models.Model):
class IpList(RevMixin, AclMixin, models.Model): class IpList(RevMixin, AclMixin, models.Model):
""" A list of IPv4 """
PRETTY_NAME = "Addresses ipv4" PRETTY_NAME = "Addresses ipv4"
ipv4 = models.GenericIPAddressField(protocol='IPv4', unique=True) ipv4 = models.GenericIPAddressField(protocol='IPv4', unique=True)
@ -1307,15 +1425,15 @@ class OuverturePortList(RevMixin, AclMixin, models.Model):
("view_ouvertureportlist", "Peut voir un objet ouvertureport"), ("view_ouvertureportlist", "Peut voir un objet ouvertureport"),
) )
def can_delete(self, user_request, *args, **kwargs): def can_delete(self, user_request, *_args, **_kwargs):
"""Verifie que l'user a les bons droits bureau pour delete """Verifie que l'user a les bons droits bureau pour delete
cette instance ouvertureportlist cette instance ouvertureportlist
:param self: Instance ouvertureportlist à delete :param self: Instance ouvertureportlist à delete
:param user_request: Utilisateur qui fait la requête :param user_request: Utilisateur qui fait la requête
:return: soit True, soit False avec la raison de l'échec""" :return: soit True, soit False avec la raison de l'échec"""
if not user_request.has_perm('machines.delete_ouvertureportlist'): if not user_request.has_perm('machines.delete_ouvertureportlist'):
return False, u"Vous n'avez pas le droit de supprimer une ouverture\ return False, (u"Vous n'avez pas le droit de supprimer une "
de port" "ouverture de port")
if self.interface_set.all(): if self.interface_set.all():
return False, u"Cette liste de ports est utilisée" return False, u"Cette liste de ports est utilisée"
return True, None return True, None
@ -1401,7 +1519,7 @@ class OuverturePort(RevMixin, AclMixin, models.Model):
@receiver(post_save, sender=Machine) @receiver(post_save, sender=Machine)
def machine_post_save(sender, **kwargs): def machine_post_save(**kwargs):
"""Synchronisation ldap et régen parefeu/dhcp lors de la modification """Synchronisation ldap et régen parefeu/dhcp lors de la modification
d'une machine""" d'une machine"""
user = kwargs['instance'].user user = kwargs['instance'].user
@ -1411,7 +1529,7 @@ def machine_post_save(sender, **kwargs):
@receiver(post_delete, sender=Machine) @receiver(post_delete, sender=Machine)
def machine_post_delete(sender, **kwargs): def machine_post_delete(**kwargs):
"""Synchronisation ldap et régen parefeu/dhcp lors de la suppression """Synchronisation ldap et régen parefeu/dhcp lors de la suppression
d'une machine""" d'une machine"""
machine = kwargs['instance'] machine = kwargs['instance']
@ -1422,7 +1540,7 @@ def machine_post_delete(sender, **kwargs):
@receiver(post_save, sender=Interface) @receiver(post_save, sender=Interface)
def interface_post_save(sender, **kwargs): def interface_post_save(**kwargs):
"""Synchronisation ldap et régen parefeu/dhcp lors de la modification """Synchronisation ldap et régen parefeu/dhcp lors de la modification
d'une interface""" d'une interface"""
interface = kwargs['instance'] interface = kwargs['instance']
@ -1435,7 +1553,7 @@ def interface_post_save(sender, **kwargs):
@receiver(post_delete, sender=Interface) @receiver(post_delete, sender=Interface)
def interface_post_delete(sender, **kwargs): def interface_post_delete(**kwargs):
"""Synchronisation ldap et régen parefeu/dhcp lors de la suppression """Synchronisation ldap et régen parefeu/dhcp lors de la suppression
d'une interface""" d'une interface"""
interface = kwargs['instance'] interface = kwargs['instance']
@ -1444,7 +1562,7 @@ def interface_post_delete(sender, **kwargs):
@receiver(post_save, sender=IpType) @receiver(post_save, sender=IpType)
def iptype_post_save(sender, **kwargs): def iptype_post_save(**kwargs):
"""Generation des objets ip après modification d'un range ip""" """Generation des objets ip après modification d'un range ip"""
iptype = kwargs['instance'] iptype = kwargs['instance']
iptype.gen_ip_range() iptype.gen_ip_range()
@ -1452,7 +1570,7 @@ def iptype_post_save(sender, **kwargs):
@receiver(post_save, sender=MachineType) @receiver(post_save, sender=MachineType)
def machine_post_save(sender, **kwargs): def machinetype_post_save(**kwargs):
"""Mise à jour des interfaces lorsque changement d'attribution """Mise à jour des interfaces lorsque changement d'attribution
d'une machinetype (changement iptype parent)""" d'une machinetype (changement iptype parent)"""
machinetype = kwargs['instance'] machinetype = kwargs['instance']
@ -1461,85 +1579,84 @@ def machine_post_save(sender, **kwargs):
@receiver(post_save, sender=Domain) @receiver(post_save, sender=Domain)
def domain_post_save(sender, **kwargs): def domain_post_save(**_kwargs):
"""Regeneration dns après modification d'un domain object""" """Regeneration dns après modification d'un domain object"""
regen('dns') regen('dns')
@receiver(post_delete, sender=Domain) @receiver(post_delete, sender=Domain)
def domain_post_delete(sender, **kwargs): def domain_post_delete(**_kwargs):
"""Regeneration dns après suppression d'un domain object""" """Regeneration dns après suppression d'un domain object"""
regen('dns') regen('dns')
@receiver(post_save, sender=Extension) @receiver(post_save, sender=Extension)
def extension_post_save(sender, **kwargs): def extension_post_save(**_kwargs):
"""Regeneration dns après modification d'une extension""" """Regeneration dns après modification d'une extension"""
regen('dns') regen('dns')
@receiver(post_delete, sender=Extension) @receiver(post_delete, sender=Extension)
def extension_post_selete(sender, **kwargs): def extension_post_selete(**_kwargs):
"""Regeneration dns après suppression d'une extension""" """Regeneration dns après suppression d'une extension"""
regen('dns') regen('dns')
@receiver(post_save, sender=SOA) @receiver(post_save, sender=SOA)
def soa_post_save(sender, **kwargs): def soa_post_save(**_kwargs):
"""Regeneration dns après modification d'un SOA""" """Regeneration dns après modification d'un SOA"""
regen('dns') regen('dns')
@receiver(post_delete, sender=SOA) @receiver(post_delete, sender=SOA)
def soa_post_delete(sender, **kwargs): def soa_post_delete(**_kwargs):
"""Regeneration dns après suppresson d'un SOA""" """Regeneration dns après suppresson d'un SOA"""
regen('dns') regen('dns')
@receiver(post_save, sender=Mx) @receiver(post_save, sender=Mx)
def mx_post_save(sender, **kwargs): def mx_post_save(**_kwargs):
"""Regeneration dns après modification d'un MX""" """Regeneration dns après modification d'un MX"""
regen('dns') regen('dns')
@receiver(post_delete, sender=Mx) @receiver(post_delete, sender=Mx)
def mx_post_delete(sender, **kwargs): def mx_post_delete(**_kwargs):
"""Regeneration dns après suppresson d'un MX""" """Regeneration dns après suppresson d'un MX"""
regen('dns') regen('dns')
@receiver(post_save, sender=Ns) @receiver(post_save, sender=Ns)
def ns_post_save(sender, **kwargs): def ns_post_save(**_kwargs):
"""Regeneration dns après modification d'un NS""" """Regeneration dns après modification d'un NS"""
regen('dns') regen('dns')
@receiver(post_delete, sender=Ns) @receiver(post_delete, sender=Ns)
def ns_post_delete(sender, **kwargs): def ns_post_delete(**_kwargs):
"""Regeneration dns après modification d'un NS""" """Regeneration dns après modification d'un NS"""
regen('dns') regen('dns')
@receiver(post_save, sender=Txt) @receiver(post_save, sender=Txt)
def text_post_save(sender, **kwargs): def text_post_save(**_kwargs):
"""Regeneration dns après modification d'un TXT""" """Regeneration dns après modification d'un TXT"""
regen('dns') regen('dns')
@receiver(post_delete, sender=Txt) @receiver(post_delete, sender=Txt)
def text_post_delete(sender, **kwargs): def text_post_delete(**_kwargs):
"""Regeneration dns après modification d'un TX""" """Regeneration dns après modification d'un TX"""
regen('dns') regen('dns')
@receiver(post_save, sender=Srv) @receiver(post_save, sender=Srv)
def srv_post_save(sender, **kwargs): def srv_post_save(**_kwargs):
"""Regeneration dns après modification d'un SRV""" """Regeneration dns après modification d'un SRV"""
regen('dns') regen('dns')
@receiver(post_delete, sender=Srv) @receiver(post_delete, sender=Srv)
def text_post_delete(sender, **kwargs): def srv_post_delete(**_kwargs):
"""Regeneration dns après modification d'un SRV""" """Regeneration dns après modification d'un SRV"""
regen('dns') regen('dns')

View file

@ -21,7 +21,11 @@
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#Augustin Lemesle # Augustin Lemesle
"""machines.serializers
Serializers for the Machines app
"""
from rest_framework import serializers from rest_framework import serializers
from machines.models import ( from machines.models import (
@ -29,28 +33,30 @@ from machines.models import (
IpType, IpType,
Extension, Extension,
IpList, IpList,
MachineType,
Domain, Domain,
Txt, Txt,
Mx, Mx,
Srv, Srv,
Service_link, Service_link,
Ns, Ns,
OuverturePortList,
OuverturePort, OuverturePort,
Ipv6List Ipv6List
) )
class IpTypeField(serializers.RelatedField): class IpTypeField(serializers.RelatedField):
"""Serialisation d'une iptype, renvoie son evaluation str""" """ Serializer for an IpType object field """
def to_representation(self, value): def to_representation(self, value):
return value.type return value.type
def to_internal_value(self, data):
pass
class IpListSerializer(serializers.ModelSerializer): class IpListSerializer(serializers.ModelSerializer):
"""Serialisation d'une iplist, ip_type etant une foreign_key, """ Serializer for an Ipv4List obejct using the IpType serialization """
on evalue sa methode str"""
ip_type = IpTypeField(read_only=True) ip_type = IpTypeField(read_only=True)
class Meta: class Meta:
@ -59,16 +65,19 @@ class IpListSerializer(serializers.ModelSerializer):
class Ipv6ListSerializer(serializers.ModelSerializer): class Ipv6ListSerializer(serializers.ModelSerializer):
""" Serializer for an Ipv6List object """
class Meta: class Meta:
model = Ipv6List model = Ipv6List
fields = ('ipv6', 'slaac_ip') fields = ('ipv6', 'slaac_ip')
class InterfaceSerializer(serializers.ModelSerializer): class InterfaceSerializer(serializers.ModelSerializer):
"""Serialisation d'une interface, ipv4, domain et extension sont """ Serializer for an Interface object. Use SerializerMethodField
des foreign_key, on les override et on les evalue avec des fonctions to get ForeignKey values """
get_..."""
ipv4 = IpListSerializer(read_only=True) ipv4 = IpListSerializer(read_only=True)
# TODO : use serializer.RelatedField to avoid duplicate code
mac_address = serializers.SerializerMethodField('get_macaddress') mac_address = serializers.SerializerMethodField('get_macaddress')
domain = serializers.SerializerMethodField('get_dns') domain = serializers.SerializerMethodField('get_dns')
extension = serializers.SerializerMethodField('get_interface_extension') extension = serializers.SerializerMethodField('get_interface_extension')
@ -77,20 +86,29 @@ class InterfaceSerializer(serializers.ModelSerializer):
model = Interface model = Interface
fields = ('ipv4', 'mac_address', 'domain', 'extension') fields = ('ipv4', 'mac_address', 'domain', 'extension')
def get_dns(self, obj): @staticmethod
def get_dns(obj):
""" The name of the associated DNS object """
return obj.domain.name return obj.domain.name
def get_interface_extension(self, obj): @staticmethod
def get_interface_extension(obj):
""" The name of the associated Interface object """
return obj.domain.extension.name return obj.domain.extension.name
def get_macaddress(self, obj): @staticmethod
def get_macaddress(obj):
""" The string representation of the associated MAC address """
return str(obj.mac_address) return str(obj.mac_address)
class FullInterfaceSerializer(serializers.ModelSerializer): class FullInterfaceSerializer(serializers.ModelSerializer):
"""Serialisation complete d'une interface avec les ipv6 en plus""" """ Serializer for an Interface obejct. Use SerializerMethodField
to get ForeignKey values """
ipv4 = IpListSerializer(read_only=True) ipv4 = IpListSerializer(read_only=True)
ipv6 = Ipv6ListSerializer(read_only=True, many=True) ipv6 = Ipv6ListSerializer(read_only=True, many=True)
# TODO : use serializer.RelatedField to avoid duplicate code
mac_address = serializers.SerializerMethodField('get_macaddress') mac_address = serializers.SerializerMethodField('get_macaddress')
domain = serializers.SerializerMethodField('get_dns') domain = serializers.SerializerMethodField('get_dns')
extension = serializers.SerializerMethodField('get_interface_extension') extension = serializers.SerializerMethodField('get_interface_extension')
@ -99,26 +117,36 @@ class FullInterfaceSerializer(serializers.ModelSerializer):
model = Interface model = Interface
fields = ('ipv4', 'ipv6', 'mac_address', 'domain', 'extension') fields = ('ipv4', 'ipv6', 'mac_address', 'domain', 'extension')
def get_dns(self, obj): @staticmethod
def get_dns(obj):
""" The name of the associated DNS object """
return obj.domain.name return obj.domain.name
def get_interface_extension(self, obj): @staticmethod
def get_interface_extension(obj):
""" The name of the associated Extension object """
return obj.domain.extension.name return obj.domain.extension.name
def get_macaddress(self, obj): @staticmethod
def get_macaddress(obj):
""" The string representation of the associated MAC address """
return str(obj.mac_address) return str(obj.mac_address)
class ExtensionNameField(serializers.RelatedField): class ExtensionNameField(serializers.RelatedField):
"""Evaluation str d'un objet extension (.example.org)""" """ Serializer for Extension object field """
def to_representation(self, value): def to_representation(self, value):
return value.name return value.name
def to_internal_value(self, data):
pass
class TypeSerializer(serializers.ModelSerializer): class TypeSerializer(serializers.ModelSerializer):
"""Serialisation d'un iptype : extension et la liste des """ Serializer for an IpType object. Use SerializerMethodField to
ouvertures de port son evalués en get_... etant des get ForeignKey values. Infos about the general port policy is added """
foreign_key ou des relations manytomany"""
extension = ExtensionNameField(read_only=True) extension = ExtensionNameField(read_only=True)
ouverture_ports_tcp_in = serializers\ ouverture_ports_tcp_in = serializers\
.SerializerMethodField('get_port_policy_input_tcp') .SerializerMethodField('get_port_policy_input_tcp')
@ -136,7 +164,10 @@ class TypeSerializer(serializers.ModelSerializer):
'ouverture_ports_tcp_in', 'ouverture_ports_tcp_out', 'ouverture_ports_tcp_in', 'ouverture_ports_tcp_out',
'ouverture_ports_udp_in', 'ouverture_ports_udp_out',) 'ouverture_ports_udp_in', 'ouverture_ports_udp_out',)
def get_port_policy(self, obj, protocole, io): @staticmethod
def get_port_policy(obj, protocole, io):
""" Generic utility function to get the policy for a given
port, protocole and IN or OUT """
if obj.ouverture_ports is None: if obj.ouverture_ports is None:
return [] return []
return map( return map(
@ -174,14 +205,20 @@ class ExtensionSerializer(serializers.ModelSerializer):
model = Extension model = Extension
fields = ('name', 'origin', 'origin_v6', 'zone_entry', 'soa') fields = ('name', 'origin', 'origin_v6', 'zone_entry', 'soa')
def get_origin_ip(self, obj): @staticmethod
return getattr(obj.origin, 'ipv4', None) def get_origin_ip(obj):
""" The IP of the associated origin for the zone """
return obj.origin.ipv4
def get_zone_name(self, obj): @staticmethod
def get_zone_name(obj):
""" The name of the associated zone """
return str(obj.dns_entry) return str(obj.dns_entry)
def get_soa_data(self, obj): @staticmethod
return { 'mail': obj.soa.dns_soa_mail, 'param': obj.soa.dns_soa_param } def get_soa_data(obj):
""" The representation of the associated SOA """
return {'mail': obj.soa.dns_soa_mail, 'param': obj.soa.dns_soa_param}
class MxSerializer(serializers.ModelSerializer): class MxSerializer(serializers.ModelSerializer):
@ -195,13 +232,19 @@ class MxSerializer(serializers.ModelSerializer):
model = Mx model = Mx
fields = ('zone', 'priority', 'name', 'mx_entry') fields = ('zone', 'priority', 'name', 'mx_entry')
def get_entry_name(self, obj): @staticmethod
def get_entry_name(obj):
""" The name of the DNS MX entry """
return str(obj.name) return str(obj.name)
def get_zone_name(self, obj): @staticmethod
def get_zone_name(obj):
""" The name of the associated zone of the MX record """
return obj.zone.name return obj.zone.name
def get_mx_name(self, obj): @staticmethod
def get_mx_name(obj):
""" The string representation of the entry to add to the DNS """
return str(obj.dns_entry) return str(obj.dns_entry)
@ -215,10 +258,14 @@ class TxtSerializer(serializers.ModelSerializer):
model = Txt model = Txt
fields = ('zone', 'txt_entry', 'field1', 'field2') fields = ('zone', 'txt_entry', 'field1', 'field2')
def get_zone_name(self, obj): @staticmethod
def get_zone_name(obj):
""" The name of the associated zone """
return str(obj.zone.name) return str(obj.zone.name)
def get_txt_name(self, obj): @staticmethod
def get_txt_name(obj):
""" The string representation of the entry to add to the DNS """
return str(obj.dns_entry) return str(obj.dns_entry)
@ -241,10 +288,14 @@ class SrvSerializer(serializers.ModelSerializer):
'srv_entry' 'srv_entry'
) )
def get_extension_name(self, obj): @staticmethod
def get_extension_name(obj):
""" The name of the associated extension """
return str(obj.extension.name) return str(obj.extension.name)
def get_srv_name(self, obj): @staticmethod
def get_srv_name(obj):
""" The string representation of the entry to add to the DNS """
return str(obj.dns_entry) return str(obj.dns_entry)
@ -259,13 +310,19 @@ class NsSerializer(serializers.ModelSerializer):
model = Ns model = Ns
fields = ('zone', 'ns', 'ns_entry') fields = ('zone', 'ns', 'ns_entry')
def get_zone_name(self, obj): @staticmethod
def get_zone_name(obj):
""" The name of the associated zone """
return obj.zone.name return obj.zone.name
def get_domain_name(self, obj): @staticmethod
def get_domain_name(obj):
""" The name of the associated NS target """
return str(obj.ns) return str(obj.ns)
def get_text_name(self, obj): @staticmethod
def get_text_name(obj):
""" The string representation of the entry to add to the DNS """
return str(obj.dns_entry) return str(obj.dns_entry)
@ -280,13 +337,19 @@ class DomainSerializer(serializers.ModelSerializer):
model = Domain model = Domain
fields = ('name', 'extension', 'cname', 'cname_entry') fields = ('name', 'extension', 'cname', 'cname_entry')
def get_zone_name(self, obj): @staticmethod
def get_zone_name(obj):
""" The name of the associated zone """
return obj.extension.name return obj.extension.name
def get_alias_name(self, obj): @staticmethod
def get_alias_name(obj):
""" The name of the associated alias """
return str(obj.cname) return str(obj.cname)
def get_cname_name(self, obj): @staticmethod
def get_cname_name(obj):
""" The name of the associated CNAME target """
return str(obj.dns_entry) return str(obj.dns_entry)
@ -300,13 +363,19 @@ class ServiceServersSerializer(serializers.ModelSerializer):
model = Service_link model = Service_link
fields = ('server', 'service', 'need_regen') fields = ('server', 'service', 'need_regen')
def get_server_name(self, obj): @staticmethod
def get_server_name(obj):
""" The name of the associated server """
return str(obj.server.domain.name) return str(obj.server.domain.name)
def get_service_name(self, obj): @staticmethod
def get_service_name(obj):
""" The name of the service name """
return str(obj.service) return str(obj.service)
def get_regen_status(self, obj): @staticmethod
def get_regen_status(obj):
""" The string representation of the regen status """
return obj.need_regen() return obj.need_regen()
@ -315,24 +384,38 @@ class OuverturePortsSerializer(serializers.Serializer):
ipv4 = serializers.SerializerMethodField() ipv4 = serializers.SerializerMethodField()
ipv6 = serializers.SerializerMethodField() ipv6 = serializers.SerializerMethodField()
def create(self, validated_data):
""" Creates a new object based on the un-serialized data.
Used to implement an abstract inherited method """
pass
def update(self, instance, validated_data):
""" Updates an object based on the un-serialized data.
Used to implement an abstract inherited method """
pass
@staticmethod
def get_ipv4(): def get_ipv4():
return {i.ipv4.ipv4: """ The representation of the policy for the IPv4 addresses """
{ return {
"tcp_in":[j.tcp_ports_in() for j in i.port_lists.all()], i.ipv4.ipv4: {
"tcp_out":[j.tcp_ports_out()for j in i.port_lists.all()], "tcp_in": [j.tcp_ports_in() for j in i.port_lists.all()],
"udp_in":[j.udp_ports_in() for j in i.port_lists.all()], "tcp_out": [j.tcp_ports_out()for j in i.port_lists.all()],
"udp_out":[j.udp_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 for i in Interface.objects.all() if i.ipv4
} }
@staticmethod
def get_ipv6(): def get_ipv6():
return {i.ipv6: """ The representation of the policy for the IPv6 addresses """
{ return {
"tcp_in":[j.tcp_ports_in() for j in i.port_lists.all()], i.ipv6: {
"tcp_out":[j.tcp_ports_out()for j in i.port_lists.all()], "tcp_in": [j.tcp_ports_in() for j in i.port_lists.all()],
"udp_in":[j.udp_ports_in() for j in i.port_lists.all()], "tcp_out": [j.tcp_ports_out()for j in i.port_lists.all()],
"udp_out":[j.udp_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 for i in Interface.objects.all() if i.ipv6
} }

View file

@ -20,7 +20,10 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""machines.tests
The tests for the API module.
"""
from django.test import TestCase # from django.test import TestCase
# Create your tests here. # Create your tests here.

View file

@ -20,6 +20,9 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""machines.urls
The defined URLs for the Cotisations app
"""
from __future__ import unicode_literals from __future__ import unicode_literals
@ -28,21 +31,39 @@ import re2o
from . import views from . import views
urlpatterns = [ urlpatterns = [
url(r'^new_machine/(?P<userid>[0-9]+)$', views.new_machine, name='new-machine'), url(r'^new_machine/(?P<userid>[0-9]+)$',
url(r'^edit_interface/(?P<interfaceid>[0-9]+)$', views.edit_interface, name='edit-interface'), views.new_machine,
url(r'^del_machine/(?P<machineid>[0-9]+)$', views.del_machine, name='del-machine'), name='new-machine'),
url(r'^new_interface/(?P<machineid>[0-9]+)$', views.new_interface, name='new-interface'), url(r'^edit_interface/(?P<interfaceid>[0-9]+)$',
url(r'^del_interface/(?P<interfaceid>[0-9]+)$', views.del_interface, name='del-interface'), views.edit_interface,
name='edit-interface'),
url(r'^del_machine/(?P<machineid>[0-9]+)$',
views.del_machine,
name='del-machine'),
url(r'^new_interface/(?P<machineid>[0-9]+)$',
views.new_interface,
name='new-interface'),
url(r'^del_interface/(?P<interfaceid>[0-9]+)$',
views.del_interface,
name='del-interface'),
url(r'^add_machinetype/$', views.add_machinetype, name='add-machinetype'), url(r'^add_machinetype/$', views.add_machinetype, name='add-machinetype'),
url(r'^edit_machinetype/(?P<machinetypeid>[0-9]+)$', views.edit_machinetype, name='edit-machinetype'), url(r'^edit_machinetype/(?P<machinetypeid>[0-9]+)$',
views.edit_machinetype,
name='edit-machinetype'),
url(r'^del_machinetype/$', views.del_machinetype, name='del-machinetype'), url(r'^del_machinetype/$', views.del_machinetype, name='del-machinetype'),
url(r'^index_machinetype/$', views.index_machinetype, name='index-machinetype'), url(r'^index_machinetype/$',
views.index_machinetype,
name='index-machinetype'),
url(r'^add_iptype/$', views.add_iptype, name='add-iptype'), url(r'^add_iptype/$', views.add_iptype, name='add-iptype'),
url(r'^edit_iptype/(?P<iptypeid>[0-9]+)$', views.edit_iptype, name='edit-iptype'), url(r'^edit_iptype/(?P<iptypeid>[0-9]+)$',
views.edit_iptype,
name='edit-iptype'),
url(r'^del_iptype/$', views.del_iptype, name='del-iptype'), url(r'^del_iptype/$', views.del_iptype, name='del-iptype'),
url(r'^index_iptype/$', views.index_iptype, name='index-iptype'), url(r'^index_iptype/$', views.index_iptype, name='index-iptype'),
url(r'^add_extension/$', views.add_extension, name='add-extension'), url(r'^add_extension/$', views.add_extension, name='add-extension'),
url(r'^edit_extension/(?P<extensionid>[0-9]+)$', views.edit_extension, name='edit-extension'), url(r'^edit_extension/(?P<extensionid>[0-9]+)$',
views.edit_extension,
name='edit-extension'),
url(r'^del_extension/$', views.del_extension, name='del-extension'), url(r'^del_extension/$', views.del_extension, name='del-extension'),
url(r'^add_soa/$', views.add_soa, name='add-soa'), url(r'^add_soa/$', views.add_soa, name='add-soa'),
url(r'^edit_soa/(?P<soaid>[0-9]+)$', views.edit_soa, name='edit-soa'), url(r'^edit_soa/(?P<soaid>[0-9]+)$', views.edit_soa, name='edit-soa'),
@ -60,16 +81,34 @@ urlpatterns = [
url(r'^edit_srv/(?P<srvid>[0-9]+)$', views.edit_srv, name='edit-srv'), url(r'^edit_srv/(?P<srvid>[0-9]+)$', views.edit_srv, name='edit-srv'),
url(r'^del_srv/$', views.del_srv, name='del-srv'), url(r'^del_srv/$', views.del_srv, name='del-srv'),
url(r'^index_extension/$', views.index_extension, name='index-extension'), 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'^add_alias/(?P<interfaceid>[0-9]+)$',
url(r'^edit_alias/(?P<domainid>[0-9]+)$', views.edit_alias, name='edit-alias'), views.add_alias,
url(r'^del_alias/(?P<interfaceid>[0-9]+)$', views.del_alias, name='del-alias'), name='add-alias'),
url(r'^index_alias/(?P<interfaceid>[0-9]+)$', views.index_alias, name='index-alias'), url(r'^edit_alias/(?P<domainid>[0-9]+)$',
url(r'^new_ipv6list/(?P<interfaceid>[0-9]+)$', views.new_ipv6list, name='new-ipv6list'), views.edit_alias,
url(r'^edit_ipv6list/(?P<ipv6listid>[0-9]+)$', views.edit_ipv6list, name='edit-ipv6list'), name='edit-alias'),
url(r'^del_ipv6list/(?P<ipv6listid>[0-9]+)$', views.del_ipv6list, name='del-ipv6list'), url(r'^del_alias/(?P<interfaceid>[0-9]+)$',
url(r'^index_ipv6/(?P<interfaceid>[0-9]+)$', views.index_ipv6, name='index-ipv6'), 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'^add_service/$', views.add_service, name='add-service'),
url(r'^edit_service/(?P<serviceid>[0-9]+)$', views.edit_service, name='edit-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'), url(r'^del_service/$', views.del_service, name='del-service'),
url(r'^index_service/$', views.index_service, name='index-service'), url(r'^index_service/$', views.index_service, name='index-service'),
url(r'^add_vlan/$', views.add_vlan, name='add-vlan'), url(r'^add_vlan/$', views.add_vlan, name='add-vlan'),
@ -80,15 +119,15 @@ urlpatterns = [
url(r'^edit_nas/(?P<nasid>[0-9]+)$', views.edit_nas, name='edit-nas'), 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'^del_nas/$', views.del_nas, name='del-nas'),
url(r'^index_nas/$', views.index_nas, name='index-nas'), url(r'^index_nas/$', views.index_nas, name='index-nas'),
url( url(r'history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
r'history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
re2o.views.history, re2o.views.history,
name='history', name='history',
kwargs={'application':'machines'}, kwargs={'application': 'machines'}),
),
url(r'^$', views.index, name='index'), url(r'^$', views.index, name='index'),
url(r'^rest/mac-ip/$', views.mac_ip, name='mac-ip'), url(r'^rest/mac-ip/$', views.mac_ip, name='mac-ip'),
url(r'^rest/regen-achieved/$', views.regen_achieved, name='regen-achieved'), url(r'^rest/regen-achieved/$',
views.regen_achieved,
name='regen-achieved'),
url(r'^rest/mac-ip-dns/$', views.mac_ip_dns, name='mac-ip-dns'), url(r'^rest/mac-ip-dns/$', views.mac_ip_dns, name='mac-ip-dns'),
url(r'^rest/alias/$', views.alias, name='alias'), url(r'^rest/alias/$', views.alias, name='alias'),
url(r'^rest/corresp/$', views.corresp, name='corresp'), url(r'^rest/corresp/$', views.corresp, name='corresp'),
@ -97,12 +136,21 @@ urlpatterns = [
url(r'^rest/txt/$', views.txt, name='txt'), url(r'^rest/txt/$', views.txt, name='txt'),
url(r'^rest/srv/$', views.srv, name='srv'), url(r'^rest/srv/$', views.srv, name='srv'),
url(r'^rest/zones/$', views.zones, name='zones'), url(r'^rest/zones/$', views.zones, name='zones'),
url(r'^rest/service_servers/$', views.service_servers, name='service-servers'), url(r'^rest/service_servers/$',
url(r'^rest/ouverture_ports/$', views.ouverture_ports, name='ouverture-ports'), views.service_servers,
name='service-servers'),
url(r'^rest/ouverture_ports/$',
views.ouverture_ports,
name='ouverture-ports'),
url(r'index_portlist/$', views.index_portlist, name='index-portlist'), url(r'index_portlist/$', views.index_portlist, name='index-portlist'),
url(r'^edit_portlist/(?P<ouvertureportlistid>[0-9]+)$', views.edit_portlist, name='edit-portlist'), url(r'^edit_portlist/(?P<ouvertureportlistid>[0-9]+)$',
url(r'^del_portlist/(?P<ouvertureportlistid>[0-9]+)$', views.del_portlist, name='del-portlist'), 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'^add_portlist/$', views.add_portlist, name='add-portlist'),
url(r'^port_config/(?P<interfaceid>[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

@ -1,2 +1,28 @@
# -*- 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
# 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.
"""preferences
The app in charge of storing all the preferences for the local installation
"""
from .acl import * from .acl import *

View file

@ -26,6 +26,7 @@
Here are defined some functions to check acl on the application. Here are defined some functions to check acl on the application.
""" """
def can_view(user): def can_view(user):
"""Check if an user can view the application. """Check if an user can view the application.

View file

@ -1,3 +1,34 @@
# 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
# 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.
# App de gestion des machines pour re2o
# Gabriel Détraz, Augustin Lemesle
# Gplv2
"""preferences.aes_field
Module defining a AESEncryptedField object that can be used in forms
to handle the use of properly encrypting and decrypting AES keys
"""
import string import string
import binascii import binascii
from random import choice from random import choice
@ -10,10 +41,13 @@ EOD = '`%EofD%`' # This should be something that will not occur in strings
def genstring(length=16, chars=string.printable): def genstring(length=16, chars=string.printable):
""" Generate a random string of length `length` and composed of
the characters in `chars` """
return ''.join([choice(chars) for i in range(length)]) return ''.join([choice(chars) for i in range(length)])
def encrypt(key, s): def encrypt(key, s):
""" AES Encrypt a secret `s` with the key `key` """
obj = AES.new(key) obj = AES.new(key)
datalength = len(s) + len(EOD) datalength = len(s) + len(EOD)
if datalength < 16: if datalength < 16:
@ -25,12 +59,15 @@ def encrypt(key, s):
def decrypt(key, s): def decrypt(key, s):
""" AES Decrypt a secret `s` with the key `key` """
obj = AES.new(key) obj = AES.new(key)
ss = obj.decrypt(s) ss = obj.decrypt(s)
return ss.split(bytes(EOD, 'utf-8'))[0] return ss.split(bytes(EOD, 'utf-8'))[0]
class AESEncryptedField(models.CharField): class AESEncryptedField(models.CharField):
""" A Field that can be used in forms for adding the support
of AES ecnrypted fields """
def save_form_data(self, instance, data): def save_form_data(self, instance, data):
setattr(instance, self.name, setattr(instance, self.name,
binascii.b2a_base64(encrypt(settings.AES_KEY, data))) binascii.b2a_base64(encrypt(settings.AES_KEY, data)))
@ -41,16 +78,10 @@ class AESEncryptedField(models.CharField):
return decrypt(settings.AES_KEY, return decrypt(settings.AES_KEY,
binascii.a2b_base64(value)).decode('utf-8') 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): def get_prep_value(self, value):
if value is None: if value is None:
return value return value
return binascii.b2a_base64(encrypt( return binascii.b2a_base64(encrypt(
settings.AES_KEY, settings.AES_KEY,
value value
)) ))

View file

@ -1,5 +0,0 @@
from django.apps import AppConfig
class PreferencesConfig(AppConfig):
name = 'preferences'

View file

@ -44,12 +44,16 @@ class EditOptionalUserForm(ModelForm):
prefix=prefix, prefix=prefix,
**kwargs **kwargs
) )
self.fields['is_tel_mandatory'].label = 'Exiger un numéro de\ self.fields['is_tel_mandatory'].label = (
téléphone' 'Exiger un numéro de téléphone'
self.fields['user_solde'].label = 'Activation du solde pour\ )
les utilisateurs' self.fields['user_solde'].label = (
'Activation du solde pour les utilisateurs'
)
self.fields['max_solde'].label = 'Solde maximum' self.fields['max_solde'].label = 'Solde maximum'
self.fields['min_online_payment'].label = 'Montant de rechargement minimum en ligne' self.fields['min_online_payment'].label = (
'Montant de rechargement minimum en ligne'
)
self.fields['self_adhesion'].label = 'Auto inscription' self.fields['self_adhesion'].label = 'Auto inscription'
@ -162,7 +166,6 @@ class EditAssoOptionForm(ModelForm):
return cleaned_data return cleaned_data
class EditMailMessageOptionForm(ModelForm): class EditMailMessageOptionForm(ModelForm):
"""Formulaire d'edition des messages de bienvenue personnalisés""" """Formulaire d'edition des messages de bienvenue personnalisés"""
class Meta: class Meta:

View file

@ -27,26 +27,33 @@ from __future__ import unicode_literals
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.db import models from django.db import models
import cotisations.models from django.db.models.signals import post_save
import machines.models
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver from django.dispatch import receiver
from django.core.cache import cache from django.core.cache import cache
from .aes_field import AESEncryptedField import cotisations.models
import machines.models
from re2o.mixins import AclMixin from re2o.mixins import AclMixin
from .aes_field import AESEncryptedField
class PreferencesModel(models.Model): class PreferencesModel(models.Model):
""" Base object for the Preferences objects
Defines methods to handle the cache of the settings (they should
not change a lot) """
@classmethod @classmethod
def set_in_cache(cls): def set_in_cache(cls):
""" Save the preferences in a server-side cache """
instance, _created = cls.objects.get_or_create() instance, _created = cls.objects.get_or_create()
cache.set(cls().__class__.__name__.lower(), instance, None) cache.set(cls().__class__.__name__.lower(), instance, None)
return instance return instance
@classmethod @classmethod
def get_cached_value(cls, key): def get_cached_value(cls, key):
""" Get the preferences from the server-side cache """
instance = cache.get(cls().__class__.__name__.lower()) instance = cache.get(cls().__class__.__name__.lower())
if instance == None: if instance is None:
instance = cls.set_in_cache() instance = cls.set_in_cache()
return getattr(instance, key) return getattr(instance, key)
@ -111,7 +118,7 @@ class OptionalUser(AclMixin, PreferencesModel):
@receiver(post_save, sender=OptionalUser) @receiver(post_save, sender=OptionalUser)
def optionaluser_post_save(sender, **kwargs): def optionaluser_post_save(**kwargs):
"""Ecriture dans le cache""" """Ecriture dans le cache"""
user_pref = kwargs['instance'] user_pref = kwargs['instance']
user_pref.set_in_cache() user_pref.set_in_cache()
@ -146,7 +153,8 @@ class OptionalMachine(AclMixin, PreferencesModel):
@cached_property @cached_property
def ipv6(self): def ipv6(self):
return not self.get_cached_value('ipv6_mode') == 'DISABLED' """ Check if the IPv6 option is activated """
return not self.get_cached_value('ipv6_mode') == 'DISABLED'
class Meta: class Meta:
permissions = ( permissions = (
@ -155,7 +163,7 @@ class OptionalMachine(AclMixin, PreferencesModel):
@receiver(post_save, sender=OptionalMachine) @receiver(post_save, sender=OptionalMachine)
def optionalmachine_post_save(sender, **kwargs): def optionalmachine_post_save(**kwargs):
"""Synchronisation ipv6 et ecriture dans le cache""" """Synchronisation ipv6 et ecriture dans le cache"""
machine_pref = kwargs['instance'] machine_pref = kwargs['instance']
machine_pref.set_in_cache() machine_pref.set_in_cache()
@ -203,7 +211,7 @@ class OptionalTopologie(AclMixin, PreferencesModel):
@receiver(post_save, sender=OptionalTopologie) @receiver(post_save, sender=OptionalTopologie)
def optionaltopologie_post_save(sender, **kwargs): def optionaltopologie_post_save(**kwargs):
"""Ecriture dans le cache""" """Ecriture dans le cache"""
topologie_pref = kwargs['instance'] topologie_pref = kwargs['instance']
topologie_pref.set_in_cache() topologie_pref.set_in_cache()
@ -230,7 +238,7 @@ class GeneralOption(AclMixin, PreferencesModel):
blank=True, blank=True,
) )
GTU = models.FileField( GTU = models.FileField(
upload_to = '', upload_to='',
default="", default="",
null=True, null=True,
blank=True, blank=True,
@ -243,7 +251,7 @@ class GeneralOption(AclMixin, PreferencesModel):
@receiver(post_save, sender=GeneralOption) @receiver(post_save, sender=GeneralOption)
def generaloption_post_save(sender, **kwargs): def generaloption_post_save(**kwargs):
"""Ecriture dans le cache""" """Ecriture dans le cache"""
general_pref = kwargs['instance'] general_pref = kwargs['instance']
general_pref.set_in_cache() general_pref.set_in_cache()
@ -317,7 +325,7 @@ class AssoOption(AclMixin, PreferencesModel):
@receiver(post_save, sender=AssoOption) @receiver(post_save, sender=AssoOption)
def assooption_post_save(sender, **kwargs): def assooption_post_save(**kwargs):
"""Ecriture dans le cache""" """Ecriture dans le cache"""
asso_pref = kwargs['instance'] asso_pref = kwargs['instance']
asso_pref.set_in_cache() asso_pref.set_in_cache()

View file

@ -33,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endif %} {% endif %}
<form class="form" method="post"> <form class="form" method="post" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
{% if preferenceform %} {% if preferenceform %}
{% bootstrap_form preferenceform %} {% bootstrap_form preferenceform %}

View file

@ -1,3 +1,29 @@
from django.test import TestCase # 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
# 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.
"""preferences.tests
The tests for the Preferences module.
"""
# from django.test import TestCase
# Create your tests here. # Create your tests here.

View file

@ -27,8 +27,8 @@ from __future__ import unicode_literals
from django.conf.urls import url from django.conf.urls import url
from . import views
import re2o import re2o
from . import views
urlpatterns = [ urlpatterns = [
@ -73,7 +73,7 @@ urlpatterns = [
r'^history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$', r'^history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
re2o.views.history, re2o.views.history,
name='history', name='history',
kwargs={'application':'preferences'}, kwargs={'application': 'preferences'},
), ),
url(r'^$', views.display_options, name='display-options'), url(r'^$', views.display_options, name='display-options'),
] ]

View file

@ -31,17 +31,17 @@ topologie, users, service...)
from __future__ import unicode_literals from __future__ import unicode_literals
from django.urls import reverse from django.urls import reverse
from django.shortcuts import render, redirect from django.shortcuts import redirect
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required
from django.db.models import ProtectedError from django.db.models import ProtectedError
from django.db import transaction from django.db import transaction
from reversion.models import Version
from reversion import revisions as reversion from reversion import revisions as reversion
from re2o.views import form from re2o.views import form
from re2o.acl import can_create, can_edit, can_delete_set, can_view_all from re2o.acl import can_create, can_edit, can_delete_set, can_view_all
from .forms import ServiceForm, DelServiceForm from .forms import ServiceForm, DelServiceForm
from .models import Service, OptionalUser, OptionalMachine, AssoOption from .models import Service, OptionalUser, OptionalMachine, AssoOption
from .models import MailMessageOption, GeneralOption, OptionalTopologie from .models import MailMessageOption, GeneralOption, OptionalTopologie
@ -119,7 +119,7 @@ def edit_options(request, section):
@can_create(Service) @can_create(Service)
def add_service(request): def add_service(request):
"""Ajout d'un service de la page d'accueil""" """Ajout d'un service de la page d'accueil"""
service = ServiceForm(request.POST or None) service = ServiceForm(request.POST or None, request.FILES or None)
if service.is_valid(): if service.is_valid():
with transaction.atomic(), reversion.create_revision(): with transaction.atomic(), reversion.create_revision():
service.save() service.save()
@ -128,7 +128,7 @@ def add_service(request):
messages.success(request, "Ce service a été ajouté") messages.success(request, "Ce service a été ajouté")
return redirect(reverse('preferences:display-options')) return redirect(reverse('preferences:display-options'))
return form( return form(
{'preferenceform': service, 'action_name' : 'Ajouter'}, {'preferenceform': service, 'action_name': 'Ajouter'},
'preferences/preferences.html', 'preferences/preferences.html',
request request
) )
@ -136,9 +136,9 @@ def add_service(request):
@login_required @login_required
@can_edit(Service) @can_edit(Service)
def edit_service(request, service_instance, serviceid): def edit_service(request, service_instance, **_kwargs):
"""Edition des services affichés sur la page d'accueil""" """Edition des services affichés sur la page d'accueil"""
service = ServiceForm(request.POST or None, instance=service_instance) service = ServiceForm(request.POST or None, request.FILES or None,instance=service_instance)
if service.is_valid(): if service.is_valid():
with transaction.atomic(), reversion.create_revision(): with transaction.atomic(), reversion.create_revision():
service.save() service.save()
@ -151,7 +151,7 @@ def edit_service(request, service_instance, serviceid):
messages.success(request, "Service modifié") messages.success(request, "Service modifié")
return redirect(reverse('preferences:display-options')) return redirect(reverse('preferences:display-options'))
return form( return form(
{'preferenceform': service, 'action_name' : 'Editer'}, {'preferenceform': service, 'action_name': 'Editer'},
'preferences/preferences.html', 'preferences/preferences.html',
request request
) )
@ -175,7 +175,7 @@ def del_services(request, instances):
suivant %s ne peut être supprimé" % services_del) suivant %s ne peut être supprimé" % services_del)
return redirect(reverse('preferences:display-options')) return redirect(reverse('preferences:display-options'))
return form( return form(
{'preferenceform': services, 'action_name' : 'Supprimer'}, {'preferenceform': services, 'action_name': 'Supprimer'},
'preferences/preferences.html', 'preferences/preferences.html',
request request
) )

View file

@ -20,4 +20,15 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""re2o
The main app of Re2o. In charge of all the basics elements which are not
specific to anyother apps. It includes :
* Templates used in multiple places
* Templatetags used in multiple places
* ACL base
* Mixins base
* Settings for the Django project
* The login part
* Some utility scripts
* ...
"""

View file

@ -33,14 +33,6 @@ from django.contrib import messages
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
import cotisations
import logs
import machines
import preferences
import search
import topologie
import users
def can_create(model): def can_create(model):
"""Decorator to check if an user can create a model. """Decorator to check if an user can create a model.
@ -49,7 +41,11 @@ def can_create(model):
of models. of models.
""" """
def decorator(view): def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
can, msg = model.can_create(request.user, *args, **kwargs) can, msg = model.can_create(request.user, *args, **kwargs)
if not can: if not can:
messages.error( messages.error(
@ -68,31 +64,37 @@ def can_edit(model, *field_list):
kind of models. kind of models.
""" """
def decorator(view): def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
try: try:
instance = model.get_instance(*args, **kwargs) instance = model.get_instance(*args, **kwargs)
except model.DoesNotExist: except model.DoesNotExist:
messages.error(request, u"Entrée inexistante") messages.error(request, u"Entrée inexistante")
return redirect(reverse('users:profil', return redirect(reverse(
kwargs={'userid': str(request.user.id)} 'users:profil',
)) kwargs={'userid': str(request.user.id)}
))
can, msg = instance.can_edit(request.user) can, msg = instance.can_edit(request.user)
if not can: if not can:
messages.error( messages.error(
request, msg or "Vous ne pouvez pas accéder à ce menu") request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil', return redirect(reverse(
kwargs={'userid': str(request.user.id)} 'users:profil',
)) kwargs={'userid': str(request.user.id)}
))
for field in field_list: for field in field_list:
can_change = getattr(instance, 'can_change_' + field) can_change_fct = getattr(instance, 'can_change_' + field)
can, msg = can_change(request.user, *args, **kwargs) can, msg = can_change_fct(request.user, *args, **kwargs)
if not can: if not can:
messages.error( messages.error(
request, msg or "Vous ne pouvez pas accéder à ce menu") request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil', return redirect(reverse(
kwargs={'userid': str( 'users:profil',
request.user.id)} kwargs={'userid': str(request.user.id)}
)) ))
return view(request, instance, *args, **kwargs) return view(request, instance, *args, **kwargs)
return wrapper return wrapper
return decorator return decorator
@ -103,17 +105,21 @@ def can_change(model, *field_list):
Difference with can_edit : take a class and not an instance Difference with can_edit : take a class and not an instance
""" """
def decorator(view): def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
for field in field_list: for field in field_list:
can_change = getattr(model, 'can_change_' + field) can_change_fct = getattr(model, 'can_change_' + field)
can, msg = can_change(request.user, *args, **kwargs) can, msg = can_change_fct(request.user, *args, **kwargs)
if not can: if not can:
messages.error( messages.error(
request, msg or "Vous ne pouvez pas accéder à ce menu") request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil', return redirect(reverse(
kwargs={'userid': str( 'users:profil',
request.user.id)} kwargs={'userid': str(request.user.id)}
)) ))
return view(request, *args, **kwargs) return view(request, *args, **kwargs)
return wrapper return wrapper
return decorator return decorator
@ -127,21 +133,27 @@ def can_delete(model):
kind of models. kind of models.
""" """
def decorator(view): def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
try: try:
instance = model.get_instance(*args, **kwargs) instance = model.get_instance(*args, **kwargs)
except model.DoesNotExist: except model.DoesNotExist:
messages.error(request, u"Entrée inexistante") messages.error(request, u"Entrée inexistante")
return redirect(reverse('users:profil', return redirect(reverse(
kwargs={'userid': str(request.user.id)} 'users:profil',
)) kwargs={'userid': str(request.user.id)}
))
can, msg = instance.can_delete(request.user) can, msg = instance.can_delete(request.user)
if not can: if not can:
messages.error( messages.error(
request, msg or "Vous ne pouvez pas accéder à ce menu") request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil', return redirect(reverse(
kwargs={'userid': str(request.user.id)} 'users:profil',
)) kwargs={'userid': str(request.user.id)}
))
return view(request, instance, *args, **kwargs) return view(request, instance, *args, **kwargs)
return wrapper return wrapper
return decorator return decorator
@ -151,19 +163,25 @@ def can_delete_set(model):
"""Decorator which returns a list of detable models by request user. """Decorator which returns a list of detable models by request user.
If none of them, return an error""" If none of them, return an error"""
def decorator(view): def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
all_objects = model.objects.all() all_objects = model.objects.all()
instances_id = [] instances_id = []
for instance in all_objects: for instance in all_objects:
can, msg = instance.can_delete(request.user) can, _msg = instance.can_delete(request.user)
if can: if can:
instances_id.append(instance.id) instances_id.append(instance.id)
instances = model.objects.filter(id__in=instances_id) instances = model.objects.filter(id__in=instances_id)
if not instances: if not instances:
messages.error(request, "Vous ne pouvez pas accéder à ce menu") messages.error(
return redirect(reverse('users:profil', request, "Vous ne pouvez pas accéder à ce menu")
kwargs={'userid': str(request.user.id)} return redirect(reverse(
)) 'users:profil',
kwargs={'userid': str(request.user.id)}
))
return view(request, instances, *args, **kwargs) return view(request, instances, *args, **kwargs)
return wrapper return wrapper
return decorator return decorator
@ -177,21 +195,27 @@ def can_view(model):
kind of models. kind of models.
""" """
def decorator(view): def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
try: try:
instance = model.get_instance(*args, **kwargs) instance = model.get_instance(*args, **kwargs)
except model.DoesNotExist: except model.DoesNotExist:
messages.error(request, u"Entrée inexistante") messages.error(request, u"Entrée inexistante")
return redirect(reverse('users:profil', return redirect(reverse(
kwargs={'userid': str(request.user.id)} 'users:profil',
)) kwargs={'userid': str(request.user.id)}
))
can, msg = instance.can_view(request.user) can, msg = instance.can_view(request.user)
if not can: if not can:
messages.error( messages.error(
request, msg or "Vous ne pouvez pas accéder à ce menu") request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil', return redirect(reverse(
kwargs={'userid': str(request.user.id)} 'users:profil',
)) kwargs={'userid': str(request.user.id)}
))
return view(request, instance, *args, **kwargs) return view(request, instance, *args, **kwargs)
return wrapper return wrapper
return decorator return decorator
@ -201,14 +225,19 @@ def can_view_all(model):
"""Decorator to check if an user can view a class of model. """Decorator to check if an user can view a class of model.
""" """
def decorator(view): def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
can, msg = model.can_view_all(request.user) can, msg = model.can_view_all(request.user)
if not can: if not can:
messages.error( messages.error(
request, msg or "Vous ne pouvez pas accéder à ce menu") request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil', return redirect(reverse(
kwargs={'userid': str(request.user.id)} 'users:profil',
)) kwargs={'userid': str(request.user.id)}
))
return view(request, *args, **kwargs) return view(request, *args, **kwargs)
return wrapper return wrapper
return decorator return decorator
@ -220,15 +249,20 @@ def can_view_app(app_name):
assert app_name in sys.modules.keys() assert app_name in sys.modules.keys()
def decorator(view): def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
app = sys.modules[app_name] app = sys.modules[app_name]
can, msg = app.can_view(request.user) can, msg = app.can_view(request.user)
if can: if can:
return view(request, *args, **kwargs) return view(request, *args, **kwargs)
messages.error(request, msg) messages.error(request, msg)
return redirect(reverse('users:profil', return redirect(reverse(
kwargs={'userid': str(request.user.id)} 'users:profil',
)) kwargs={'userid': str(request.user.id)}
))
return wrapper return wrapper
return decorator return decorator
@ -236,13 +270,16 @@ def can_view_app(app_name):
def can_edit_history(view): def can_edit_history(view):
"""Decorator to check if an user can edit history.""" """Decorator to check if an user can edit history."""
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
if request.user.has_perm('admin.change_logentry'): if request.user.has_perm('admin.change_logentry'):
return view(request, *args, **kwargs) return view(request, *args, **kwargs)
messages.error( messages.error(
request, request,
"Vous ne pouvez pas éditer l'historique." "Vous ne pouvez pas éditer l'historique."
) )
return redirect(reverse('users:profil', return redirect(reverse(
kwargs={'userid': str(request.user.id)} 'users:profil',
)) kwargs={'userid': str(request.user.id)}
))
return wrapper return wrapper

View file

@ -1,3 +1,30 @@
#!/usr/bin/env python3 """re2o.contributors
A list of the proud contributors to Re2o
"""
CONTRIBUTORS = ['Gabriel "Chirac" Détraz', 'Maël "MoaMoaK" Kervella', 'Hugo "Klafyvel" Levy--Falk', 'Augustin "Dahlaro" Lemesle', 'Goulven "Lhark" Kermarec', 'Guillaume "Guimoz" Goessel', 'Yoann "Nanoy" Pietri', 'Matthieu "Lebanni" Michelet', 'Arthur "Grizzly" Grisel-Davy', 'Simon "Rezatoune" Brélivet', 'Sellem Lev-Arcady', 'David "5-1" Sinquin', 'Pierre "Redstorm" Cadart', 'Éloi "Goslig" Alain', 'Laouen "Volgarr" Fernet', 'Joanne Steiner', '"Krokmou"', 'Thibault "Tipunchetrhum" de Boutray', 'Baptiste "B" Fournier', 'Daniel "Dstan" Stan', 'Hugo "Shaka" Hervieux', '"Mikachu"', 'Thomas "Nymous" Gaudin', '"Esum"'] CONTRIBUTORS = [
'Gabriel "Chirac" Détraz',
'Maël "MoaMoaK" Kervella',
'Hugo "Klafyvel" Levy--Falk',
'Augustin "Dahlaro" Lemesle',
'Goulven "Lhark" Kermarec',
'Guillaume "Guimoz" Goessel',
'Yoann "Nanoy" Pietri',
'Matthieu "Lebanni" Michelet',
'Arthur "Grizzly" Grisel-Davy',
'Simon "Rezatoune" Brélivet',
'Sellem Lev-Arcady',
'David "5-1" Sinquin',
'Pierre "Redstorm" Cadart',
'Éloi "Goslig" Alain',
'Laouen "Volgarr" Fernet',
'Joanne Steiner',
'"Krokmou"',
'Thibault "Tipunchetrhum" de Boutray',
'Baptiste "B" Fournier',
'Daniel "Dstan" Stan',
'Hugo "Shaka" Hervieux',
'"Mikachu"',
'Thomas "Nymous" Gaudin',
'"Esum"'
]

View file

@ -1,15 +1,45 @@
from django.db import models # -*- mode: python; coding: utf-8 -*-
from django import forms # Re2o est un logiciel d'administration développé initiallement au rezometz. Il
from functools import partial # 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
# 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.
"""re2o.field_permissions
A model mixin and a field mixin used to remove some unauthorized fields
from the form automatically generated from the model. The model must
subclass `FieldPermissionModelMixin` and the form must subclass
`FieldPermissionFieldMixin` so when a Django form is generated from the
fields of the models, some fields will be removed if the user don't have
the rights to change them (can_change_{name})
"""
class FieldPermissionModelMixin: class FieldPermissionModelMixin:
""" The model mixin. Defines the `has_field_perm` function """
field_permissions = {} # {'field_name': callable} field_permissions = {} # {'field_name': callable}
FIELD_PERM_CODENAME = 'can_change_{model}_{name}' FIELD_PERM_CODENAME = 'can_change_{model}_{name}'
FIELD_PERMISSION_GETTER = 'can_change_{name}' FIELD_PERMISSION_GETTER = 'can_change_{name}'
FIELD_PERMISSION_MISSING_DEFAULT = True FIELD_PERMISSION_MISSING_DEFAULT = True
def has_field_perm(self, user, field): def has_field_perm(self, user, field):
""" Checks if a `user` has the right to edit the `field`
of this model """
if field in self.field_permissions: if field in self.field_permissions:
checks = self.field_permissions[field] checks = self.field_permissions[field]
if not isinstance(checks, (list, tuple)): if not isinstance(checks, (list, tuple)):
@ -39,21 +69,18 @@ class FieldPermissionModelMixin:
# Try to find a user setting that qualifies them for permission. # Try to find a user setting that qualifies them for permission.
for perm in checks: for perm in checks:
if callable(perm): if callable(perm):
result, reason = perm(user_request=user) result, _reason = perm(user_request=user)
if result is not None: if result is not None:
return result return result
else: else:
result = user.has_perm(perm) # Don't supply 'obj', or else infinite recursion. # Don't supply 'obj', or else infinite recursion.
result = user.has_perm(perm)
if result: if result:
return True return True
# If no requirement can be met, then permission is denied. # If no requirement can be met, then permission is denied.
return False return False
class FieldPermissionModel(FieldPermissionModelMixin, models.Model):
class Meta:
abstract = True
class FieldPermissionFormMixin: class FieldPermissionFormMixin:
""" """
@ -71,9 +98,5 @@ class FieldPermissionFormMixin:
self.remove_unauthorized_field(name) self.remove_unauthorized_field(name)
def remove_unauthorized_field(self, name): def remove_unauthorized_field(self, name):
""" Remove one field from the fields of the form """
del self.fields[name] del self.fields[name]
class FieldPermissionForm(FieldPermissionFormMixin, forms.ModelForm):
pass

View file

@ -24,7 +24,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Module d'authentification # Module d'authentification
# David Sinquin, Gabriel Détraz, Goulven Kermarec # David Sinquin, Gabriel Détraz, Goulven Kermarec
"""re2o.login
Module in charge of handling the login process and verifications
"""
import hashlib import hashlib
import binascii import binascii
@ -42,6 +44,7 @@ DIGEST_LEN = 20
def makeSecret(password): def makeSecret(password):
""" Build a hashed and salted version of the password """
salt = os.urandom(4) salt = os.urandom(4)
h = hashlib.sha1(password.encode()) h = hashlib.sha1(password.encode())
h.update(salt) h.update(salt)
@ -49,11 +52,13 @@ def makeSecret(password):
def hashNT(password): def hashNT(password):
hash = hashlib.new('md4', password.encode('utf-16le')).digest() """ Build a md4 hash of the password to use as the NT-password """
return binascii.hexlify(hash).upper() hash_str = hashlib.new('md4', password.encode('utf-16le')).digest()
return binascii.hexlify(hash_str).upper()
def checkPassword(challenge_password, password): def checkPassword(challenge_password, password):
""" Check if a given password match the hash of a stored password """
challenge_bytes = decodestring(challenge_password[ALGO_LEN:].encode()) challenge_bytes = decodestring(challenge_password[ALGO_LEN:].encode())
digest = challenge_bytes[:DIGEST_LEN] digest = challenge_bytes[:DIGEST_LEN]
salt = challenge_bytes[DIGEST_LEN:] salt = challenge_bytes[DIGEST_LEN:]
@ -74,7 +79,7 @@ class SSHAPasswordHasher(hashers.BasePasswordHasher):
algorithm = ALGO_NAME algorithm = ALGO_NAME
def encode(self, password, salt, iterations=None): def encode(self, password, salt):
""" """
Hash and salt the given password using SSHA algorithm Hash and salt the given password using SSHA algorithm
@ -92,16 +97,16 @@ class SSHAPasswordHasher(hashers.BasePasswordHasher):
def safe_summary(self, encoded): def safe_summary(self, encoded):
""" """
Provides a safe summary ofthe password Provides a safe summary of the password
""" """
assert encoded.startswith(self.algorithm) assert encoded.startswith(self.algorithm)
hash = encoded[ALGO_LEN:] hash_str = encoded[ALGO_LEN:]
hash = binascii.hexlify(decodestring(hash.encode())).decode() hash_str = binascii.hexlify(decodestring(hash_str.encode())).decode()
return OrderedDict([ return OrderedDict([
('algorithm', self.algorithm), ('algorithm', self.algorithm),
('iterations', 0), ('iterations', 0),
('salt', hashers.mask_hash(hash[2*DIGEST_LEN:], show=2)), ('salt', hashers.mask_hash(hash_str[2*DIGEST_LEN:], show=2)),
('hash', hashers.mask_hash(hash[:2*DIGEST_LEN])), ('hash', hashers.mask_hash(hash_str[:2*DIGEST_LEN])),
]) ])
def harden_runtime(self, password, encoded): def harden_runtime(self, password, encoded):

View file

@ -20,20 +20,28 @@
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
""" """
Write in a python file the list of all contributors sorted by number of commits. Write in a python file the list of all contributors sorted by number of
This list is extracted from the FedeRez gitlab repository. commits. This list is extracted from the current gitlab repository.
""" """
from django.core.management.base import BaseCommand, CommandError
import os import os
from django.core.management.base import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
help = 'Update contributors list' """ The command object for `gen_contrib` """
help = 'Update contributors list'
def handle(self, *args, **options): def handle(self, *args, **options):
contributeurs = [item.split('\t')[1] for item in os.popen("git shortlog -s -n").read().split("\n") if '\t' in item] contributeurs = [
item.split('\t')[1]
for item in os.popen("git shortlog -s -n").read().split("\n")
if '\t' in item
]
self.stdout.write(self.style.SUCCESS("Exportation Sucessfull")) self.stdout.write(self.style.SUCCESS("Exportation Sucessfull"))
with open("re2o/contributors.py", "w") as contrib_file: with open("re2o/contributors.py", "w") as contrib_file:
contrib_file.write("#!/usr/bin/env python3\n") contrib_file.write("\"\"\"re2o.contributors\n")
contrib_file.write("A list of the proud contributors to Re2o\n")
contrib_file.write("\"\"\"\n")
contrib_file.write("\n") contrib_file.write("\n")
contrib_file.write("CONTRIBUTORS = " + str(contributeurs)) contrib_file.write("CONTRIBUTORS = " + str(contributeurs))

View file

@ -19,27 +19,44 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""re2o.mixins
A set of mixins used all over the project to avoid duplicating code
"""
from reversion import revisions as reversion from reversion import revisions as reversion
class RevMixin(object): class RevMixin(object):
""" A mixin to subclass the save and delete function of a model
to enforce the versioning of the object before those actions
really happen """
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" Creates a version of this object and save it to database """
if self.pk is None: if self.pk is None:
reversion.set_comment("Création") reversion.set_comment("Création")
return super(RevMixin, self).save(*args, **kwargs) return super(RevMixin, self).save(*args, **kwargs)
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
""" Creates a version of this object and delete it from database """
reversion.set_comment("Suppresion") reversion.set_comment("Suppresion")
return super(RevMixin, self).delete(*args, **kwargs) return super(RevMixin, self).delete(*args, **kwargs)
class FormRevMixin(object): class FormRevMixin(object):
""" A mixin to subclass the save function of a form
to enforce the versionning of the object before it is really edited """
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" Create a version of this object and save it to database """
if reversion.get_comment() != "" and self.changed_data != []: if reversion.get_comment() != "" and self.changed_data != []:
reversion.set_comment(reversion.get_comment() + ",%s" % ', '.join(field for field in self.changed_data)) reversion.set_comment(
reversion.get_comment() + ",%s"
% ', '.join(field for field in self.changed_data)
)
elif self.changed_data: elif self.changed_data:
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in self.changed_data)) reversion.set_comment(
"Champs modifié(s) : %s"
% ', '.join(field for field in self.changed_data)
)
return super(FormRevMixin, self).save(*args, **kwargs) return super(FormRevMixin, self).save(*args, **kwargs)
@ -47,23 +64,29 @@ class AclMixin(object):
"""This mixin is used in nearly every class/models defined in re2o apps. """This mixin is used in nearly every class/models defined in re2o apps.
It is used by acl, in models (decorators can_...) and in templates tags It is used by acl, in models (decorators can_...) and in templates tags
:get_instance: Applied on a class, take an id argument, return an instance :get_instance: Applied on a class, take an id argument, return an instance
:can_create: Applied on a class, take the requested user, return if the user :can_create: Applied on a class, take the requested user, return if the
can do the creation user can do the creation
:can_edit: Applied on an instance, return if the user can edit the instance :can_edit: Applied on an instance, return if the user can edit the
:can_delete: Applied on an instance, return if the user can delete the instance instance
:can_view: Applied on an instance, return if the user can view the instance :can_delete: Applied on an instance, return if the user can delete the
:can_view_all: Applied on a class, return if the user can view all instances""" instance
:can_view: Applied on an instance, return if the user can view the
instance
:can_view_all: Applied on a class, return if the user can view all
instances"""
@classmethod @classmethod
def get_classname(cls): def get_classname(cls):
""" Returns the name of the class where this mixin is used """
return str(cls.__name__).lower() return str(cls.__name__).lower()
@classmethod @classmethod
def get_modulename(cls): def get_modulename(cls):
""" Returns the name of the module where this mixin is used """
return str(cls.__module__).split('.')[0].lower() return str(cls.__module__).split('.')[0].lower()
@classmethod @classmethod
def get_instance(cls, *args, **kwargs): def get_instance(cls, *_args, **kwargs):
"""Récupère une instance """Récupère une instance
:param objectid: Instance id à trouver :param objectid: Instance id à trouver
:return: Une instance de la classe évidemment""" :return: Une instance de la classe évidemment"""
@ -71,42 +94,66 @@ class AclMixin(object):
return cls.objects.get(pk=object_id) return cls.objects.get(pk=object_id)
@classmethod @classmethod
def can_create(cls, user_request, *args, **kwargs): def can_create(cls, user_request, *_args, **_kwargs):
"""Verifie que l'user a les bons droits pour créer """Verifie que l'user a les bons droits pour créer
un object un object
:param user_request: instance utilisateur qui fait la requête :param user_request: instance utilisateur qui fait la requête
:return: soit True, soit False avec la raison de l'échec""" :return: soit True, soit False avec la raison de l'échec"""
return user_request.has_perm(cls.get_modulename() + '.add_' + cls.get_classname()), u"Vous n'avez pas le droit\ return (
de créer un " + cls.get_classname() user_request.has_perm(
cls.get_modulename() + '.add_' + cls.get_classname()
),
u"Vous n'avez pas le droit de créer un " + cls.get_classname()
)
def can_edit(self, user_request, *args, **kwargs): def can_edit(self, user_request, *_args, **_kwargs):
"""Verifie que l'user a les bons droits pour editer """Verifie que l'user a les bons droits pour editer
cette instance cette instance
:param self: Instance à editer :param self: Instance à editer
:param user_request: Utilisateur qui fait la requête :param user_request: Utilisateur qui fait la requête
:return: soit True, soit False avec la raison de l'échec""" :return: soit True, soit False avec la raison de l'échec"""
return user_request.has_perm(self.get_modulename() + '.change_' + self.get_classname()), u"Vous n'avez pas le droit d'éditer des " + self.get_classname() return (
user_request.has_perm(
self.get_modulename() + '.change_' + self.get_classname()
),
u"Vous n'avez pas le droit d'éditer des " + self.get_classname()
)
def can_delete(self, user_request, *args, **kwargs): def can_delete(self, user_request, *_args, **_kwargs):
"""Verifie que l'user a les bons droits pour delete """Verifie que l'user a les bons droits pour delete
cette instance cette instance
:param self: Instance à delete :param self: Instance à delete
:param user_request: Utilisateur qui fait la requête :param user_request: Utilisateur qui fait la requête
:return: soit True, soit False avec la raison de l'échec""" :return: soit True, soit False avec la raison de l'échec"""
return user_request.has_perm(self.get_modulename() + '.delete_' + self.get_classname()), u"Vous n'avez pas le droit d'éditer des " + self.get_classname() return (
user_request.has_perm(
self.get_modulename() + '.delete_' + self.get_classname()
),
u"Vous n'avez pas le droit d'éditer des " + self.get_classname()
)
@classmethod @classmethod
def can_view_all(cls, user_request, *args, **kwargs): def can_view_all(cls, user_request, *_args, **_kwargs):
"""Vérifie qu'on peut bien afficher l'ensemble des objets, """Vérifie qu'on peut bien afficher l'ensemble des objets,
droit particulier view objet correspondant droit particulier view objet correspondant
:param user_request: instance user qui fait l'edition :param user_request: instance user qui fait l'edition
:return: True ou False avec la raison de l'échec le cas échéant""" :return: True ou False avec la raison de l'échec le cas échéant"""
return user_request.has_perm(cls.get_modulename() + '.view_' + cls.get_classname()), u"Vous n'avez pas le droit de voir des " + cls.get_classname() return (
user_request.has_perm(
cls.get_modulename() + '.view_' + cls.get_classname()
),
u"Vous n'avez pas le droit de voir des " + cls.get_classname()
)
def can_view(self, user_request, *args, **kwargs): def can_view(self, user_request, *_args, **_kwargs):
"""Vérifie qu'on peut bien voir cette instance particulière avec """Vérifie qu'on peut bien voir cette instance particulière avec
droit view objet droit view objet
:param self: instance à voir :param self: instance à voir
:param user_request: instance user qui fait l'edition :param user_request: instance user qui fait l'edition
:return: True ou False avec la raison de l'échec le cas échéant""" :return: True ou False avec la raison de l'échec le cas échéant"""
return user_request.has_perm(self.get_modulename() + '.view_' + self.get_classname()), u"Vous n'avez pas le droit de voir des " + self.get_classname() return (
user_request.has_perm(
self.get_modulename() + '.view_' + self.get_classname()
),
u"Vous n'avez pas le droit de voir des " + self.get_classname()
)

View file

@ -18,33 +18,42 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""re2o.script_utils
A set of utility scripts that can be used as standalone to interact easily
with Re2o throught the CLI
"""
import os, sys, pwd import os
from os.path import dirname
import sys
import pwd
from getpass import getpass
from reversion import revisions as reversion
proj_path="/var/www/re2o"
os.environ.setdefault("DJANGO_SETTINGS_MODULE","re2o.settings")
sys.path.append(proj_path)
os.chdir(proj_path)
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
from django.core.management.base import CommandError from django.core.management.base import CommandError
from django.db import transaction
from django.utils.html import strip_tags
from users.models import User from users.models import User
from django.utils.html import strip_tags proj_path = dirname(dirname(__file__))
from reversion import revisions as reversion os.environ.setdefault("DJANGO_SETTINGS_MODULE", "re2o.settings")
from django.db import transaction sys.path.append(proj_path)
from getpass import getpass os.chdir(proj_path)
application = get_wsgi_application()
def get_user(pseudo): def get_user(pseudo):
"""Cherche un utilisateur re2o à partir de son pseudo""" """Cherche un utilisateur re2o à partir de son pseudo"""
user = User.objects.filter(pseudo=pseudo) user = User.objects.filter(pseudo=pseudo)
if len(user)==0: if len(user) == 0:
raise CommandError("Utilisateur invalide") raise CommandError("Utilisateur invalide")
if len(user)>1: if len(user) > 1:
raise CommandError("Plusieurs utilisateurs correspondant à ce pseudo. Ceci NE DEVRAIT PAS arriver") raise CommandError("Plusieurs utilisateurs correspondant à ce "
"pseudo. Ceci NE DEVRAIT PAS arriver")
return user[0] return user[0]
@ -53,7 +62,7 @@ def get_system_user():
return pwd.getpwuid(int(os.getenv("SUDO_UID") or os.getuid())).pw_name return pwd.getpwuid(int(os.getenv("SUDO_UID") or os.getuid())).pw_name
def form_cli(Form,user,action,*args,**kwargs): def form_cli(Form, user, action, *args, **kwargs):
""" """
Remplit un formulaire à partir de la ligne de commande Remplit un formulaire à partir de la ligne de commande
Form : le formulaire (sous forme de classe) à remplir Form : le formulaire (sous forme de classe) à remplir
@ -61,26 +70,30 @@ def form_cli(Form,user,action,*args,**kwargs):
action : l'action réalisée par le formulaire (pour les logs) action : l'action réalisée par le formulaire (pour les logs)
Les arguments suivants sont transmis tels quels au formulaire. Les arguments suivants sont transmis tels quels au formulaire.
""" """
data={} data = {}
dumb_form = Form(user=user,*args,**kwargs) dumb_form = Form(user=user, *args, **kwargs)
for key in dumb_form.fields: for key in dumb_form.fields:
if not dumb_form.fields[key].widget.input_type=='hidden': if not dumb_form.fields[key].widget.input_type == 'hidden':
if dumb_form.fields[key].widget.input_type=='password': if dumb_form.fields[key].widget.input_type == 'password':
data[key]=getpass("%s : " % dumb_form.fields[key].label) data[key] = getpass("%s : " % dumb_form.fields[key].label)
else: else:
data[key]=input("%s : " % dumb_form.fields[key].label) data[key] = input("%s : " % dumb_form.fields[key].label)
form = Form(data,user=user,*args,**kwargs) form = Form(data, user=user, *args, **kwargs)
if not form.is_valid(): if not form.is_valid():
sys.stderr.write("Erreurs : \n") sys.stderr.write("Erreurs : \n")
for err in form.errors: for err in form.errors:
#Oui, oui, on gère du HTML là où d'autres ont eu la lumineuse idée de le mettre # Oui, oui, on gère du HTML là où d'autres ont eu la
sys.stderr.write("\t%s : %s\n" % (err,strip_tags(form.errors[err]))) # lumineuse idée de le mettre
sys.stderr.write(
"\t%s : %s\n" % (err, strip_tags(form.errors[err]))
)
raise CommandError("Formulaire invalide") raise CommandError("Formulaire invalide")
with transaction.atomic(), reversion.create_revision(): with transaction.atomic(), reversion.create_revision():
form.save() form.save()
reversion.set_user(user) reversion.set_user(user)
reversion.set_comment(action) reversion.set_comment(action)
sys.stdout.write("%s : effectué. La modification peut prendre quelques minutes pour s'appliquer.\n" % action) sys.stdout.write("%s : effectué. La modification peut prendre "
"quelques minutes pour s'appliquer.\n" % action)

View file

@ -35,38 +35,37 @@ https://docs.djangoproject.com/en/1.8/ref/settings/
from __future__ import unicode_literals from __future__ import unicode_literals
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os import os
from .settings_local import * from .settings_local import *
# The root directory for the project
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
# Auth definition # Auth definition
PASSWORD_HASHERS = ( PASSWORD_HASHERS = (
're2o.login.SSHAPasswordHasher', 're2o.login.SSHAPasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2PasswordHasher',
) )
AUTH_USER_MODEL = 'users.User' # The class to use for authentication
AUTH_USER_MODEL = 'users.User' LOGIN_URL = '/login/' # The URL for login page
LOGIN_URL = '/login/' LOGIN_REDIRECT_URL = '/' # The URL for redirecting after login
LOGIN_REDIRECT_URL = '/'
# Application definition # Application definition
DJANGO_CONTRIB_APPS = (
INSTALLED_APPS = (
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
)
EXTERNAL_CONTRIB_APPS = (
'bootstrap3', 'bootstrap3',
'rest_framework',
'reversion',
)
LOCAL_APPS = (
'users', 'users',
'machines', 'machines',
'cotisations', 'cotisations',
@ -75,11 +74,14 @@ INSTALLED_APPS = (
're2o', 're2o',
'preferences', 'preferences',
'logs', 'logs',
'rest_framework', 'api',
'reversion', )
'api' INSTALLED_APPS = (
) + OPTIONNAL_APPS DJANGO_CONTRIB_APPS +
EXTERNAL_CONTRIB_APPS +
LOCAL_APPS +
OPTIONNAL_APPS
)
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware', 'django.middleware.locale.LocaleMiddleware',
@ -93,14 +95,17 @@ MIDDLEWARE_CLASSES = (
'reversion.middleware.RevisionMiddleware', 'reversion.middleware.RevisionMiddleware',
) )
# The root url module to define the project URLs
ROOT_URLCONF = 're2o.urls' ROOT_URLCONF = 're2o.urls'
# The templates configuration (see Django documentation)
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [ 'DIRS': [
os.path.join(BASE_DIR, 'templates').replace('\\', '/'), # Use only absolute paths with '/' delimiters even on Windows
], os.path.join(BASE_DIR, 'templates').replace('\\', '/'),
],
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
@ -115,62 +120,52 @@ TEMPLATES = [
}, },
] ]
# The WSGI module to use in a server environment
WSGI_APPLICATION = 're2o.wsgi.application' WSGI_APPLICATION = 're2o.wsgi.application'
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/ # https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'en' LANGUAGE_CODE = 'en'
USE_I18N = True
USE_L10N = True
# Proritary location search for translations # Proritary location search for translations
# then searches in {app}/locale/ for app in INSTALLED_APPS # then searches in {app}/locale/ for app in INSTALLED_APPS
# Use only absolute paths with '/' delimiters even on Windows
LOCALE_PATHS = [ LOCALE_PATHS = [
BASE_DIR + '/templates/locale/' # to define translations outside of apps # For translations outside of apps
os.path.join(BASE_DIR, 'templates', 'locale').replace('\\', '/')
] ]
TIME_ZONE = 'Europe/Paris' # Should use time zone ?
USE_I18N = True
USE_L10N = True
USE_TZ = True USE_TZ = True
# Router config for database
DATABASE_ROUTERS = ['ldapdb.router.Router'] DATABASE_ROUTERS = ['ldapdb.router.Router']
# django-bootstrap3 config
# django-bootstrap3 config dictionnary
BOOTSTRAP3 = { BOOTSTRAP3 = {
'jquery_url': '/static/js/jquery-2.2.4.min.js', 'jquery_url': '/static/js/jquery-2.2.4.min.js',
'base_url': '/static/bootstrap/', 'base_url': '/static/bootstrap/',
'include_jquery': True, 'include_jquery': True,
} }
BOOTSTRAP_BASE_URL = '/static/bootstrap/' BOOTSTRAP_BASE_URL = '/static/bootstrap/'
# Directories where collectstatic should look for static files
# Use only absolute paths with '/' delimiters even on Windows
STATICFILES_DIRS = ( STATICFILES_DIRS = (
# Put strings here, like "/home/html/static" or "C:/www/django/static". os.path.join(BASE_DIR, 'static').replace('\\', '/'),
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
os.path.join(
BASE_DIR,
'static',
),
) )
# Directory where the static files served by the server are stored
MEDIA_ROOT = '/var/www/re2o/media'
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static_files') STATIC_ROOT = os.path.join(BASE_DIR, 'static_files')
# The URL to access the static files
STATIC_URL = '/static/'
# Directory where the media files served by the server are stored
MEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace('\\', '/')
# The URL to access the static files
MEDIA_URL = '/media/'
RIGHTS_LINK = { # Models to use for graphs
'cableur' : ['bureau','infra','bofh','tresorier'],
'bofh' : ['bureau','tresorier'],
}
GRAPH_MODELS = { GRAPH_MODELS = {
'all_applications': True, 'all_applications': True,
'group_models': True, 'group_models': True,
} }

View file

@ -19,45 +19,56 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""re2o.settings_locale.example
The example settings_locale.py file with all the available
options for a locale configuration of re2o
"""
from __future__ import unicode_literals from __future__ import unicode_literals
# A secret key used by the server.
SECRET_KEY = 'SUPER_SECRET_KEY' SECRET_KEY = 'SUPER_SECRET_KEY'
# The password to access the project database
DB_PASSWORD = 'SUPER_SECRET_DB' DB_PASSWORD = 'SUPER_SECRET_DB'
# AES key for secret key encryption length must be a multiple of 16 # AES key for secret key encryption.
AES_KEY = 'THE_AES_KEY' # The length must be a multiple of 16
AES_KEY = 'A_SECRET_AES_KEY'
# Should the server run in debug mode ?
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False DEBUG = False
# A list of admins of the services. Receive mails when an error occurs
ADMINS = [('Example', 'rezo-admin@example.org')] ADMINS = [('Example', 'rezo-admin@example.org')]
SERVER_EMAIL = 'no-reply@example.org' # The list of hostname the server will respond to.
# Obligatoire, liste des host autorisés
ALLOWED_HOSTS = ['URL_SERVER'] ALLOWED_HOSTS = ['URL_SERVER']
# The time zone the server is runned in
TIME_ZONE = 'Europe/Paris'
# The storage systems parameters to use
DATABASES = { DATABASES = {
'default': { 'default': { # The DB
'ENGINE': 'db_engine', 'ENGINE': 'db_engine',
'NAME': 'db_name_value', 'NAME': 'db_name_value',
'USER': 'db_user_value', 'USER': 'db_user_value',
'PASSWORD': DB_PASSWORD, 'PASSWORD': DB_PASSWORD,
'HOST': 'db_host_value', 'HOST': 'db_host_value',
}, },
'ldap': { 'ldap': { # The LDAP
'ENGINE': 'ldapdb.backends.ldap', 'ENGINE': 'ldapdb.backends.ldap',
'NAME': 'ldap://ldap_host_ip/', 'NAME': 'ldap://ldap_host_ip/',
'USER': 'ldap_dn', 'USER': 'ldap_dn',
# 'TLS': True, 'TLS': True,
'PASSWORD': 'SUPER_SECRET_LDAP', 'PASSWORD': 'SUPER_SECRET_LDAP',
} }
} }
# Security settings, à activer une fois https en place # Security settings for secure https
# Activate once https is correctly configured
SECURE_CONTENT_TYPE_NOSNIFF = False SECURE_CONTENT_TYPE_NOSNIFF = False
SECURE_BROWSER_XSS_FILTER = False SECURE_BROWSER_XSS_FILTER = False
SESSION_COOKIE_SECURE = False SESSION_COOKIE_SECURE = False
@ -66,30 +77,33 @@ CSRF_COOKIE_HTTPONLY = False
X_FRAME_OPTIONS = 'DENY' X_FRAME_OPTIONS = 'DENY'
SESSION_COOKIE_AGE = 60 * 60 * 3 SESSION_COOKIE_AGE = 60 * 60 * 3
# The path where your organization logo is stored
LOGO_PATH = "static_files/logo.png" LOGO_PATH = "static_files/logo.png"
EMAIL_HOST = 'MY_EMAIL_HOST' # The mail configuration for Re2o to send mails
EMAIL_PORT = MY_EMAIL_PORT SERVER_EMAIL = 'no-reply@example.org' # The mail address to use
EMAIL_HOST = 'MY_EMAIL_HOST' # The host to use
EMAIL_PORT = MY_EMAIL_PORT # The port to use
# Reglages pour la bdd ldap # Settings of the LDAP structure
LDAP = { LDAP = {
'base_user_dn' : 'cn=Utilisateurs,dc=example,dc=org', 'base_user_dn': 'cn=Utilisateurs,dc=example,dc=org',
'base_userservice_dn' : 'ou=service-users,dc=example,dc=org', 'base_userservice_dn': 'ou=service-users,dc=example,dc=org',
'base_usergroup_dn' : 'ou=posix,ou=groups,dc=example,dc=org', 'base_usergroup_dn': 'ou=posix,ou=groups,dc=example,dc=org',
'base_userservicegroup_dn' : 'ou=services,ou=groups,dc=example,dc=org', 'base_userservicegroup_dn': 'ou=services,ou=groups,dc=example,dc=org',
'user_gid' : 500, 'user_gid': 500,
} }
# A range of UID to use. Used in linux environement
UID_RANGES = { UID_RANGES = {
'users' : [21001,30000], 'users': [21001, 30000],
'service-users' : [20000,21000], 'service-users': [20000, 21000],
} }
# Chaque groupe a un gid assigné, voici la place libre pour assignation # A range of GID to use. Used in linux environement
GID_RANGES = { GID_RANGES = {
'posix' : [501, 600], 'posix': [501, 600],
} }
# Some Django apps you want to add in you local project
OPTIONNAL_APPS = () OPTIONNAL_APPS = ()

View file

@ -37,7 +37,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% for service in service_list %} {% for service in service_list %}
<div class="col-12"> <div class="col-12">
<div class="thumbnail"> <div class="thumbnail">
<a href="{{ service.url }}"><img src="{% static service.image %}" alt="{{ service.name }}"></a> {% if service.image %}
<a href="{{ service.url }}"><img src="{{ service.image.url }}" alt="{{ service.name }}"></a>
{% endif %}
<div class="caption"> <div class="caption">
<h3>{{ service.name }}</h3> <h3>{{ service.name }}</h3>
<p>{{ service.description }}</p> <p>{{ service.description }}</p>

View file

@ -18,4 +18,3 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

View file

@ -85,32 +85,32 @@ register = template.Library()
MODEL_NAME = { MODEL_NAME = {
# cotisations # cotisations
'Facture' : cotisations.models.Facture, 'Facture': cotisations.models.Facture,
'Vente' : cotisations.models.Vente, 'Vente': cotisations.models.Vente,
'Article' : cotisations.models.Article, 'Article': cotisations.models.Article,
'Banque' : cotisations.models.Banque, 'Banque': cotisations.models.Banque,
'Paiement' : cotisations.models.Paiement, 'Paiement': cotisations.models.Paiement,
'Cotisation' : cotisations.models.Cotisation, 'Cotisation': cotisations.models.Cotisation,
# machines # machines
'Machine' : machines.models.Machine, 'Machine': machines.models.Machine,
'MachineType' : machines.models.MachineType, 'MachineType': machines.models.MachineType,
'IpType' : machines.models.IpType, 'IpType': machines.models.IpType,
'Vlan' : machines.models.Vlan, 'Vlan': machines.models.Vlan,
'Nas' : machines.models.Nas, 'Nas': machines.models.Nas,
'SOA' : machines.models.SOA, 'SOA': machines.models.SOA,
'Extension' : machines.models.Extension, 'Extension': machines.models.Extension,
'Mx' : machines.models.Mx, 'Mx': machines.models.Mx,
'Ns' : machines.models.Ns, 'Ns': machines.models.Ns,
'Txt' : machines.models.Txt, 'Txt': machines.models.Txt,
'Srv' : machines.models.Srv, 'Srv': machines.models.Srv,
'Interface' : machines.models.Interface, 'Interface': machines.models.Interface,
'Domain' : machines.models.Domain, 'Domain': machines.models.Domain,
'IpList' : machines.models.IpList, 'IpList': machines.models.IpList,
'Ipv6List' : machines.models.Ipv6List, 'Ipv6List': machines.models.Ipv6List,
'machines.Service' : machines.models.Service, 'machines.Service': machines.models.Service,
'Service_link' : machines.models.Service_link, 'Service_link': machines.models.Service_link,
'OuverturePortList' : machines.models.OuverturePortList, 'OuverturePortList': machines.models.OuverturePortList,
'OuverturePort' : machines.models.OuverturePort, 'OuverturePort': machines.models.OuverturePort,
# preferences # preferences
'OptionalUser': preferences.models.OptionalUser, 'OptionalUser': preferences.models.OptionalUser,
'OptionalMachine': preferences.models.OptionalMachine, 'OptionalMachine': preferences.models.OptionalMachine,
@ -120,25 +120,25 @@ MODEL_NAME = {
'AssoOption': preferences.models.AssoOption, 'AssoOption': preferences.models.AssoOption,
'MailMessageOption': preferences.models.MailMessageOption, 'MailMessageOption': preferences.models.MailMessageOption,
# topologie # topologie
'Stack' : topologie.models.Stack, 'Stack': topologie.models.Stack,
'Switch' : topologie.models.Switch, 'Switch': topologie.models.Switch,
'AccessPoint' : topologie.models.AccessPoint, 'AccessPoint': topologie.models.AccessPoint,
'ModelSwitch' : topologie.models.ModelSwitch, 'ModelSwitch': topologie.models.ModelSwitch,
'ConstructorSwitch' : topologie.models.ConstructorSwitch, 'ConstructorSwitch': topologie.models.ConstructorSwitch,
'Port' : topologie.models.Port, 'Port': topologie.models.Port,
'Room' : topologie.models.Room, 'Room': topologie.models.Room,
'Building' : topologie.models.Building, 'Building': topologie.models.Building,
'SwitchBay' : topologie.models.SwitchBay, 'SwitchBay': topologie.models.SwitchBay,
# users # users
'User' : users.models.User, 'User': users.models.User,
'Adherent' : users.models.Adherent, 'Adherent': users.models.Adherent,
'Club' : users.models.Club, 'Club': users.models.Club,
'ServiceUser' : users.models.ServiceUser, 'ServiceUser': users.models.ServiceUser,
'School' : users.models.School, 'School': users.models.School,
'ListRight' : users.models.ListRight, 'ListRight': users.models.ListRight,
'ListShell' : users.models.ListShell, 'ListShell': users.models.ListShell,
'Ban' : users.models.Ban, 'Ban': users.models.Ban,
'Whitelist' : users.models.Whitelist, 'Whitelist': users.models.Whitelist,
} }
@ -184,17 +184,41 @@ def get_callback(tag_name, obj=None):
if tag_name == 'cannot_view_all': if tag_name == 'cannot_view_all':
return acl_fct(obj.can_view_all, True) return acl_fct(obj.can_view_all, True)
if tag_name == 'can_view_app': if tag_name == 'can_view_app':
return acl_fct(lambda x : (not any(not sys.modules[o].can_view(x) for o in obj), None), False) return acl_fct(
lambda x: (
not any(not sys.modules[o].can_view(x) for o in obj),
None
),
False
)
if tag_name == 'cannot_view_app': if tag_name == 'cannot_view_app':
return acl_fct(lambda x : (not any(not sys.modules[o].can_view(x) for o in obj), None), True) return acl_fct(
lambda x: (
not any(not sys.modules[o].can_view(x) for o in obj),
None
),
True
)
if tag_name == 'can_edit_history': if tag_name == 'can_edit_history':
return acl_fct(lambda user:(user.has_perm('admin.change_logentry'),None),False) return acl_fct(
lambda user: (user.has_perm('admin.change_logentry'), None),
False
)
if tag_name == 'cannot_edit_history': if tag_name == 'cannot_edit_history':
return acl_fct(lambda user:(user.has_perm('admin.change_logentry'),None),True) return acl_fct(
lambda user: (user.has_perm('admin.change_logentry'), None),
True
)
if tag_name == 'can_view_any_app': if tag_name == 'can_view_any_app':
return acl_fct(lambda x : (any(sys.modules[o].can_view(x) for o in obj), None), False) return acl_fct(
lambda x: (any(sys.modules[o].can_view(x) for o in obj), None),
False
)
if tag_name == 'cannot_view_any_app': if tag_name == 'cannot_view_any_app':
return acl_fct(lambda x : (any(sys.modules[o].can_view(x) for o in obj), None), True) return acl_fct(
lambda x: (any(sys.modules[o].can_view(x) for o in obj), None),
True
)
raise template.TemplateSyntaxError( raise template.TemplateSyntaxError(
"%r tag is not a valid can_xxx tag" % tag_name "%r tag is not a valid can_xxx tag" % tag_name
@ -246,11 +270,11 @@ def acl_app_filter(parser, token):
tag_name, *app_name = token.split_contents() tag_name, *app_name = token.split_contents()
except ValueError: except ValueError:
raise template.TemplateSyntaxError( raise template.TemplateSyntaxError(
"%r tag require 1 argument : an application" "%r tag require 1 argument: an application"
% token.contents.split()[0] % token.contents.split()[0]
) )
for name in app_name: for name in app_name:
if not name in sys.modules.keys(): if name not in sys.modules.keys():
raise template.TemplateSyntaxError( raise template.TemplateSyntaxError(
"%r is not a registered application for acl." "%r is not a registered application for acl."
% name % name
@ -270,6 +294,7 @@ def acl_app_filter(parser, token):
return AclNode(callback, oknodes, konodes) return AclNode(callback, oknodes, konodes)
@register.tag('can_change') @register.tag('can_change')
@register.tag('cannot_change') @register.tag('cannot_change')
def acl_change_filter(parser, token): def acl_change_filter(parser, token):
@ -277,13 +302,13 @@ def acl_change_filter(parser, token):
try: try:
tag_content = token.split_contents() tag_content = token.split_contents()
tag_name = tag_content[0] # tag_name = tag_content[0]
model_name = tag_content[1] model_name = tag_content[1]
field_name = tag_content[2] field_name = tag_content[2]
args = tag_content[3:] args = tag_content[3:]
except ValueError: except ValueError:
raise template.TemplateSyntaxError( raise template.TemplateSyntaxError(
"%r tag require at least 2 argument : the model and the field" "%r tag require at least 2 argument: the model and the field"
% token.contents.split()[0] % token.contents.split()[0]
) )
@ -306,6 +331,7 @@ def acl_change_filter(parser, token):
return AclNode(callback, oknodes, konodes, *args) return AclNode(callback, oknodes, konodes, *args)
@register.tag('can_create') @register.tag('can_create')
@register.tag('cannot_create') @register.tag('cannot_create')
@register.tag('can_edit_all') @register.tag('can_edit_all')
@ -324,7 +350,7 @@ def acl_model_filter(parser, token):
args = tag_content[2:] args = tag_content[2:]
except ValueError: except ValueError:
raise template.TemplateSyntaxError( raise template.TemplateSyntaxError(
"%r tag require at least 1 argument : the model" "%r tag require at least 1 argument: the model"
% token.contents.split()[0] % token.contents.split()[0]
) )
@ -364,7 +390,7 @@ def acl_instance_filter(parser, token):
args = tag_content[2:] args = tag_content[2:]
except ValueError: except ValueError:
raise template.TemplateSyntaxError( raise template.TemplateSyntaxError(
"%r tag require at least 1 argument : the instance" "%r tag require at least 1 argument: the instance"
% token.contents.split()[0] % token.contents.split()[0]
) )

View file

@ -36,13 +36,14 @@ from bootstrap3.forms import render_field
register = template.Library() register = template.Library()
@register.simple_tag @register.simple_tag
def massive_bootstrap_form(form, mbf_fields, *args, **kwargs): def massive_bootstrap_form(form, mbf_fields, *args, **kwargs):
""" """
Render a form where some specific fields are rendered using Twitter Render a form where some specific fields are rendered using Twitter
Typeahead and/or splitree's Bootstrap Tokenfield to improve the performance, the Typeahead and/or splitree's Bootstrap Tokenfield to improve the
speed and UX when dealing with very large datasets (select with 50k+ elts performance, the speed and UX when dealing with very large datasets
for instance). (select with 50k+ elts for instance).
When the fields specified should normally be rendered as a select with When the fields specified should normally be rendered as a select with
single selectable option, Twitter Typeahead is used for a better display single selectable option, Twitter Typeahead is used for a better display
and the matching query engine. When dealing with multiple selectable and the matching query engine. When dealing with multiple selectable
@ -189,8 +190,6 @@ def massive_bootstrap_form(form, mbf_fields, *args, **kwargs):
return mbf_form.render() return mbf_form.render()
class MBFForm(): class MBFForm():
""" An object to hold all the information and useful methods needed to """ An object to hold all the information and useful methods needed to
create and render a massive django form into an actual HTML and JS create and render a massive django form into an actual HTML and JS
@ -198,7 +197,6 @@ class MBFForm():
Every field that is not listed is rendered as a normal bootstrap_field. Every field that is not listed is rendered as a normal bootstrap_field.
""" """
def __init__(self, form, mbf_fields, *args, **kwargs): def __init__(self, form, mbf_fields, *args, **kwargs):
# The django form object # The django form object
self.form = form self.form = form
@ -224,14 +222,13 @@ class MBFForm():
# HTML code to insert inside a template # HTML code to insert inside a template
self.html = "" self.html = ""
def render(self): def render(self):
""" HTML code for the fully rendered form with all the necessary form """ HTML code for the fully rendered form with all the necessary form
""" """
for name, field in self.form.fields.items(): for name, field in self.form.fields.items():
if not name in self.exclude: if name not in self.exclude:
if name in self.fields and not name in self.hidden_fields: if name in self.fields and name not in self.hidden_fields:
mbf_field = MBFField( mbf_field = MBFField(
name, name,
field, field,
@ -256,9 +253,6 @@ class MBFForm():
return mark_safe(self.html) return mark_safe(self.html)
class MBFField(): class MBFField():
""" An object to hold all the information and useful methods needed to """ An object to hold all the information and useful methods needed to
create and render a massive django form field into an actual HTML and JS create and render a massive django form field into an actual HTML and JS
@ -270,7 +264,6 @@ class MBFField():
the displayed input. It's used to store the actual data that will be sent the displayed input. It's used to store the actual data that will be sent
to the server """ to the server """
def __init__(self, name_, field_, bound_, choices_, engine_, match_func_, def __init__(self, name_, field_, bound_, choices_, engine_, match_func_,
update_on_, gen_select_, *args_, **kwargs_): update_on_, gen_select_, *args_, **kwargs_):
@ -278,8 +271,8 @@ class MBFField():
if not isinstance(field_.widget, Select): if not isinstance(field_.widget, Select):
raise ValueError( raise ValueError(
('Field named {f_name} is not a Select and' ('Field named {f_name} is not a Select and'
'can\'t be rendered with massive_bootstrap_form.' 'can\'t be rendered with massive_bootstrap_form.')
).format( .format(
f_name=name_ f_name=name_
) )
) )
@ -324,7 +317,6 @@ class MBFField():
self.args = args_ self.args = args_
self.kwargs = kwargs_ self.kwargs = kwargs_
def default_choices(self): def default_choices(self):
""" JS code of the variable choices_<fieldname> """ """ JS code of the variable choices_<fieldname> """
@ -351,7 +343,6 @@ class MBFField():
) )
) )
def default_engine(self): def default_engine(self):
""" Default JS code of the variable engine_<field_name> """ """ Default JS code of the variable engine_<field_name> """
return ( return (
@ -365,7 +356,6 @@ class MBFField():
name=self.name name=self.name
) )
def default_datasets(self): def default_datasets(self):
""" Default JS script of the datasets to use with typeahead """ """ Default JS script of the datasets to use with typeahead """
return ( return (
@ -384,7 +374,6 @@ class MBFField():
match_func=self.match_func match_func=self.match_func
) )
def default_match_func(self): def default_match_func(self):
""" Default JS code of the matching function to use with typeahed """ """ Default JS code of the matching function to use with typeahed """
return ( return (
@ -402,14 +391,12 @@ class MBFField():
name=self.name name=self.name
) )
def render(self): def render(self):
""" HTML code for the fully rendered field """ """ HTML code for the fully rendered field """
self.gen_displayed_div() self.gen_displayed_div()
self.gen_hidden_div() self.gen_hidden_div()
return mark_safe(self.html) return mark_safe(self.html)
def gen_displayed_div(self): def gen_displayed_div(self):
""" Generate HTML code for the div that contains displayed tags """ """ Generate HTML code for the div that contains displayed tags """
if self.gen_select: if self.gen_select:
@ -434,7 +421,6 @@ class MBFField():
if not self.gen_select: if not self.gen_select:
self.html += self.replace_input self.html += self.replace_input
def gen_hidden_div(self): def gen_hidden_div(self):
""" Generate HTML code for the div that contains hidden tags """ """ Generate HTML code for the div that contains hidden tags """
self.gen_full_js() self.gen_full_js()
@ -449,7 +435,6 @@ class MBFField():
attrs={'id': self.div2_id} attrs={'id': self.div2_id}
) )
def hidden_input(self): def hidden_input(self):
""" HTML for the hidden input element """ """ HTML for the hidden input element """
return render_tag( return render_tag(
@ -462,14 +447,12 @@ class MBFField():
} }
) )
def gen_full_js(self): def gen_full_js(self):
""" Generate the full script tag containing the JS code """ """ Generate the full script tag containing the JS code """
self.create_js() self.create_js()
self.fill_js() self.fill_js()
self.get_script() self.get_script()
def create_js(self): def create_js(self):
""" Generate a template for the whole script to use depending on """ Generate a template for the whole script to use depending on
gen_select and multiple """ gen_select and multiple """
@ -549,7 +532,6 @@ class MBFField():
'}} );' '}} );'
) )
def fill_js(self): def fill_js(self):
""" Fill the template with the correct values """ """ Fill the template with the correct values """
self.js_script = self.js_script.format( self.js_script = self.js_script.format(
@ -571,11 +553,12 @@ class MBFField():
typ_init_input=self.typeahead_init_input() typ_init_input=self.typeahead_init_input()
) )
def get_script(self): def get_script(self):
""" Insert the JS code inside a script tag """ """ Insert the JS code inside a script tag """
self.js_script = render_tag('script', content=mark_safe(self.js_script)) self.js_script = render_tag(
'script',
content=mark_safe(self.js_script)
)
def del_select(self): def del_select(self):
""" JS code to delete the select if it has been generated and replace """ JS code to delete the select if it has been generated and replace
@ -589,7 +572,6 @@ class MBFField():
replace_input=self.replace_input replace_input=self.replace_input
) )
def gen_hidden(self): def gen_hidden(self):
""" JS code to add a hidden tag to store the value. """ """ JS code to add a hidden tag to store the value. """
return ( return (
@ -606,7 +588,6 @@ class MBFField():
html_name=self.bound.html_name html_name=self.bound.html_name
) )
def typeahead_init_input(self): def typeahead_init_input(self):
""" JS code to init the fields values """ """ JS code to init the fields values """
init_key = self.bound.value() or '""' init_key = self.bound.value() or '""'
@ -624,7 +605,6 @@ class MBFField():
hidden_id=self.hidden_id hidden_id=self.hidden_id
) )
def typeahead_reset_input(self): def typeahead_reset_input(self):
""" JS code to reset the fields values """ """ JS code to reset the fields values """
return ( return (
@ -635,7 +615,6 @@ class MBFField():
hidden_id=self.hidden_id hidden_id=self.hidden_id
) )
def typeahead_select(self): def typeahead_select(self):
""" JS code to create the function triggered when an item is selected """ JS code to create the function triggered when an item is selected
through typeahead """ through typeahead """
@ -649,7 +628,6 @@ class MBFField():
hidden_id=self.hidden_id hidden_id=self.hidden_id
) )
def typeahead_change(self): def typeahead_change(self):
""" JS code of the function triggered when an item is changed (i.e. """ JS code of the function triggered when an item is changed (i.e.
looses focus and value has changed since the moment it gained focus ) looses focus and value has changed since the moment it gained focus )
@ -666,7 +644,6 @@ class MBFField():
hidden_id=self.hidden_id hidden_id=self.hidden_id
) )
def typeahead_updates(self): def typeahead_updates(self):
""" JS code for binding external fields changes with a reset """ """ JS code for binding external fields changes with a reset """
reset_input = self.typeahead_reset_input() reset_input = self.typeahead_reset_input()
@ -683,7 +660,6 @@ class MBFField():
) for u_id in self.update_on] ) for u_id in self.update_on]
return ''.join(updates) return ''.join(updates)
def tokenfield_init_input(self): def tokenfield_init_input(self):
""" JS code to init the fields values """ """ JS code to init the fields values """
init_key = self.bound.value() or '""' init_key = self.bound.value() or '""'
@ -700,7 +676,6 @@ class MBFField():
) )
) )
def tokenfield_reset_input(self): def tokenfield_reset_input(self):
""" JS code to reset the fields values """ """ JS code to reset the fields values """
return ( return (
@ -709,7 +684,6 @@ class MBFField():
input_id=self.input_id input_id=self.input_id
) )
def tokenfield_create(self): def tokenfield_create(self):
""" JS code triggered when a new token is created in tokenfield. """ """ JS code triggered when a new token is created in tokenfield. """
return ( return (
@ -739,7 +713,6 @@ class MBFField():
div2_id=self.div2_id div2_id=self.div2_id
) )
def tokenfield_edit(self): def tokenfield_edit(self):
""" JS code triggered when a token is edited in tokenfield. """ """ JS code triggered when a token is edited in tokenfield. """
return ( return (
@ -765,7 +738,6 @@ class MBFField():
hidden_id=self.hidden_id hidden_id=self.hidden_id
) )
def tokenfield_remove(self): def tokenfield_remove(self):
""" JS code trigggered when a token is removed from tokenfield. """ """ JS code trigggered when a token is removed from tokenfield. """
return ( return (
@ -791,7 +763,6 @@ class MBFField():
hidden_id=self.hidden_id hidden_id=self.hidden_id
) )
def tokenfield_updates(self): def tokenfield_updates(self):
""" JS code for binding external fields changes with a reset """ """ JS code for binding external fields changes with a reset """
reset_input = self.tokenfield_reset_input() reset_input = self.tokenfield_reset_input()

View file

@ -19,12 +19,20 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""re2o.templatetags.self_adhesion
A simple templatagetag which returns the value of the option `self_adhesion`
which indicated if a user can creates an account by himself
"""
from django import template from django import template
from preferences.models import OptionalUser, GeneralOption from preferences.models import OptionalUser
register = template.Library() register = template.Library()
@register.simple_tag @register.simple_tag
def self_adhesion(): def self_adhesion():
""" Returns True if the user are allowed to create accounts """
options, _created = OptionalUser.objects.get_or_create() options, _created = OptionalUser.objects.get_or_create()
return options.self_adhesion return options.self_adhesion

View file

@ -48,6 +48,8 @@ from django.contrib.auth import views as auth_views
from .views import index, about_page from .views import index, about_page
handler500 = 're2o.views.handler500'
urlpatterns = [ urlpatterns = [
url(r'^$', index, name='index'), url(r'^$', index, name='index'),
url(r'^about/$', about_page, name='about'), url(r'^about/$', about_page, name='about'),

View file

@ -36,18 +36,13 @@ Fonction :
from __future__ import unicode_literals from __future__ import unicode_literals
from django.utils import timezone from django.utils import timezone
from django.db.models import Q from django.db.models import Q
from django.contrib import messages
from django.shortcuts import redirect
from django.urls import reverse
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from cotisations.models import Cotisation, Facture, Paiement, Vente from cotisations.models import Cotisation, Facture, Vente
from machines.models import Domain, Interface, Machine from machines.models import Interface, Machine
from users.models import Adherent, User, Ban, Whitelist from users.models import Adherent, User, Ban, Whitelist
from preferences.models import Service
def all_adherent(search_time=None): def all_adherent(search_time=None):
@ -118,14 +113,18 @@ def all_has_access(search_time=None):
def filter_active_interfaces(interface_set): def filter_active_interfaces(interface_set):
"""Filtre les machines autorisées à sortir sur internet dans une requête""" """Filtre les machines autorisées à sortir sur internet dans une requête"""
return interface_set.filter( return (interface_set
machine__in=Machine.objects.filter( .filter(
user__in=all_has_access() machine__in=Machine.objects.filter(
).filter(active=True) user__in=all_has_access()
).select_related('domain').select_related('machine')\ ).filter(active=True)
.select_related('type').select_related('ipv4')\ ).select_related('domain')
.select_related('domain__extension').select_related('ipv4__ip_type')\ .select_related('machine')
.distinct() .select_related('type')
.select_related('ipv4')
.select_related('domain__extension')
.select_related('ipv4__ip_type')
.distinct())
def filter_complete_interfaces(interface_set): def filter_complete_interfaces(interface_set):
@ -160,6 +159,7 @@ def all_active_assigned_interfaces_count():
""" Version light seulement pour compter""" """ Version light seulement pour compter"""
return all_active_interfaces_count().filter(ipv4__isnull=False) return all_active_interfaces_count().filter(ipv4__isnull=False)
class SortTable: class SortTable:
""" Class gathering uselful stuff to sort the colums of a table, according """ Class gathering uselful stuff to sort the colums of a table, according
to the column and order requested. It's used with a dict of possible to the column and order requested. It's used with a dict of possible
@ -171,7 +171,8 @@ class SortTable:
# the url value and the values are a list of model field name to use to # the url value and the values are a list of model field name to use to
# order the request. They are applied in the order they are given. # order the request. They are applied in the order they are given.
# A 'default' might be provided to specify what to do if the requested col # A 'default' might be provided to specify what to do if the requested col
# doesn't match any keys. # doesn't match any keys.
USERS_INDEX = { USERS_INDEX = {
'user_name': ['name'], 'user_name': ['name'],
'user_surname': ['surname'], 'user_surname': ['surname'],
@ -255,7 +256,7 @@ class SortTable:
} }
TOPOLOGIE_INDEX_MODEL_SWITCH = { TOPOLOGIE_INDEX_MODEL_SWITCH = {
'model-switch_name': ['reference'], 'model-switch_name': ['reference'],
'model-switch_contructor' : ['constructor__name'], 'model-switch_contructor': ['constructor__name'],
'default': ['reference'], 'default': ['reference'],
} }
TOPOLOGIE_INDEX_SWITCH_BAY = { TOPOLOGIE_INDEX_SWITCH_BAY = {
@ -290,6 +291,7 @@ class SortTable:
else: else:
return request return request
def re2o_paginator(request, query_set, pagination_number): def re2o_paginator(request, query_set, pagination_number):
"""Paginator script for list display in re2o. """Paginator script for list display in re2o.
:request: :request:
@ -307,6 +309,7 @@ def re2o_paginator(request, query_set, pagination_number):
results = paginator.page(paginator.num_pages) results = paginator.page(paginator.num_pages)
return results return results
def remove_user_room(room): def remove_user_room(room):
""" Déménage de force l'ancien locataire de la chambre """ """ Déménage de force l'ancien locataire de la chambre """
try: try:

View file

@ -26,30 +26,31 @@ les views
from __future__ import unicode_literals from __future__ import unicode_literals
from itertools import chain
import git
from reversion.models import Version
from django.http import Http404 from django.http import Http404
from django.urls import reverse from django.urls import reverse
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.template.context_processors import csrf from django.template.context_processors import csrf
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required
from reversion.models import Version
from django.contrib import messages from django.contrib import messages
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.decorators.cache import cache_page from django.views.decorators.cache import cache_page
import git import preferences
import os from preferences.models import Service, GeneralOption, AssoOption
import time import users
from itertools import chain import cotisations
import topologie
from preferences.models import Service import machines
from preferences.models import OptionalUser, GeneralOption, AssoOption
import users, preferences, cotisations, topologie, machines
from .utils import re2o_paginator from .utils import re2o_paginator
from .settings import BASE_DIR, INSTALLED_APPS, MIDDLEWARE_CLASSES
from .contributors import CONTRIBUTORS from .contributors import CONTRIBUTORS
def form(ctx, template, request): def form(ctx, template, request):
"""Form générique, raccourci importé par les fonctions views du site""" """Form générique, raccourci importé par les fonctions views du site"""
context = ctx context = ctx
@ -64,56 +65,58 @@ def index(request):
services[indice % 3].append(serv) services[indice % 3].append(serv)
return form({'services_urls': services}, 're2o/index.html', request) return form({'services_urls': services}, 're2o/index.html', request)
#: Binding the corresponding char sequence of history url to re2o models. #: Binding the corresponding char sequence of history url to re2o models.
HISTORY_BIND = { HISTORY_BIND = {
'users' : { 'users': {
'user' : users.models.User, 'user': users.models.User,
'ban' : users.models.Ban, 'ban': users.models.Ban,
'whitelist' : users.models.Whitelist, 'whitelist': users.models.Whitelist,
'school' : users.models.School, 'school': users.models.School,
'listright' : users.models.ListRight, 'listright': users.models.ListRight,
'serviceuser' : users.models.ServiceUser, 'serviceuser': users.models.ServiceUser,
'listshell' : users.models.ListShell, 'listshell': users.models.ListShell,
}, },
'preferences' : { 'preferences': {
'service' : preferences.models.Service, 'service': preferences.models.Service,
}, },
'cotisations' : { 'cotisations': {
'facture' : cotisations.models.Facture, 'facture': cotisations.models.Facture,
'article' : cotisations.models.Article, 'article': cotisations.models.Article,
'paiement' : cotisations.models.Paiement, 'paiement': cotisations.models.Paiement,
'banque' : cotisations.models.Banque, 'banque': cotisations.models.Banque,
}, },
'topologie' : { 'topologie': {
'switch' : topologie.models.Switch, 'switch': topologie.models.Switch,
'port' : topologie.models.Port, 'port': topologie.models.Port,
'room' : topologie.models.Room, 'room': topologie.models.Room,
'stack' : topologie.models.Stack, 'stack': topologie.models.Stack,
'modelswitch' : topologie.models.ModelSwitch, 'modelswitch': topologie.models.ModelSwitch,
'constructorswitch' : topologie.models.ConstructorSwitch, 'constructorswitch': topologie.models.ConstructorSwitch,
'accesspoint' : topologie.models.AccessPoint, 'accesspoint': topologie.models.AccessPoint,
'switchbay' : topologie.models.SwitchBay, 'switchbay': topologie.models.SwitchBay,
'building' : topologie.models.Building, 'building': topologie.models.Building,
}, },
'machines' : { 'machines': {
'machine' : machines.models.Machine, 'machine': machines.models.Machine,
'interface' : machines.models.Interface, 'interface': machines.models.Interface,
'domain' : machines.models.Domain, 'domain': machines.models.Domain,
'machinetype' : machines.models.MachineType, 'machinetype': machines.models.MachineType,
'iptype' : machines.models.IpType, 'iptype': machines.models.IpType,
'extension' : machines.models.Extension, 'extension': machines.models.Extension,
'soa' : machines.models.SOA, 'soa': machines.models.SOA,
'mx' : machines.models.Mx, 'mx': machines.models.Mx,
'txt' : machines.models.Txt, 'txt': machines.models.Txt,
'srv' : machines.models.Srv, 'srv': machines.models.Srv,
'ns' : machines.models.Ns, 'ns': machines.models.Ns,
'service' : machines.models.Service, 'service': machines.models.Service,
'vlan' : machines.models.Vlan, 'vlan': machines.models.Vlan,
'nas' : machines.models.Nas, 'nas': machines.models.Nas,
'ipv6list' : machines.models.Ipv6List, 'ipv6list': machines.models.Ipv6List,
}, },
} }
@login_required @login_required
def history(request, application, object_name, object_id): def history(request, application, object_name, object_id):
"""Render history for a model. """Render history for a model.
@ -136,7 +139,7 @@ def history(request, application, object_name, object_id):
""" """
try: try:
model = HISTORY_BIND[application][object_name] model = HISTORY_BIND[application][object_name]
except KeyError as e: except KeyError:
raise Http404(u"Il n'existe pas d'historique pour ce modèle.") raise Http404(u"Il n'existe pas d'historique pour ce modèle.")
object_name_id = object_name + 'id' object_name_id = object_name + 'id'
kwargs = {object_name_id: object_id} kwargs = {object_name_id: object_id}
@ -144,21 +147,23 @@ def history(request, application, object_name, object_id):
instance = model.get_instance(**kwargs) instance = model.get_instance(**kwargs)
except model.DoesNotExist: except model.DoesNotExist:
messages.error(request, u"Entrée inexistante") messages.error(request, u"Entrée inexistante")
return redirect(reverse('users:profil', return redirect(reverse(
kwargs={'userid':str(request.user.id)} 'users:profil',
kwargs={'userid': str(request.user.id)}
)) ))
can, msg = instance.can_view(request.user) can, msg = instance.can_view(request.user)
if not can: if not can:
messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu") messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse( return redirect(reverse(
'users:profil', 'users:profil',
kwargs={'userid':str(request.user.id)} kwargs={'userid': str(request.user.id)}
)) ))
pagination_number = GeneralOption.get_cached_value('pagination_number') pagination_number = GeneralOption.get_cached_value('pagination_number')
reversions = Version.objects.get_for_object(instance) reversions = Version.objects.get_for_object(instance)
if hasattr(instance, 'linked_objects'): if hasattr(instance, 'linked_objects'):
for related_object in chain(instance.linked_objects()): for related_object in chain(instance.linked_objects()):
reversions = reversions | Version.objects.get_for_object(related_object) reversions = (reversions |
Version.objects.get_for_object(related_object))
reversions = re2o_paginator(request, reversions, pagination_number) reversions = re2o_paginator(request, reversions, pagination_number)
return render( return render(
request, request,
@ -169,10 +174,13 @@ def history(request, application, object_name, object_id):
@cache_page(7 * 24 * 60 * 60) @cache_page(7 * 24 * 60 * 60)
def about_page(request): def about_page(request):
""" The view for the about page.
Fetch some info about the configuration of the project. If it can't
get the info from the Git repository, fallback to default string """
option = AssoOption.objects.get() option = AssoOption.objects.get()
git_info_contributors = CONTRIBUTORS git_info_contributors = CONTRIBUTORS
try: try:
git_repo = git.Repo(BASE_DIR) git_repo = git.Repo(settings.BASE_DIR)
git_info_remote = ", ".join(git_repo.remote().urls) git_info_remote = ", ".join(git_repo.remote().urls)
git_info_branch = git_repo.active_branch.name git_info_branch = git_repo.active_branch.name
last_commit = git_repo.commit() last_commit = git_repo.commit()
@ -185,14 +193,14 @@ def about_page(request):
git_info_commit = NO_GIT_MSG git_info_commit = NO_GIT_MSG
git_info_commit_date = NO_GIT_MSG git_info_commit_date = NO_GIT_MSG
dependencies = INSTALLED_APPS + MIDDLEWARE_CLASSES dependencies = settings.INSTALLED_APPS + settings.MIDDLEWARE_CLASSES
return render( return render(
request, request,
"re2o/about.html", "re2o/about.html",
{ {
'description': option.description , 'description': option.description,
'AssoName' : option.name , 'AssoName': option.name,
'git_info_contributors': git_info_contributors, 'git_info_contributors': git_info_contributors,
'git_info_remote': git_info_remote, 'git_info_remote': git_info_remote,
'git_info_branch': git_info_branch, 'git_info_branch': git_info_branch,
@ -202,3 +210,7 @@ def about_page(request):
} }
) )
def handler500(request):
"""The handler view for a 500 error"""
return render(request, 'errors/500.html')

View file

@ -32,8 +32,9 @@ https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/
from __future__ import unicode_literals from __future__ import unicode_literals
import os import os
import sys
from os.path import dirname from os.path import dirname
import sys
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application

View file

@ -20,5 +20,8 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""search
The app in charge of evrything related to the search function
"""
from .acl import * from .acl import *

View file

@ -26,7 +26,8 @@
Here are defined some functions to check acl on the application. Here are defined some functions to check acl on the application.
""" """
def can_view(user):
def can_view(_user):
"""Check if an user can view the application. """Check if an user can view the application.
Args: Args:

View file

@ -19,7 +19,10 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""search.tests
The tests for the Search module.
"""
from django.test import TestCase # from django.test import TestCase
# Create your tests here. # Create your tests here.

View file

@ -144,7 +144,7 @@ def search_single_word(word, filters, user,
if not User.can_view_all(user)[0]: if not User.can_view_all(user)[0]:
filter_users &= Q(id=user.id) filter_users &= Q(id=user.id)
filter_clubs = filter_users filter_clubs = filter_users
filter_users |= Q(name__icontains=word) filter_users |= Q(name__icontains=word)
filters['users'] |= filter_users filters['users'] |= filter_users
filters['clubs'] |= filter_clubs filters['clubs'] |= filter_clubs

56
templates/errors/500.html Normal file

File diff suppressed because one or more lines are too long

View file

@ -20,5 +20,9 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""topologie
The app in charge of handling all the informations about the network
topology like the switches, the rooms, how are the connections, ...
"""
from .acl import * from .acl import *

View file

@ -26,6 +26,7 @@
Here are defined some functions to check acl on the application. Here are defined some functions to check acl on the application.
""" """
def can_view(user): def can_view(user):
"""Check if an user can view the application. """Check if an user can view the application.

View file

@ -32,16 +32,18 @@ NewSwitchForm)
from __future__ import unicode_literals from __future__ import unicode_literals
from django import forms
from django.forms import ModelForm
from django.db.models import Prefetch
from machines.models import Interface from machines.models import Interface
from machines.forms import ( from machines.forms import (
EditInterfaceForm,
EditMachineForm, EditMachineForm,
NewMachineForm NewMachineForm
) )
from django import forms from re2o.mixins import FormRevMixin
from django.forms import ModelForm, Form
from django.db.models import Prefetch from .models import (
from .models import (
Port, Port,
Switch, Switch,
Room, Room,
@ -52,7 +54,7 @@ from .models import (
SwitchBay, SwitchBay,
Building, Building,
) )
from re2o.mixins import FormRevMixin
class PortForm(FormRevMixin, ModelForm): class PortForm(FormRevMixin, ModelForm):
"""Formulaire pour la création d'un port d'un switch """Formulaire pour la création d'un port d'un switch
@ -82,32 +84,48 @@ class EditPortForm(FormRevMixin, ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(EditPortForm, self).__init__(*args, prefix=prefix, **kwargs) super(EditPortForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['machine_interface'].queryset = Interface.objects.all()\ self.fields['machine_interface'].queryset = (
.select_related('domain__extension') Interface.objects.all().select_related('domain__extension')
self.fields['related'].queryset = Port.objects.all()\ )
self.fields['related'].queryset = (
Port.objects.all()
.prefetch_related(Prefetch( .prefetch_related(Prefetch(
'switch__interface_set', 'switch__interface_set',
queryset=Interface.objects.select_related('ipv4__ip_type__extension').select_related('domain__extension') queryset=(Interface.objects
.select_related('ipv4__ip_type__extension')
.select_related('domain__extension'))
)) ))
)
class AddPortForm(FormRevMixin, ModelForm): class AddPortForm(FormRevMixin, ModelForm):
"""Permet d'ajouter un port de switch. Voir EditPortForm pour plus """Permet d'ajouter un port de switch. Voir EditPortForm pour plus
d'informations""" d'informations"""
class Meta(PortForm.Meta): class Meta(PortForm.Meta):
fields = ['port', 'room', 'machine_interface', 'related', fields = [
'radius', 'vlan_force', 'details'] 'port',
'room',
'machine_interface',
'related',
'radius',
'vlan_force',
'details'
]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(AddPortForm, self).__init__(*args, prefix=prefix, **kwargs) super(AddPortForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['machine_interface'].queryset = Interface.objects.all()\ self.fields['machine_interface'].queryset = (
.select_related('domain__extension') Interface.objects.all().select_related('domain__extension')
self.fields['related'].queryset = Port.objects.all()\ )
.prefetch_related(Prefetch( self.fields['related'].queryset = (
'switch__interface_set', Port.objects.all().prefetch_related(Prefetch(
queryset=Interface.objects.select_related('ipv4__ip_type__extension').select_related('domain__extension') 'switch__interface_set',
queryset=(Interface.objects
.select_related('ipv4__ip_type__extension')
.select_related('domain__extension'))
)) ))
)
class StackForm(FormRevMixin, ModelForm): class StackForm(FormRevMixin, ModelForm):
@ -170,15 +188,22 @@ class CreatePortsForm(forms.Form):
class EditModelSwitchForm(FormRevMixin, ModelForm): class EditModelSwitchForm(FormRevMixin, ModelForm):
"""Permet d'éediter un modèle de switch : nom et constructeur""" """Permet d'éediter un modèle de switch : nom et constructeur"""
members = forms.ModelMultipleChoiceField(Switch.objects.all(), required=False) members = forms.ModelMultipleChoiceField(
Switch.objects.all(),
required=False
)
class Meta: class Meta:
model = ModelSwitch model = ModelSwitch
fields = '__all__' fields = '__all__'
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(EditModelSwitchForm, self).__init__(*args, prefix=prefix, **kwargs) super(EditModelSwitchForm, self).__init__(
*args,
prefix=prefix,
**kwargs
)
instance = kwargs.get('instance', None) instance = kwargs.get('instance', None)
if instance: if instance:
self.initial['members'] = Switch.objects.filter(model=instance) self.initial['members'] = Switch.objects.filter(model=instance)
@ -197,13 +222,20 @@ class EditConstructorSwitchForm(FormRevMixin, ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(EditConstructorSwitchForm, self).__init__(*args, prefix=prefix, **kwargs) super(EditConstructorSwitchForm, self).__init__(
*args,
prefix=prefix,
**kwargs
)
class EditSwitchBayForm(FormRevMixin, ModelForm): class EditSwitchBayForm(FormRevMixin, ModelForm):
"""Permet d'éditer une baie de brassage""" """Permet d'éditer une baie de brassage"""
members = forms.ModelMultipleChoiceField(Switch.objects.all(), required=False) members = forms.ModelMultipleChoiceField(
Switch.objects.all(),
required=False
)
class Meta: class Meta:
model = SwitchBay model = SwitchBay
fields = '__all__' fields = '__all__'

View file

@ -47,9 +47,10 @@ from django.db import IntegrityError
from django.db import transaction from django.db import transaction
from reversion import revisions as reversion from reversion import revisions as reversion
from machines.models import Machine, Interface, regen from machines.models import Machine, regen
from re2o.mixins import AclMixin, RevMixin from re2o.mixins import AclMixin, RevMixin
class Stack(AclMixin, RevMixin, models.Model): class Stack(AclMixin, RevMixin, models.Model):
"""Un objet stack. Regrouppe des switchs en foreign key """Un objet stack. Regrouppe des switchs en foreign key
,contient une id de stack, un switch id min et max dans ,contient une id de stack, un switch id min et max dans
@ -85,12 +86,12 @@ class Stack(AclMixin, RevMixin, models.Model):
class AccessPoint(AclMixin, Machine): class AccessPoint(AclMixin, Machine):
"""Define a wireless AP. Inherit from machines.interfaces """Define a wireless AP. Inherit from machines.interfaces
Definition pour une borne wifi , hérite de machines.interfaces Definition pour une borne wifi , hérite de machines.interfaces
""" """
PRETTY_NAME = "Borne WiFi" PRETTY_NAME = "Borne WiFi"
location = models.CharField( location = models.CharField(
max_length=255, max_length=255,
help_text="Détails sur la localisation de l'AP", help_text="Détails sur la localisation de l'AP",
blank=True, blank=True,
@ -120,7 +121,6 @@ class Switch(AclMixin, Machine):
id_max de la stack parente""" id_max de la stack parente"""
PRETTY_NAME = "Switch / Commutateur" PRETTY_NAME = "Switch / Commutateur"
number = models.PositiveIntegerField() number = models.PositiveIntegerField()
stack = models.ForeignKey( stack = models.ForeignKey(
'topologie.Stack', 'topologie.Stack',
@ -165,7 +165,8 @@ class Switch(AclMixin, Machine):
ne peut être nul"}) ne peut être nul"})
def create_ports(self, begin, end): def create_ports(self, begin, end):
""" Crée les ports de begin à end si les valeurs données sont cohérentes. """ """ Crée les ports de begin à end si les valeurs données
sont cohérentes. """
s_begin = s_end = 0 s_begin = s_end = 0
nb_ports = self.ports.count() nb_ports = self.ports.count()
@ -192,6 +193,7 @@ class Switch(AclMixin, Machine):
ValidationError("Création d'un port existant.") ValidationError("Création d'un port existant.")
def main_interface(self): def main_interface(self):
""" Returns the 'main' interface of the switch """
return self.interface_set.first() return self.interface_set.first()
def __str__(self): def __str__(self):
@ -331,14 +333,15 @@ class Port(AclMixin, RevMixin, models.Model):
("view_port", "Peut voir un objet port"), ("view_port", "Peut voir un objet port"),
) )
def get_instance(portid, *args, **kwargs): @classmethod
return Port.objects\ def get_instance(cls, portid, *_args, **kwargs):
.select_related('machine_interface__domain__extension')\ return (cls.objects
.select_related('machine_interface__machine__switch')\ .select_related('machine_interface__domain__extension')
.select_related('room')\ .select_related('machine_interface__machine__switch')
.select_related('related')\ .select_related('room')
.prefetch_related('switch__interface_set__domain__extension')\ .select_related('related')
.get(pk=portid) .prefetch_related('switch__interface_set__domain__extension')
.get(pk=portid))
def make_port_related(self): def make_port_related(self):
""" Synchronise le port distant sur self""" """ Synchronise le port distant sur self"""
@ -363,18 +366,24 @@ class Port(AclMixin, RevMixin, models.Model):
cohérence""" cohérence"""
if hasattr(self, 'switch'): if hasattr(self, 'switch'):
if self.port > self.switch.number: if self.port > self.switch.number:
raise ValidationError("Ce port ne peut exister,\ raise ValidationError(
numero trop élevé") "Ce port ne peut exister, numero trop élevé"
if self.room and self.machine_interface or self.room and\ )
self.related or self.machine_interface and self.related: if (self.room and self.machine_interface or
raise ValidationError("Chambre, interface et related_port sont\ self.room and self.related or
mutuellement exclusifs") self.machine_interface and self.related):
raise ValidationError(
"Chambre, interface et related_port sont mutuellement "
"exclusifs"
)
if self.related == self: if self.related == self:
raise ValidationError("On ne peut relier un port à lui même") raise ValidationError("On ne peut relier un port à lui même")
if self.related and not self.related.related: if self.related and not self.related.related:
if self.related.machine_interface or self.related.room: if self.related.machine_interface or self.related.room:
raise ValidationError("Le port relié est déjà occupé, veuillez\ raise ValidationError(
le libérer avant de créer une relation") "Le port relié est déjà occupé, veuillez le libérer "
"avant de créer une relation"
)
else: else:
self.make_port_related() self.make_port_related()
elif hasattr(self, 'related_port'): elif hasattr(self, 'related_port'):
@ -402,18 +411,18 @@ class Room(AclMixin, RevMixin, models.Model):
@receiver(post_save, sender=AccessPoint) @receiver(post_save, sender=AccessPoint)
def ap_post_save(sender, **kwargs): def ap_post_save(**_kwargs):
"""Regeneration des noms des bornes vers le controleur""" """Regeneration des noms des bornes vers le controleur"""
regen('unifi-ap-names') regen('unifi-ap-names')
@receiver(post_delete, sender=AccessPoint) @receiver(post_delete, sender=AccessPoint)
def ap_post_delete(sender, **kwargs): def ap_post_delete(**_kwargs):
"""Regeneration des noms des bornes vers le controleur""" """Regeneration des noms des bornes vers le controleur"""
regen('unifi-ap-names') regen('unifi-ap-names')
@receiver(post_delete, sender=Stack) @receiver(post_delete, sender=Stack)
def stack_post_delete(sender, **kwargs): def stack_post_delete(**_kwargs):
"""Vide les id des switches membres d'une stack supprimée""" """Vide les id des switches membres d'une stack supprimée"""
Switch.objects.filter(stack=None).update(stack_member_id=None) Switch.objects.filter(stack=None).update(stack_member_id=None)

View file

@ -19,7 +19,10 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""topologie.tests
The tests for the Topologie module.
"""
from django.test import TestCase # from django.test import TestCase
# Create your tests here. # Create your tests here.

View file

@ -51,12 +51,10 @@ urlpatterns = [
url(r'^switch/(?P<switchid>[0-9]+)$', url(r'^switch/(?P<switchid>[0-9]+)$',
views.index_port, views.index_port,
name='index-port'), name='index-port'),
url( url(r'^history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
r'^history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
re2o.views.history, re2o.views.history,
name='history', name='history',
kwargs={'application':'topologie'}, kwargs={'application': 'topologie'}),
),
url(r'^edit_port/(?P<portid>[0-9]+)$', views.edit_port, name='edit-port'), url(r'^edit_port/(?P<portid>[0-9]+)$', views.edit_port, name='edit-port'),
url(r'^new_port/(?P<switchid>[0-9]+)$', views.new_port, name='new-port'), url(r'^new_port/(?P<switchid>[0-9]+)$', views.new_port, name='new-port'),
url(r'^del_port/(?P<portid>[0-9]+)$', views.del_port, name='del-port'), url(r'^del_port/(?P<portid>[0-9]+)$', views.del_port, name='del-port'),
@ -64,7 +62,9 @@ urlpatterns = [
views.edit_switch, views.edit_switch,
name='edit-switch'), name='edit-switch'),
url(r'^new_stack/$', views.new_stack, name='new-stack'), url(r'^new_stack/$', views.new_stack, name='new-stack'),
url(r'^index_physical_grouping/$', views.index_physical_grouping, name='index-physical-grouping'), url(r'^index_physical_grouping/$',
views.index_physical_grouping,
name='index-physical-grouping'),
url(r'^edit_stack/(?P<stackid>[0-9]+)$', url(r'^edit_stack/(?P<stackid>[0-9]+)$',
views.edit_stack, views.edit_stack,
name='edit-stack'), name='edit-stack'),
@ -73,16 +73,13 @@ urlpatterns = [
name='del-stack'), name='del-stack'),
url(r'^index_model_switch/$', url(r'^index_model_switch/$',
views.index_model_switch, views.index_model_switch,
name='index-model-switch' name='index-model-switch'),
),
url(r'^index_model_switch/$', url(r'^index_model_switch/$',
views.index_model_switch, views.index_model_switch,
name='index-model-switch' name='index-model-switch'),
),
url(r'^new_model_switch/$', url(r'^new_model_switch/$',
views.new_model_switch, views.new_model_switch,
name='new-model-switch' name='new-model-switch'),
),
url(r'^edit_model_switch/(?P<modelswitchid>[0-9]+)$', url(r'^edit_model_switch/(?P<modelswitchid>[0-9]+)$',
views.edit_model_switch, views.edit_model_switch,
name='edit-model-switch'), name='edit-model-switch'),
@ -91,8 +88,7 @@ urlpatterns = [
name='del-model-switch'), name='del-model-switch'),
url(r'^new_constructor_switch/$', url(r'^new_constructor_switch/$',
views.new_constructor_switch, views.new_constructor_switch,
name='new-constructor-switch' name='new-constructor-switch'),
),
url(r'^edit_constructor_switch/(?P<constructorswitchid>[0-9]+)$', url(r'^edit_constructor_switch/(?P<constructorswitchid>[0-9]+)$',
views.edit_constructor_switch, views.edit_constructor_switch,
name='edit-constructor-switch'), name='edit-constructor-switch'),
@ -101,8 +97,7 @@ urlpatterns = [
name='del-constructor-switch'), name='del-constructor-switch'),
url(r'^new_switch_bay/$', url(r'^new_switch_bay/$',
views.new_switch_bay, views.new_switch_bay,
name='new-switch-bay' name='new-switch-bay'),
),
url(r'^edit_switch_bay/(?P<switchbayid>[0-9]+)$', url(r'^edit_switch_bay/(?P<switchbayid>[0-9]+)$',
views.edit_switch_bay, views.edit_switch_bay,
name='edit-switch-bay'), name='edit-switch-bay'),
@ -111,8 +106,7 @@ urlpatterns = [
name='del-switch-bay'), name='del-switch-bay'),
url(r'^new_building/$', url(r'^new_building/$',
views.new_building, views.new_building,
name='new-building' name='new-building'),
),
url(r'^edit_building/(?P<buildingid>[0-9]+)$', url(r'^edit_building/(?P<buildingid>[0-9]+)$',
views.edit_building, views.edit_building,
name='edit-building'), name='edit-building'),

View file

@ -38,37 +38,12 @@ from __future__ import unicode_literals
from django.urls import reverse from django.urls import reverse
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required
from django.db import IntegrityError from django.db import IntegrityError
from django.db import transaction
from django.db.models import ProtectedError, Prefetch from django.db.models import ProtectedError, Prefetch
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.contrib.staticfiles.storage import staticfiles_storage from django.contrib.staticfiles.storage import staticfiles_storage
from topologie.models import (
Switch,
Port,
Room,
Stack,
ModelSwitch,
ConstructorSwitch,
AccessPoint,
SwitchBay,
Building
)
from topologie.forms import EditPortForm, NewSwitchForm, EditSwitchForm
from topologie.forms import (
AddPortForm,
EditRoomForm,
StackForm,
EditModelSwitchForm,
EditConstructorSwitchForm,
CreatePortsForm,
AddAccessPointForm,
EditAccessPointForm,
EditSwitchBayForm,
EditBuildingForm
)
from users.views import form from users.views import form
from re2o.utils import re2o_paginator, SortTable from re2o.utils import re2o_paginator, SortTable
from re2o.acl import ( from re2o.acl import (
@ -80,8 +55,6 @@ from re2o.acl import (
) )
from machines.forms import ( from machines.forms import (
DomainForm, DomainForm,
NewMachineForm,
EditMachineForm,
EditInterfaceForm, EditInterfaceForm,
AddInterfaceForm AddInterfaceForm
) )
@ -89,6 +62,33 @@ from machines.views import generate_ipv4_mbf_param
from machines.models import Interface from machines.models import Interface
from preferences.models import AssoOption, GeneralOption from preferences.models import AssoOption, GeneralOption
from .models import (
Switch,
Port,
Room,
Stack,
ModelSwitch,
ConstructorSwitch,
AccessPoint,
SwitchBay,
Building
)
from .forms import (
EditPortForm,
NewSwitchForm,
EditSwitchForm,
AddPortForm,
EditRoomForm,
StackForm,
EditModelSwitchForm,
EditConstructorSwitchForm,
CreatePortsForm,
AddAccessPointForm,
EditAccessPointForm,
EditSwitchBayForm,
EditBuildingForm
)
from subprocess import Popen,PIPE from subprocess import Popen,PIPE
@ -96,12 +96,14 @@ from subprocess import Popen,PIPE
@can_view_all(Switch) @can_view_all(Switch)
def index(request): def index(request):
""" Vue d'affichage de tous les swicthes""" """ Vue d'affichage de tous les swicthes"""
switch_list = Switch.objects\ switch_list = (Switch.objects
.prefetch_related(Prefetch( .prefetch_related(Prefetch(
'interface_set', 'interface_set',
queryset=Interface.objects.select_related('ipv4__ip_type__extension').select_related('domain__extension') queryset=(Interface.objects
))\ .select_related('ipv4__ip_type__extension')
.select_related('stack') .select_related('domain__extension'))
))
.select_related('stack'))
switch_list = SortTable.sort( switch_list = SortTable.sort(
switch_list, switch_list,
request.GET.get('col'), request.GET.get('col'),
@ -110,9 +112,11 @@ def index(request):
) )
pagination_number = GeneralOption.get_cached_value('pagination_number') pagination_number = GeneralOption.get_cached_value('pagination_number')
switch_list = re2o_paginator(request, switch_list, pagination_number) switch_list = re2o_paginator(request, switch_list, pagination_number)
return render(request, 'topologie/index.html', { return render(
'switch_list': switch_list request,
}) 'topologie/index.html',
{'switch_list': switch_list}
)
@login_required @login_required
@ -120,27 +124,33 @@ def index(request):
@can_view(Switch) @can_view(Switch)
def index_port(request, switch, switchid): def index_port(request, switch, switchid):
""" Affichage de l'ensemble des ports reliés à un switch particulier""" """ Affichage de l'ensemble des ports reliés à un switch particulier"""
port_list = Port.objects.filter(switch=switch)\ port_list = (Port.objects
.select_related('room')\ .filter(switch=switch)
.select_related('machine_interface__domain__extension')\ .select_related('room')
.select_related('machine_interface__machine__user')\ .select_related('machine_interface__domain__extension')
.select_related('related__switch')\ .select_related('machine_interface__machine__user')
.prefetch_related(Prefetch( .select_related('related__switch')
'related__switch__interface_set', .prefetch_related(Prefetch(
queryset=Interface.objects.select_related('domain__extension') 'related__switch__interface_set',
))\ queryset=(Interface.objects
.select_related('switch') .select_related('domain__extension'))
))
.select_related('switch'))
port_list = SortTable.sort( port_list = SortTable.sort(
port_list, port_list,
request.GET.get('col'), request.GET.get('col'),
request.GET.get('order'), request.GET.get('order'),
SortTable.TOPOLOGIE_INDEX_PORT SortTable.TOPOLOGIE_INDEX_PORT
) )
return render(request, 'topologie/index_p.html', { return render(
'port_list': port_list, request,
'id_switch': switchid, 'topologie/index_p.html',
'nom_switch': switch {
}) 'port_list': port_list,
'id_switch': switchid,
'nom_switch': switch
}
)
@login_required @login_required
@ -156,20 +166,24 @@ def index_room(request):
) )
pagination_number = GeneralOption.get_cached_value('pagination_number') pagination_number = GeneralOption.get_cached_value('pagination_number')
room_list = re2o_paginator(request, room_list, pagination_number) room_list = re2o_paginator(request, room_list, pagination_number)
return render(request, 'topologie/index_room.html', { return render(
'room_list': room_list request,
}) 'topologie/index_room.html',
{'room_list': room_list}
)
@login_required @login_required
@can_view_all(AccessPoint) @can_view_all(AccessPoint)
def index_ap(request): def index_ap(request):
""" Affichage de l'ensemble des bornes""" """ Affichage de l'ensemble des bornes"""
ap_list = AccessPoint.objects\ ap_list = (AccessPoint.objects
.prefetch_related(Prefetch( .prefetch_related(Prefetch(
'interface_set', 'interface_set',
queryset=Interface.objects.select_related('ipv4__ip_type__extension').select_related('domain__extension') queryset=(Interface.objects
)) .select_related('ipv4__ip_type__extension')
.select_related('domain__extension'))
)))
ap_list = SortTable.sort( ap_list = SortTable.sort(
ap_list, ap_list,
request.GET.get('col'), request.GET.get('col'),
@ -178,9 +192,11 @@ def index_ap(request):
) )
pagination_number = GeneralOption.get_cached_value('pagination_number') pagination_number = GeneralOption.get_cached_value('pagination_number')
ap_list = re2o_paginator(request, ap_list, pagination_number) ap_list = re2o_paginator(request, ap_list, pagination_number)
return render(request, 'topologie/index_ap.html', { return render(
'ap_list': ap_list request,
}) 'topologie/index_ap.html',
{'ap_list': ap_list}
)
@login_required @login_required
@ -189,8 +205,10 @@ def index_ap(request):
@can_view_all(SwitchBay) @can_view_all(SwitchBay)
def index_physical_grouping(request): def index_physical_grouping(request):
"""Affichage de la liste des stacks (affiche l'ensemble des switches)""" """Affichage de la liste des stacks (affiche l'ensemble des switches)"""
stack_list = Stack.objects\ stack_list = (Stack.objects
.prefetch_related('switch_set__interface_set__domain__extension') .prefetch_related(
'switch_set__interface_set__domain__extension'
))
building_list = Building.objects.all() building_list = Building.objects.all()
switch_bay_list = SwitchBay.objects.select_related('building') switch_bay_list = SwitchBay.objects.select_related('building')
stack_list = SortTable.sort( stack_list = SortTable.sort(
@ -211,11 +229,15 @@ def index_physical_grouping(request):
request.GET.get('order'), request.GET.get('order'),
SortTable.TOPOLOGIE_INDEX_SWITCH_BAY SortTable.TOPOLOGIE_INDEX_SWITCH_BAY
) )
return render(request, 'topologie/index_physical_grouping.html', { return render(
'stack_list': stack_list, request,
'switch_bay_list': switch_bay_list, 'topologie/index_physical_grouping.html',
'building_list' : building_list, {
}) 'stack_list': stack_list,
'switch_bay_list': switch_bay_list,
'building_list': building_list,
}
)
@login_required @login_required
@ -237,10 +259,14 @@ def index_model_switch(request):
request.GET.get('order'), request.GET.get('order'),
SortTable.TOPOLOGIE_INDEX_CONSTRUCTOR_SWITCH SortTable.TOPOLOGIE_INDEX_CONSTRUCTOR_SWITCH
) )
return render(request, 'topologie/index_model_switch.html', { return render(
'model_switch_list': model_switch_list, request,
'constructor_switch_list': constructor_switch_list, 'topologie/index_model_switch.html',
}) {
'model_switch_list': model_switch_list,
'constructor_switch_list': constructor_switch_list,
}
)
@login_required @login_required
@ -263,14 +289,17 @@ def new_port(request, switchid):
messages.error(request, "Ce port existe déjà") messages.error(request, "Ce port existe déjà")
return redirect(reverse( return redirect(reverse(
'topologie:index-port', 'topologie:index-port',
kwargs={'switchid':switchid} kwargs={'switchid': switchid}
)) ))
return form({'id_switch': switchid,'topoform': port, 'action_name' : 'Ajouter'}, 'topologie/topo.html', request) return form(
{'id_switch': switchid, 'topoform': port, 'action_name': 'Ajouter'},
'topologie/topo.html',
request)
@login_required @login_required
@can_edit(Port) @can_edit(Port)
def edit_port(request, port_object, portid): def edit_port(request, port_object, **_kwargs):
""" Edition d'un port. Permet de changer le switch parent et """ Edition d'un port. Permet de changer le switch parent et
l'affectation du port""" l'affectation du port"""
@ -282,25 +311,36 @@ def edit_port(request, port_object, portid):
return redirect(reverse( return redirect(reverse(
'topologie:index-port', 'topologie:index-port',
kwargs={'switchid': str(port_object.switch.id)} kwargs={'switchid': str(port_object.switch.id)}
)) ))
return form({'id_switch': str(port_object.switch.id), 'topoform': port, 'action_name' : 'Editer'}, 'topologie/topo.html', request) return form(
{
'id_switch': str(port_object.switch.id),
'topoform': port,
'action_name': 'Editer'
},
'topologie/topo.html',
request
)
@login_required @login_required
@can_delete(Port) @can_delete(Port)
def del_port(request, port, portid): def del_port(request, port, **_kwargs):
""" Supprime le port""" """ Supprime le port"""
if request.method == "POST": if request.method == "POST":
try: try:
port.delete() port.delete()
messages.success(request, "Le port a été détruit") messages.success(request, "Le port a été détruit")
except ProtectedError: except ProtectedError:
messages.error(request, "Le port %s est affecté à un autre objet,\ messages.error(
impossible de le supprimer" % port) request,
("Le port %s est affecté à un autre objet, impossible "
"de le supprimer" % port)
)
return redirect(reverse( return redirect(reverse(
'topologie:index-port', 'topologie:index-port',
kwargs={'switchid':str(port.switch.id)} kwargs={'switchid': str(port.switch.id)}
)) ))
return form({'objet': port}, 'topologie/delete.html', request) return form({'objet': port}, 'topologie/delete.html', request)
@ -312,39 +352,50 @@ def new_stack(request):
if stack.is_valid(): if stack.is_valid():
stack.save() stack.save()
messages.success(request, "Stack crée") messages.success(request, "Stack crée")
return form({'topoform': stack, 'action_name' : 'Créer'}, 'topologie/topo.html', request) return form(
{'topoform': stack, 'action_name': 'Créer'},
'topologie/topo.html',
request
)
@login_required @login_required
@can_edit(Stack) @can_edit(Stack)
def edit_stack(request, stack, stackid): def edit_stack(request, stack, **_kwargs):
"""Edition d'un stack (nombre de switches, nom...)""" """Edition d'un stack (nombre de switches, nom...)"""
stack = StackForm(request.POST or None, instance=stack) stack = StackForm(request.POST or None, instance=stack)
if stack.is_valid(): if stack.is_valid():
if stack.changed_data: if stack.changed_data:
stack.save() stack.save()
return redirect(reverse('topologie:index-physical-grouping')) return redirect(reverse('topologie:index-physical-grouping'))
return form({'topoform': stack, 'action_name' : 'Editer'}, 'topologie/topo.html', request) return form(
{'topoform': stack, 'action_name': 'Editer'},
'topologie/topo.html',
request
)
@login_required @login_required
@can_delete(Stack) @can_delete(Stack)
def del_stack(request, stack, stackid): def del_stack(request, stack, **_kwargs):
"""Supprime un stack""" """Supprime un stack"""
if request.method == "POST": if request.method == "POST":
try: try:
stack.delete() stack.delete()
messages.success(request, "La stack a eté détruite") messages.success(request, "La stack a eté détruite")
except ProtectedError: except ProtectedError:
messages.error(request, "La stack %s est affectée à un autre\ messages.error(
objet, impossible de la supprimer" % stack) request,
("La stack %s est affectée à un autre objet, impossible "
"de la supprimer" % stack)
)
return redirect(reverse('topologie:index-physical-grouping')) return redirect(reverse('topologie:index-physical-grouping'))
return form({'objet': stack}, 'topologie/delete.html', request) return form({'objet': stack}, 'topologie/delete.html', request)
@login_required @login_required
@can_edit(Stack) @can_edit(Stack)
def edit_switchs_stack(request, stack, stackid): def edit_switchs_stack(request, stack, **_kwargs):
"""Permet d'éditer la liste des switches dans une stack et l'ajouter""" """Permet d'éditer la liste des switches dans une stack et l'ajouter"""
if request.method == "POST": if request.method == "POST":
@ -375,30 +426,37 @@ def new_switch(request):
if switch.is_valid() and interface.is_valid(): if switch.is_valid() and interface.is_valid():
user = AssoOption.get_cached_value('utilisateur_asso') user = AssoOption.get_cached_value('utilisateur_asso')
if not user: if not user:
messages.error(request, "L'user association n'existe pas encore,\ messages.error(
veuillez le créer ou le linker dans preferences") request,
("L'user association n'existe pas encore, veuillez le "
"créer ou le linker dans preferences")
)
return redirect(reverse('topologie:index')) return redirect(reverse('topologie:index'))
new_switch = switch.save(commit=False) new_switch_obj = switch.save(commit=False)
new_switch.user = user new_switch_obj.user = user
new_interface_instance = interface.save(commit=False) new_interface_obj = interface.save(commit=False)
domain.instance.interface_parent = new_interface_instance domain.instance.interface_parent = new_interface_obj
if domain.is_valid(): if domain.is_valid():
new_domain_instance = domain.save(commit=False) new_domain_obj = domain.save(commit=False)
new_switch.save() new_switch_obj.save()
new_interface_instance.machine = new_switch new_interface_obj.machine = new_switch_obj
new_interface_instance.save() new_interface_obj.save()
new_domain_instance.interface_parent = new_interface_instance new_domain_obj.interface_parent = new_interface_obj
new_domain_instance.save() new_domain_obj.save()
messages.success(request, "Le switch a été créé") messages.success(request, "Le switch a été créé")
return redirect(reverse('topologie:index')) return redirect(reverse('topologie:index'))
i_mbf_param = generate_ipv4_mbf_param(interface, False) i_mbf_param = generate_ipv4_mbf_param(interface, False)
return form({ return form(
'topoform': interface, {
'machineform': switch, 'topoform': interface,
'domainform': domain, 'machineform': switch,
'i_mbf_param': i_mbf_param, 'domainform': domain,
'device' : 'switch', 'i_mbf_param': i_mbf_param,
}, 'topologie/topo_more.html', request) 'device': 'switch',
},
'topologie/topo_more.html',
request
)
@login_required @login_required
@ -433,9 +491,13 @@ def create_ports(request, switchid):
messages.error(request, ''.join(e)) messages.error(request, ''.join(e))
return redirect(reverse( return redirect(reverse(
'topologie:index-port', 'topologie:index-port',
kwargs={'switchid':switchid} kwargs={'switchid': switchid}
)) ))
return form({'id_switch': switchid, 'topoform': port_form}, 'topologie/switch.html', request) return form(
{'id_switch': switchid, 'topoform': port_form},
'topologie/switch.html',
request
)
@login_required @login_required
@ -459,26 +521,30 @@ def edit_switch(request, switch, switchid):
instance=switch.interface_set.first().domain instance=switch.interface_set.first().domain
) )
if switch_form.is_valid() and interface_form.is_valid(): if switch_form.is_valid() and interface_form.is_valid():
new_switch = switch_form.save(commit=False) new_switch_obj = switch_form.save(commit=False)
new_interface_instance = interface_form.save(commit=False) new_interface_obj = interface_form.save(commit=False)
new_domain = domain_form.save(commit=False) new_domain_obj = domain_form.save(commit=False)
if switch_form.changed_data: if switch_form.changed_data:
new_switch.save() new_switch_obj.save()
if interface_form.changed_data: if interface_form.changed_data:
new_interface_instance.save() new_interface_obj.save()
if domain_form.changed_data: if domain_form.changed_data:
new_domain.save() new_domain_obj.save()
messages.success(request, "Le switch a bien été modifié") messages.success(request, "Le switch a bien été modifié")
return redirect(reverse('topologie:index')) return redirect(reverse('topologie:index'))
i_mbf_param = generate_ipv4_mbf_param(interface_form, False ) i_mbf_param = generate_ipv4_mbf_param(interface_form, False)
return form({ return form(
'id_switch': switchid, {
'topoform': interface_form, 'id_switch': switchid,
'machineform': switch_form, 'topoform': interface_form,
'domainform': domain_form, 'machineform': switch_form,
'i_mbf_param': i_mbf_param, 'domainform': domain_form,
'device' : 'switch', 'i_mbf_param': i_mbf_param,
}, 'topologie/topo_more.html', request) 'device': 'switch',
},
'topologie/topo_more.html',
request
)
@login_required @login_required
@ -501,35 +567,42 @@ def new_ap(request):
if ap.is_valid() and interface.is_valid(): if ap.is_valid() and interface.is_valid():
user = AssoOption.get_cached_value('utilisateur_asso') user = AssoOption.get_cached_value('utilisateur_asso')
if not user: if not user:
messages.error(request, "L'user association n'existe pas encore,\ messages.error(
veuillez le créer ou le linker dans preferences") request,
("L'user association n'existe pas encore, veuillez le "
"créer ou le linker dans preferences")
)
return redirect(reverse('topologie:index')) return redirect(reverse('topologie:index'))
new_ap = ap.save(commit=False) new_ap_obj = ap.save(commit=False)
new_ap.user = user new_ap_obj.user = user
new_interface = interface.save(commit=False) new_interface_obj = interface.save(commit=False)
domain.instance.interface_parent = new_interface domain.instance.interface_parent = new_interface_obj
if domain.is_valid(): if domain.is_valid():
new_domain_instance = domain.save(commit=False) new_domain_obj = domain.save(commit=False)
new_ap.save() new_ap_obj.save()
new_interface.machine = new_ap new_interface_obj.machine = new_ap_obj
new_interface.save() new_interface_obj.save()
new_domain_instance.interface_parent = new_interface new_domain_obj.interface_parent = new_interface_obj
new_domain_instance.save() new_domain_obj.save()
messages.success(request, "La borne a été créé") messages.success(request, "La borne a été créé")
return redirect(reverse('topologie:index-ap')) return redirect(reverse('topologie:index-ap'))
i_mbf_param = generate_ipv4_mbf_param(interface, False) i_mbf_param = generate_ipv4_mbf_param(interface, False)
return form({ return form(
'topoform': interface, {
'machineform': ap, 'topoform': interface,
'domainform': domain, 'machineform': ap,
'i_mbf_param': i_mbf_param, 'domainform': domain,
'device' : 'wifi ap', 'i_mbf_param': i_mbf_param,
}, 'topologie/topo_more.html', request) 'device': 'wifi ap',
},
'topologie/topo_more.html',
request
)
@login_required @login_required
@can_edit(AccessPoint) @can_edit(AccessPoint)
def edit_ap(request, ap, accesspointid): def edit_ap(request, ap, **_kwargs):
""" Edition d'un switch. Permet de chambre nombre de ports, """ Edition d'un switch. Permet de chambre nombre de ports,
place dans le stack, interface et machine associée""" place dans le stack, interface et machine associée"""
interface_form = EditInterfaceForm( interface_form = EditInterfaceForm(
@ -549,29 +622,36 @@ def edit_ap(request, ap, accesspointid):
if ap_form.is_valid() and interface_form.is_valid(): if ap_form.is_valid() and interface_form.is_valid():
user = AssoOption.get_cached_value('utilisateur_asso') user = AssoOption.get_cached_value('utilisateur_asso')
if not user: if not user:
messages.error(request, "L'user association n'existe pas encore,\ messages.error(
veuillez le créer ou le linker dans preferences") request,
("L'user association n'existe pas encore, veuillez le "
"créer ou le linker dans preferences")
)
return redirect(reverse('topologie:index-ap')) return redirect(reverse('topologie:index-ap'))
new_ap = ap_form.save(commit=False) new_ap_obj = ap_form.save(commit=False)
new_interface = interface_form.save(commit=False) new_interface_obj = interface_form.save(commit=False)
new_domain = domain_form.save(commit=False) new_domain_obj = domain_form.save(commit=False)
if ap_form.changed_data: if ap_form.changed_data:
new_ap.save() new_ap_obj.save()
if interface_form.changed_data: if interface_form.changed_data:
new_interface.save() new_interface_obj.save()
if domain_form.changed_data: if domain_form.changed_data:
new_domain.save() new_domain_obj.save()
messages.success(request, "La borne a été modifiée") messages.success(request, "La borne a été modifiée")
return redirect(reverse('topologie:index-ap')) return redirect(reverse('topologie:index-ap'))
i_mbf_param = generate_ipv4_mbf_param(interface_form, False ) i_mbf_param = generate_ipv4_mbf_param(interface_form, False)
return form({ return form(
'topoform': interface_form, {
'machineform': ap_form, 'topoform': interface_form,
'domainform': domain_form, 'machineform': ap_form,
'i_mbf_param': i_mbf_param, 'domainform': domain_form,
'device' : 'wifi ap', 'i_mbf_param': i_mbf_param,
}, 'topologie/topo_more.html', request) 'device': 'wifi ap',
},
'topologie/topo_more.html',
request
)
@login_required @login_required
@can_create(Room) @can_create(Room)
@ -582,12 +662,16 @@ def new_room(request):
room.save() room.save()
messages.success(request, "La chambre a été créé") messages.success(request, "La chambre a été créé")
return redirect(reverse('topologie:index-room')) return redirect(reverse('topologie:index-room'))
return form({'topoform': room, 'action_name' : 'Ajouter'}, 'topologie/topo.html', request) return form(
{'topoform': room, 'action_name': 'Ajouter'},
'topologie/topo.html',
request
)
@login_required @login_required
@can_edit(Room) @can_edit(Room)
def edit_room(request, room, roomid): def edit_room(request, room, **_kwargs):
""" Edition numero et details de la chambre""" """ Edition numero et details de la chambre"""
room = EditRoomForm(request.POST or None, instance=room) room = EditRoomForm(request.POST or None, instance=room)
if room.is_valid(): if room.is_valid():
@ -595,25 +679,33 @@ def edit_room(request, room, roomid):
room.save() room.save()
messages.success(request, "La chambre a bien été modifiée") messages.success(request, "La chambre a bien été modifiée")
return redirect(reverse('topologie:index-room')) return redirect(reverse('topologie:index-room'))
return form({'topoform': room, 'action_name' : 'Editer'}, 'topologie/topo.html', request) return form(
{'topoform': room, 'action_name': 'Editer'},
'topologie/topo.html',
request
)
@login_required @login_required
@can_delete(Room) @can_delete(Room)
def del_room(request, room, roomid): def del_room(request, room, **_kwargs):
""" Suppression d'un chambre""" """ Suppression d'un chambre"""
if request.method == "POST": if request.method == "POST":
try: try:
room.delete() room.delete()
messages.success(request, "La chambre/prise a été détruite") messages.success(request, "La chambre/prise a été détruite")
except ProtectedError: except ProtectedError:
messages.error(request, "La chambre %s est affectée à un autre objet,\ messages.error(
impossible de la supprimer (switch ou user)" % room) request,
("La chambre %s est affectée à un autre objet, impossible "
"de la supprimer (switch ou user)" % room)
)
return redirect(reverse('topologie:index-room')) return redirect(reverse('topologie:index-room'))
return form({ return form(
'objet': room, {'objet': room, 'objet_name': 'Chambre'},
'objet_name': 'Chambre' 'topologie/delete.html',
}, 'topologie/delete.html', request) request
)
@login_required @login_required
@ -625,39 +717,54 @@ def new_model_switch(request):
model_switch.save() model_switch.save()
messages.success(request, "Le modèle a été créé") messages.success(request, "Le modèle a été créé")
return redirect(reverse('topologie:index-model-switch')) return redirect(reverse('topologie:index-model-switch'))
return form({'topoform': model_switch, 'action_name' : 'Ajouter'}, 'topologie/topo.html', request) return form(
{'topoform': model_switch, 'action_name': 'Ajouter'},
'topologie/topo.html',
request
)
@login_required @login_required
@can_edit(ModelSwitch) @can_edit(ModelSwitch)
def edit_model_switch(request, model_switch, modelswitchid): def edit_model_switch(request, model_switch, **_kwargs):
""" Edition d'un modèle de switch""" """ Edition d'un modèle de switch"""
model_switch = EditModelSwitchForm(request.POST or None, instance=model_switch) model_switch = EditModelSwitchForm(
request.POST or None,
instance=model_switch
)
if model_switch.is_valid(): if model_switch.is_valid():
if model_switch.changed_data: if model_switch.changed_data:
model_switch.save() model_switch.save()
messages.success(request, "Le modèle a bien été modifié") messages.success(request, "Le modèle a bien été modifié")
return redirect(reverse('topologie:index-model-switch')) return redirect(reverse('topologie:index-model-switch'))
return form({'topoform': model_switch, 'action_name' : 'Editer'}, 'topologie/topo.html', request) return form(
{'topoform': model_switch, 'action_name': 'Editer'},
'topologie/topo.html',
request
)
@login_required @login_required
@can_delete(ModelSwitch) @can_delete(ModelSwitch)
def del_model_switch(request, model_switch, modelswitchid): def del_model_switch(request, model_switch, **_kwargs):
""" Suppression d'un modèle de switch""" """ Suppression d'un modèle de switch"""
if request.method == "POST": if request.method == "POST":
try: try:
model_switch.delete() model_switch.delete()
messages.success(request, "Le modèle a été détruit") messages.success(request, "Le modèle a été détruit")
except ProtectedError: except ProtectedError:
messages.error(request, "Le modèle %s est affectée à un autre objet,\ messages.error(
impossible de la supprimer (switch ou user)" % model_switch) request,
("Le modèle %s est affectée à un autre objet, impossible "
"de la supprimer (switch ou user)" % model_switch)
)
return redirect(reverse('topologie:index-model-switch')) return redirect(reverse('topologie:index-model-switch'))
return form({ return form(
'objet': model_switch, {'objet': model_switch, 'objet_name': 'Modèle de switch'},
'objet_name': 'Modèle de switch' 'topologie/delete.html',
}, 'topologie/delete.html', request) request
)
@login_required @login_required
@ -669,12 +776,16 @@ def new_switch_bay(request):
switch_bay.save() switch_bay.save()
messages.success(request, "La baie a été créé") messages.success(request, "La baie a été créé")
return redirect(reverse('topologie:index-physical-grouping')) return redirect(reverse('topologie:index-physical-grouping'))
return form({'topoform': switch_bay, 'action_name' : 'Ajouter'}, 'topologie/topo.html', request) return form(
{'topoform': switch_bay, 'action_name': 'Ajouter'},
'topologie/topo.html',
request
)
@login_required @login_required
@can_edit(SwitchBay) @can_edit(SwitchBay)
def edit_switch_bay(request, switch_bay, switchbayid): def edit_switch_bay(request, switch_bay, **_kwargs):
""" Edition d'une baie de switch""" """ Edition d'une baie de switch"""
switch_bay = EditSwitchBayForm(request.POST or None, instance=switch_bay) switch_bay = EditSwitchBayForm(request.POST or None, instance=switch_bay)
if switch_bay.is_valid(): if switch_bay.is_valid():
@ -682,25 +793,33 @@ def edit_switch_bay(request, switch_bay, switchbayid):
switch_bay.save() switch_bay.save()
messages.success(request, "Le switch a bien été modifié") messages.success(request, "Le switch a bien été modifié")
return redirect(reverse('topologie:index-physical-grouping')) return redirect(reverse('topologie:index-physical-grouping'))
return form({'topoform': switch_bay, 'action_name' : 'Editer'}, 'topologie/topo.html', request) return form(
{'topoform': switch_bay, 'action_name': 'Editer'},
'topologie/topo.html',
request
)
@login_required @login_required
@can_delete(SwitchBay) @can_delete(SwitchBay)
def del_switch_bay(request, switch_bay, switchbayid): def del_switch_bay(request, switch_bay, **_kwargs):
""" Suppression d'une baie de switch""" """ Suppression d'une baie de switch"""
if request.method == "POST": if request.method == "POST":
try: try:
switch_bay.delete() switch_bay.delete()
messages.success(request, "La baie a été détruite") messages.success(request, "La baie a été détruite")
except ProtectedError: except ProtectedError:
messages.error(request, "La baie %s est affecté à un autre objet,\ messages.error(
impossible de la supprimer (switch ou user)" % switch_bay) request,
("La baie %s est affecté à un autre objet, impossible "
"de la supprimer (switch ou user)" % switch_bay)
)
return redirect(reverse('topologie:index-physical-grouping')) return redirect(reverse('topologie:index-physical-grouping'))
return form({ return form(
'objet': switch_bay, {'objet': switch_bay, 'objet_name': 'Baie de switch'},
'objet_name': 'Baie de switch' 'topologie/delete.html',
}, 'topologie/delete.html', request) request
)
@login_required @login_required
@ -712,12 +831,16 @@ def new_building(request):
building.save() building.save()
messages.success(request, "Le batiment a été créé") messages.success(request, "Le batiment a été créé")
return redirect(reverse('topologie:index-physical-grouping')) return redirect(reverse('topologie:index-physical-grouping'))
return form({'topoform': building, 'action_name' : 'Ajouter'}, 'topologie/topo.html', request) return form(
{'topoform': building, 'action_name': 'Ajouter'},
'topologie/topo.html',
request
)
@login_required @login_required
@can_edit(Building) @can_edit(Building)
def edit_building(request, building, buildingid): def edit_building(request, building, **_kwargs):
""" Edition d'un batiment""" """ Edition d'un batiment"""
building = EditBuildingForm(request.POST or None, instance=building) building = EditBuildingForm(request.POST or None, instance=building)
if building.is_valid(): if building.is_valid():
@ -725,25 +848,33 @@ def edit_building(request, building, buildingid):
building.save() building.save()
messages.success(request, "Le batiment a bien été modifié") messages.success(request, "Le batiment a bien été modifié")
return redirect(reverse('topologie:index-physical-grouping')) return redirect(reverse('topologie:index-physical-grouping'))
return form({'topoform': building, 'action_name' : 'Editer'}, 'topologie/topo.html', request) return form(
{'topoform': building, 'action_name': 'Editer'},
'topologie/topo.html',
request
)
@login_required @login_required
@can_delete(Building) @can_delete(Building)
def del_building(request, building, buildingid): def del_building(request, building, **_kwargs):
""" Suppression d'un batiment""" """ Suppression d'un batiment"""
if request.method == "POST": if request.method == "POST":
try: try:
building.delete() building.delete()
messages.success(request, "La batiment a été détruit") messages.success(request, "La batiment a été détruit")
except ProtectedError: except ProtectedError:
messages.error(request, "Le batiment %s est affecté à un autre objet,\ messages.error(
impossible de la supprimer (switch ou user)" % building) request,
("Le batiment %s est affecté à un autre objet, impossible "
"de la supprimer (switch ou user)" % building)
)
return redirect(reverse('topologie:index-physical-grouping')) return redirect(reverse('topologie:index-physical-grouping'))
return form({ return form(
'objet': building, {'objet': building, 'objet_name': 'Bâtiment'},
'objet_name': 'Bâtiment' 'topologie/delete.html',
}, 'topologie/delete.html', request) request
)
@login_required @login_required
@ -755,34 +886,48 @@ def new_constructor_switch(request):
constructor_switch.save() constructor_switch.save()
messages.success(request, "Le constructeur a été créé") messages.success(request, "Le constructeur a été créé")
return redirect(reverse('topologie:index-model-switch')) return redirect(reverse('topologie:index-model-switch'))
return form({'topoform': constructor_switch, 'action_name' : 'Ajouter'}, 'topologie/topo.html', request) return form(
{'topoform': constructor_switch, 'action_name': 'Ajouter'},
'topologie/topo.html',
request
)
@login_required @login_required
@can_edit(ConstructorSwitch) @can_edit(ConstructorSwitch)
def edit_constructor_switch(request, constructor_switch, constructorswitchid): def edit_constructor_switch(request, constructor_switch, **_kwargs):
""" Edition d'un constructeur de switch""" """ Edition d'un constructeur de switch"""
constructor_switch = EditConstructorSwitchForm(request.POST or None, instance=constructor_switch) constructor_switch = EditConstructorSwitchForm(
request.POST or None,
instance=constructor_switch
)
if constructor_switch.is_valid(): if constructor_switch.is_valid():
if constructor_switch.changed_data: if constructor_switch.changed_data:
constructor_switch.save() constructor_switch.save()
messages.success(request, "Le modèle a bien été modifié") messages.success(request, "Le modèle a bien été modifié")
return redirect(reverse('topologie:index-model-switch')) return redirect(reverse('topologie:index-model-switch'))
return form({'topoform': constructor_switch, 'action_name' : 'Editer'}, 'topologie/topo.html', request) return form(
{'topoform': constructor_switch, 'action_name': 'Editer'},
'topologie/topo.html',
request
)
@login_required @login_required
@can_delete(ConstructorSwitch) @can_delete(ConstructorSwitch)
def del_constructor_switch(request, constructor_switch, constructorswitchid): def del_constructor_switch(request, constructor_switch, **_kwargs):
""" Suppression d'un constructeur de switch""" """ Suppression d'un constructeur de switch"""
if request.method == "POST": if request.method == "POST":
try: try:
constructor_switch.delete() constructor_switch.delete()
messages.success(request, "Le constructeur a été détruit") messages.success(request, "Le constructeur a été détruit")
except ProtectedError: except ProtectedError:
messages.error(request, "Le constructeur %s est affecté à un autre objet,\ messages.error(
impossible de la supprimer (switch ou user)" % constructor_switch) request,
("Le constructeur %s est affecté à un autre objet, impossible "
"de la supprimer (switch ou user)" % constructor_switch)
)
return redirect(reverse('topologie:index-model-switch')) return redirect(reverse('topologie:index-model-switch'))
return form({ return form({
'objet': constructor_switch, 'objet': constructor_switch,
@ -883,4 +1028,4 @@ def recursive_switchs(port_start, switch_before, lignes,detected):
links.append("\"{}\"".format(sw.id)) links.append("\"{}\"".format(sw.id))
lignes.append(links[0]+" -> "+links[1]) lignes.append(links[0]+" -> "+links[1])
lignes, detected = recursive_switchs(port.related, port_start.switch, lignes, detected) lignes, detected = recursive_switchs(port.related, port_start.switch, lignes, detected)
return (lignes, detected) return (lignes, detected)

View file

@ -20,5 +20,12 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""users
The app managing everything related to the users such as personal
informations or the right groups.
This is probably the most central app. It is strongly linked with
all the other because a user has devices (machines), a cotisation
(cotisations), a room (topologie)
"""
from .acl import * from .acl import *

View file

@ -26,6 +26,7 @@
Here are defined some functions to check acl on the application. Here are defined some functions to check acl on the application.
""" """
def can_view(user): def can_view(user):
"""Check if an user can view the application. """Check if an user can view the application.

View file

@ -56,19 +56,6 @@ from .forms import (
) )
class UserAdmin(admin.ModelAdmin):
"""Administration d'un user"""
list_display = (
'surname',
'pseudo',
'email',
'school',
'shell',
'state'
)
search_fields = ('surname', 'pseudo')
class LdapUserAdmin(admin.ModelAdmin): class LdapUserAdmin(admin.ModelAdmin):
"""Administration du ldapuser""" """Administration du ldapuser"""
list_display = ('name', 'uidNumber', 'login_shell') list_display = ('name', 'uidNumber', 'login_shell')
@ -143,7 +130,8 @@ class UserAdmin(VersionAdmin, BaseUserAdmin):
'is_admin', 'is_admin',
'shell' 'shell'
) )
list_display = ('pseudo',) # Need to reset the settings from BaseUserAdmin
# They are using fields we don't use like 'is_staff'
list_filter = () list_filter = ()
fieldsets = ( fieldsets = (
(None, {'fields': ('pseudo', 'password')}), (None, {'fields': ('pseudo', 'password')}),
@ -175,7 +163,7 @@ class UserAdmin(VersionAdmin, BaseUserAdmin):
} }
), ),
) )
search_fields = ('pseudo',) search_fields = ('pseudo', 'surname')
ordering = ('pseudo',) ordering = ('pseudo',)
filter_horizontal = () filter_horizontal = ()

View file

@ -41,6 +41,10 @@ from django.utils import timezone
from django.contrib.auth.models import Group, Permission from django.contrib.auth.models import Group, Permission
from preferences.models import OptionalUser from preferences.models import OptionalUser
from re2o.utils import remove_user_room
from re2o.mixins import FormRevMixin
from re2o.field_permissions import FieldPermissionFormMixin
from .models import ( from .models import (
User, User,
ServiceUser, ServiceUser,
@ -52,9 +56,6 @@ from .models import (
Adherent, Adherent,
Club Club
) )
from re2o.utils import remove_user_room
from re2o.mixins import FormRevMixin
from re2o.field_permissions import FieldPermissionFormMixin
class PassForm(FormRevMixin, FieldPermissionFormMixin, forms.ModelForm): class PassForm(FormRevMixin, FieldPermissionFormMixin, forms.ModelForm):
@ -89,12 +90,16 @@ class PassForm(FormRevMixin, FieldPermissionFormMixin, forms.ModelForm):
password1 = self.cleaned_data.get("passwd1") password1 = self.cleaned_data.get("passwd1")
password2 = self.cleaned_data.get("passwd2") password2 = self.cleaned_data.get("passwd2")
if password1 and password2 and password1 != password2: if password1 and password2 and password1 != password2:
raise forms.ValidationError("Les 2 nouveaux mots de passe sont différents") raise forms.ValidationError(
"Les 2 nouveaux mots de passe sont différents"
)
return password2 return password2
def clean_selfpasswd(self): def clean_selfpasswd(self):
"""Verifie si il y a lieu que le mdp self est correct""" """Verifie si il y a lieu que le mdp self est correct"""
if not self.instance.check_password(self.cleaned_data.get("selfpasswd")): if not self.instance.check_password(
self.cleaned_data.get("selfpasswd")
):
raise forms.ValidationError("Le mot de passe actuel est incorrect") raise forms.ValidationError("Le mot de passe actuel est incorrect")
return return
@ -386,7 +391,11 @@ class ClubAdminandMembersForm(FormRevMixin, ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(ClubAdminandMembersForm, self).__init__(*args, prefix=prefix, **kwargs) super(ClubAdminandMembersForm, self).__init__(
*args,
prefix=prefix,
**kwargs
)
class PasswordForm(FormRevMixin, ModelForm): class PasswordForm(FormRevMixin, ModelForm):

View file

@ -19,12 +19,14 @@
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import os, pwd import os
import pwd
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from users.forms import PassForm from users.forms import PassForm
from re2o.script_utils import get_user, get_system_user, form_cli from re2o.script_utils import get_user, get_system_user, form_cli
class Command(BaseCommand): class Command(BaseCommand):
help = "Changer le mot de passe d'un utilisateur" help = "Changer le mot de passe d'un utilisateur"
@ -42,6 +44,13 @@ class Command(BaseCommand):
if not ok: if not ok:
raise CommandError(msg) raise CommandError(msg)
self.stdout.write("Changement du mot de passe de %s" % target_user.pseudo) self.stdout.write(
"Changement du mot de passe de %s" % target_user.pseudo
)
form_cli(PassForm,current_user,"Changement du mot de passe",instance=target_user) form_cli(
PassForm,
current_user,
"Changement du mot de passe",
instance=target_user
)

View file

@ -19,7 +19,9 @@
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import os, sys, pwd import os
import sys
import pwd
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.db import transaction from django.db import transaction
@ -28,6 +30,7 @@ from reversion import revisions as reversion
from users.models import User, ListShell from users.models import User, ListShell
from re2o.script_utils import get_user, get_system_user from re2o.script_utils import get_user, get_system_user
class Command(BaseCommand): class Command(BaseCommand):
help = 'Change the default shell of a user' help = 'Change the default shell of a user'
@ -36,13 +39,13 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
current_username = get_system_user() current_username = get_system_user()
current_user = get_user(current_username) current_user = get_user(current_username)
target_username = options["target_username"] or current_username target_username = options["target_username"] or current_username
target_user = get_user(target_username) target_user = get_user(target_username)
#L'utilisateur n'a pas le droit de changer le shell # L'utilisateur n'a pas le droit de changer le shell
ok, msg = target_user.can_change_shell(current_user) ok, msg = target_user.can_change_shell(current_user)
if not ok: if not ok:
raise CommandError(msg) raise CommandError(msg)
@ -52,9 +55,16 @@ class Command(BaseCommand):
current_shell = "inconnu" current_shell = "inconnu"
if target_user.shell: if target_user.shell:
current_shell = target_user.shell.get_pretty_name() current_shell = target_user.shell.get_pretty_name()
self.stdout.write("Choisissez un shell pour l'utilisateur %s (le shell actuel est %s) :" % (target_user.pseudo, current_shell)) self.stdout.write(
"Choisissez un shell pour l'utilisateur %s (le shell actuel est "
"%s) :" % (target_user.pseudo, current_shell)
)
for shell in shells: for shell in shells:
self.stdout.write("%d - %s (%s)" % (shell.id, shell.get_pretty_name(), shell.shell)) self.stdout.write("%d - %s (%s)" % (
shell.id,
shell.get_pretty_name(),
shell.shell
))
shell_id = input("Entrez un nombre : ") shell_id = input("Entrez un nombre : ")
try: try:
@ -72,4 +82,7 @@ class Command(BaseCommand):
reversion.set_user(current_user) reversion.set_user(current_user)
reversion.set_comment("Shell modifié") reversion.set_comment("Shell modifié")
self.stdout.write(self.style.SUCCESS("Shell modifié. La modification peut prendre quelques minutes pour s'appliquer.")) self.stdout.write(self.style.SUCCESS(
"Shell modifié. La modification peut prendre quelques minutes "
"pour s'appliquer."
))

View file

@ -4,7 +4,6 @@
# quelques clics. # quelques clics.
# #
# Copyright © 2018 Benjamin Graillot # Copyright © 2018 Benjamin Graillot
#
# Copyright © 2013-2015 Raphaël-David Lasseri <lasseri@crans.org> # Copyright © 2013-2015 Raphaël-David Lasseri <lasseri@crans.org>
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
@ -32,7 +31,8 @@ from users.models import User
# Une liste d'expressions régulières à chercher dans les logs. # Une liste d'expressions régulières à chercher dans les logs.
# Elles doivent contenir un groupe 'date' et un groupe 'user'. # Elles doivent contenir un groupe 'date' et un groupe 'user'.
# Pour le CAS on prend comme entrée cat ~/cas.log | grep -B 2 -A 2 "ACTION: AUTHENTICATION_SUCCESS"| grep 'WHEN\|WHO'|sed 'N;s/\n/ /' # Pour le CAS on prend comme entrée
# cat ~/cas.log | grep -B 2 -A 2 "ACTION: AUTHENTICATION_SUCCESS"| grep 'WHEN\|WHO'|sed 'N;s/\n/ /'
COMPILED_REGEX = map(re.compile, [ COMPILED_REGEX = map(re.compile, [
r'^(?P<date>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}).*(?:'r'dovecot.*Login: user=<|'r'sshd.*Accepted.*for 'r')(?P<user>[^ >]+).*$', r'^(?P<date>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}).*(?:'r'dovecot.*Login: user=<|'r'sshd.*Accepted.*for 'r')(?P<user>[^ >]+).*$',
r'^(?P<date>.*) LOGIN INFO User logged in : (?P<user>.*)', r'^(?P<date>.*) LOGIN INFO User logged in : (?P<user>.*)',
@ -48,8 +48,10 @@ DATE_FORMATS = [
"%a %b %d CEST %H:%M:%S%Y" "%a %b %d CEST %H:%M:%S%Y"
] ]
class Command(BaseCommand): class Command(BaseCommand):
help = 'Update the time of the latest connection for users by matching stdin against a set of regular expressions' help = ('Update the time of the latest connection for users by matching '
'stdin against a set of regular expressions')
def handle(self, *args, **options): def handle(self, *args, **options):
@ -65,7 +67,9 @@ class Command(BaseCommand):
for i, regex in enumerate(COMPILED_REGEX): for i, regex in enumerate(COMPILED_REGEX):
m = regex.match(line) m = regex.match(line)
if m: if m:
parsed_log[m.group('user')] = make_aware(datetime.strptime(m.group('date'), DATE_FORMATS[i])) parsed_log[m.group('user')] = make_aware(
datetime.strptime(m.group('date'), DATE_FORMATS[i])
)
return parsed_log return parsed_log
parsed_log = parse_logs(sys.stdin) parsed_log = parse_logs(sys.stdin)

View file

@ -7,8 +7,11 @@ from users.models import User
UTC = pytz.timezone('UTC') UTC = pytz.timezone('UTC')
# TODO : remove of finsihed this because currently it should
# be failing! Who commited that ?!
class Command(BaseCommand): class Command(BaseCommand):
commands = ['email_remainder',] commands = ['email_remainder']
args = '[command]' args = '[command]'
help = 'Send email remainders' help = 'Send email remainders'
@ -27,6 +30,6 @@ class Command(BaseCommand):
elif remaining.days == 1: elif remaining.days == 1:
last_day_reminder() last_day_reminder()
def month_reminder(): def month_reminder():
pass pass

View file

@ -20,6 +20,7 @@ from django.core.management.base import BaseCommand, CommandError
from users.models import User from users.models import User
class Command(BaseCommand): class Command(BaseCommand):
help = 'Synchronise le ldap à partir du sql. A utiliser dans un cron' help = 'Synchronise le ldap à partir du sql. A utiliser dans un cron'
@ -37,4 +38,3 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
for usr in User.objects.all(): for usr in User.objects.all():
usr.ldap_sync(mac_refresh=options['full']) usr.ldap_sync(mac_refresh=options['full'])

View file

@ -37,6 +37,6 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='user', model_name='user',
name='uid_number', name='uid_number',
field=models.IntegerField(unique=True, default=users.models.User.auto_uid), field=models.IntegerField(unique=True, default=users.models.get_fresh_user_uid),
), ),
] ]

View file

@ -32,6 +32,6 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='user', model_name='user',
name='uid_number', name='uid_number',
field=models.PositiveIntegerField(default=users.models.User.auto_uid, unique=True), field=models.PositiveIntegerField(default=users.models.get_fresh_user_uid, unique=True),
), ),
] ]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-04-15 10:52
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0070_auto_20180324_1906'),
]
operations = [
migrations.AlterField(
model_name='listright',
name='unix_name',
field=models.CharField(max_length=255, unique=True, validators=[django.core.validators.RegexValidator('^[a-z]+$', message='Les groupes unix ne peuvent contenir que des lettres minuscules')]),
),
]

View file

@ -73,7 +73,7 @@ from reversion import revisions as reversion
import ldapdb.models import ldapdb.models
import ldapdb.models.fields import ldapdb.models.fields
from re2o.settings import RIGHTS_LINK, LDAP, GID_RANGES, UID_RANGES from re2o.settings import LDAP, GID_RANGES, UID_RANGES
from re2o.login import hashNT from re2o.login import hashNT
from re2o.field_permissions import FieldPermissionModelMixin from re2o.field_permissions import FieldPermissionModelMixin
from re2o.mixins import AclMixin, RevMixin from re2o.mixins import AclMixin, RevMixin
@ -153,7 +153,7 @@ class UserManager(BaseUserManager):
user.set_password(password) user.set_password(password)
if su: if su:
user.is_superuser=True user.is_superuser = True
user.save(using=self._db) user.save(using=self._db)
return user return user
@ -171,7 +171,9 @@ class UserManager(BaseUserManager):
""" """
return self._create_user(pseudo, surname, email, password, True) return self._create_user(pseudo, surname, email, password, True)
class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMixin, AclMixin):
class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
PermissionsMixin, AclMixin):
""" Definition de l'utilisateur de base. """ Definition de l'utilisateur de base.
Champs principaux : name, surnname, pseudo, email, room, password Champs principaux : name, surnname, pseudo, email, room, password
Herite du django BaseUser et du système d'auth django""" Herite du django BaseUser et du système d'auth django"""
@ -185,10 +187,6 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
(2, 'STATE_ARCHIVE'), (2, 'STATE_ARCHIVE'),
) )
def auto_uid():
"""Renvoie un uid libre"""
return get_fresh_user_uid()
surname = models.CharField(max_length=255) surname = models.CharField(max_length=255)
pseudo = models.CharField( pseudo = models.CharField(
max_length=32, max_length=32,
@ -218,8 +216,15 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
state = models.IntegerField(choices=STATES, default=STATE_ACTIVE) state = models.IntegerField(choices=STATES, default=STATE_ACTIVE)
registered = models.DateTimeField(auto_now_add=True) registered = models.DateTimeField(auto_now_add=True)
telephone = models.CharField(max_length=15, blank=True, null=True) telephone = models.CharField(max_length=15, blank=True, null=True)
uid_number = models.PositiveIntegerField(default=auto_uid, unique=True) uid_number = models.PositiveIntegerField(
rezo_rez_uid = models.PositiveIntegerField(unique=True, blank=True, null=True) default=get_fresh_user_uid,
unique=True
)
rezo_rez_uid = models.PositiveIntegerField(
unique=True,
blank=True,
null=True
)
USERNAME_FIELD = 'pseudo' USERNAME_FIELD = 'pseudo'
REQUIRED_FIELDS = ['surname', 'email'] REQUIRED_FIELDS = ['surname', 'email']
@ -228,13 +233,18 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
class Meta: class Meta:
permissions = ( permissions = (
("change_user_password", "Peut changer le mot de passe d'un user"), ("change_user_password",
"Peut changer le mot de passe d'un user"),
("change_user_state", "Peut éditer l'etat d'un user"), ("change_user_state", "Peut éditer l'etat d'un user"),
("change_user_force", "Peut forcer un déménagement"), ("change_user_force", "Peut forcer un déménagement"),
("change_user_shell", "Peut éditer le shell d'un user"), ("change_user_shell", "Peut éditer le shell d'un user"),
("change_user_groups", "Peut éditer les groupes d'un user ! Permission critique"), ("change_user_groups",
("change_all_users", "Peut éditer tous les users, y compris ceux dotés de droits. Superdroit"), "Peut éditer les groupes d'un user ! Permission critique"),
("view_user", "Peut voir un objet user quelquonque"), ("change_all_users",
"Peut éditer tous les users, y compris ceux dotés de droits. "
"Superdroit"),
("view_user",
"Peut voir un objet user quelquonque"),
) )
@cached_property @cached_property
@ -267,10 +277,14 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
@cached_property @cached_property
def is_class_club(self): def is_class_club(self):
""" Returns True if the object is a Club (subclassing User) """
# TODO : change to isinstance (cleaner)
return hasattr(self, 'club') return hasattr(self, 'club')
@cached_property @cached_property
def is_class_adherent(self): def is_class_adherent(self):
""" Returns True if the object is a Adherent (subclassing User) """
# TODO : change to isinstance (cleaner)
return hasattr(self, 'adherent') return hasattr(self, 'adherent')
@property @property
@ -286,7 +300,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
@property @property
def is_admin(self): def is_admin(self):
""" Renvoie si l'user est admin""" """ Renvoie si l'user est admin"""
admin,_ = Group.objects.get_or_create(name="admin") admin, _ = Group.objects.get_or_create(name="admin")
return self.is_superuser or admin in self.groups.all() return self.is_superuser or admin in self.groups.all()
def get_full_name(self): def get_full_name(self):
@ -393,8 +407,9 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
def has_access(self): def has_access(self):
""" Renvoie si un utilisateur a accès à internet """ """ Renvoie si un utilisateur a accès à internet """
return self.state == User.STATE_ACTIVE\ return (self.state == User.STATE_ACTIVE and
and not self.is_ban() and (self.is_connected() or self.is_whitelisted()) not self.is_ban() and
(self.is_connected() or self.is_whitelisted()))
def end_access(self): def end_access(self):
""" Renvoie la date de fin normale d'accès (adhésion ou whiteliste)""" """ Renvoie la date de fin normale d'accès (adhésion ou whiteliste)"""
@ -480,7 +495,8 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
self.assign_ips() self.assign_ips()
self.state = User.STATE_ACTIVE self.state = User.STATE_ACTIVE
def ldap_sync(self, base=True, access_refresh=True, mac_refresh=True, group_refresh=False): def ldap_sync(self, base=True, access_refresh=True, mac_refresh=True,
group_refresh=False):
""" Synchronisation du ldap. Synchronise dans le ldap les attributs de """ Synchronisation du ldap. Synchronise dans le ldap les attributs de
self self
Options : base : synchronise tous les attributs de base - nom, prenom, Options : base : synchronise tous les attributs de base - nom, prenom,
@ -573,12 +589,15 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
'asso_mail': AssoOption.get_cached_value('contact'), 'asso_mail': AssoOption.get_cached_value('contact'),
'site_name': GeneralOption.get_cached_value('site_name'), 'site_name': GeneralOption.get_cached_value('site_name'),
'url': request.build_absolute_uri( 'url': request.build_absolute_uri(
reverse('users:process', kwargs={'token': req.token})), reverse('users:process', kwargs={'token': req.token})
'expire_in': str(GeneralOption.get_cached_value('req_expire_hrs')) + ' heures', ),
} 'expire_in': str(
GeneralOption.get_cached_value('req_expire_hrs')
) + ' heures',
}
send_mail( send_mail(
'Changement de mot de passe du %(name)s / Password\ 'Changement de mot de passe du %(name)s / Password renewal for '
renewal for %(name)s' % {'name': AssoOption.get_cached_value('name')}, '%(name)s' % {'name': AssoOption.get_cached_value('name')},
template.render(context), template.render(context),
GeneralOption.get_cached_value('email_from'), GeneralOption.get_cached_value('email_from'),
[req.user.email], [req.user.email],
@ -590,7 +609,9 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
""" Fonction appellée par freeradius. Enregistre la mac pour """ Fonction appellée par freeradius. Enregistre la mac pour
une machine inconnue sur le compte de l'user""" une machine inconnue sur le compte de l'user"""
all_interfaces = self.user_interfaces(active=False) all_interfaces = self.user_interfaces(active=False)
if all_interfaces.count() > OptionalMachine.get_cached_value('max_lambdauser_interfaces'): if all_interfaces.count() > OptionalMachine.get_cached_value(
'max_lambdauser_interfaces'
):
return False, "Maximum de machines enregistrees atteinte" return False, "Maximum de machines enregistrees atteinte"
if not nas_type: if not nas_type:
return False, "Re2o ne sait pas à quel machinetype affecter cette\ return False, "Re2o ne sait pas à quel machinetype affecter cette\
@ -625,9 +646,9 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
template = loader.get_template('users/email_auto_newmachine') template = loader.get_template('users/email_auto_newmachine')
context = Context({ context = Context({
'nom': self.get_full_name(), 'nom': self.get_full_name(),
'mac_address' : interface.mac_address, 'mac_address': interface.mac_address,
'asso_name': AssoOption.get_cached_value('name'), 'asso_name': AssoOption.get_cached_value('name'),
'interface_name' : interface.domain, 'interface_name': interface.domain,
'asso_email': AssoOption.get_cached_value('contact'), 'asso_email': AssoOption.get_cached_value('contact'),
'pseudo': self.pseudo, 'pseudo': self.pseudo,
}) })
@ -668,19 +689,19 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
num += 1 num += 1
return composed_pseudo(num) return composed_pseudo(num)
def can_edit(self, user_request, *args, **kwargs): def can_edit(self, user_request, *_args, **_kwargs):
"""Check if an user can edit an user object. """Check if a user can edit a user object.
:param self: The user which is to be edited. :param self: The user which is to be edited.
:param user_request: The user who requests to edit self. :param user_request: The user who requests to edit self.
:return: a message and a boolean which is True if self is a club and :return: a message and a boolean which is True if self is a club and
user_request one of its member, or if user_request is self, or if user_request one of its member, or if user_request is self, or if
user_request has the 'cableur' right. user_request has the 'cableur' right.
""" """
if self.is_class_club and user_request.is_class_adherent: if self.is_class_club and user_request.is_class_adherent:
if self == user_request or \ if (self == user_request or
user_request.has_perm('users.change_user') or \ user_request.has_perm('users.change_user') or
user_request.adherent in self.club.administrators.all(): user_request.adherent in self.club.administrators.all()):
return True, None return True, None
else: else:
return False, u"Vous n'avez pas le droit d'éditer ce club" return False, u"Vous n'avez pas le droit d'éditer ce club"
@ -691,98 +712,162 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
return True, None return True, None
elif user_request.has_perm('users.change_user'): elif user_request.has_perm('users.change_user'):
if self.groups.filter(listright__critical=True): if self.groups.filter(listright__critical=True):
return False, u"Utilisateurs avec droits critiques, ne peut etre édité" return False, (u"Utilisateurs avec droits critiques, ne "
"peut etre édité")
elif self == AssoOption.get_cached_value('utilisateur_asso'): elif self == AssoOption.get_cached_value('utilisateur_asso'):
return False, u"Impossible d'éditer l'utilisateur asso sans droit change_all_users" return False, (u"Impossible d'éditer l'utilisateur asso "
"sans droit change_all_users")
else: else:
return True, None return True, None
elif user_request.has_perm('users.change_all_users'): elif user_request.has_perm('users.change_all_users'):
return True, None return True, None
else: else:
return False, u"Vous ne pouvez éditer un autre utilisateur que vous même" return False, (u"Vous ne pouvez éditer un autre utilisateur "
"que vous même")
def can_change_password(self, user_request, *args, **kwargs): def can_change_password(self, user_request, *_args, **_kwargs):
"""Check if a user can change a user's password
:param self: The user which is to be edited
:param user_request: The user who request to edit self
:returns: a message and a boolean which is True if self is a club
and user_request one of it's admins, or if user_request is self,
or if user_request has the right to change other's password
"""
if self.is_class_club and user_request.is_class_adherent: if self.is_class_club and user_request.is_class_adherent:
if self == user_request or \ if (self == user_request or
user_request.has_perm('users.change_user_password') or \ user_request.has_perm('users.change_user_password') or
user_request.adherent in self.club.administrators.all(): user_request.adherent in self.club.administrators.all()):
return True, None return True, None
else: else:
return False, u"Vous n'avez pas le droit d'éditer ce club" return False, u"Vous n'avez pas le droit d'éditer ce club"
else: else:
if self == user_request or \ if (self == user_request or
user_request.has_perm('users.change_user_groups'): user_request.has_perm('users.change_user_groups')):
# Peut éditer les groupes d'un user, c'est un privilège élevé, True # Peut éditer les groupes d'un user,
# c'est un privilège élevé, True
return True, None return True, None
elif user_request.has_perm('users.change_user') and not self.groups.all(): elif (user_request.has_perm('users.change_user') and
not self.groups.all()):
return True, None return True, None
else: else:
return False, u"Vous ne pouvez éditer un autre utilisateur que vous même" return False, (u"Vous ne pouvez éditer un autre utilisateur "
"que vous même")
def check_selfpasswd(self, user_request, *args, **kwargs): def check_selfpasswd(self, user_request, *_args, **_kwargs):
""" Returns (True, None) if user_request is self, else returns
(False, None)
"""
return user_request == self, None return user_request == self, None
@staticmethod @staticmethod
def can_change_state(user_request, *args, **kwargs): def can_change_state(user_request, *_args, **_kwargs):
return user_request.has_perm('users.change_user_state'), "Droit requis pour changer l'état" """ Check if a user can change a state
:param user_request: The user who request
:returns: a message and a boolean which is True if the user has
the right to change a state
"""
return (
user_request.has_perm('users.change_user_state'),
"Droit requis pour changer l'état"
)
@staticmethod @staticmethod
def can_change_shell(user_request, *args, **kwargs): def can_change_shell(user_request, *_args, **_kwargs):
return user_request.has_perm('users.change_user_shell'), "Droit requis pour changer le shell" """ Check if a user can change a shell
:param user_request: The user who request
:returns: a message and a boolean which is True if the user has
the right to change a shell
"""
return (
user_request.has_perm('users.change_user_shell'),
"Droit requis pour changer le shell"
)
@staticmethod @staticmethod
def can_change_force(user_request, *args, **kwargs): def can_change_force(user_request, *_args, **_kwargs):
return user_request.has_perm('users.change_user_force'), "Droit requis pour forcer le déménagement" """ Check if a user can change a force
:param user_request: The user who request
:returns: a message and a boolean which is True if the user has
the right to change a force
"""
return (
user_request.has_perm('users.change_user_force'),
"Droit requis pour forcer le déménagement"
)
@staticmethod @staticmethod
def can_change_groups(user_request, *args, **kwargs): def can_change_groups(user_request, *_args, **_kwargs):
return user_request.has_perm('users.change_user_groups'), "Droit requis pour éditer les groupes de l'user" """ Check if a user can change a group
def can_view(self, user_request, *args, **kwargs): :param user_request: The user who request
:returns: a message and a boolean which is True if the user has
the right to change a group
"""
return (
user_request.has_perm('users.change_user_groups'),
"Droit requis pour éditer les groupes de l'user"
)
def can_view(self, user_request, *_args, **_kwargs):
"""Check if an user can view an user object. """Check if an user can view an user object.
:param self: The targeted user. :param self: The targeted user.
:param user_request: The user who ask for viewing the target. :param user_request: The user who ask for viewing the target.
:return: A boolean telling if the acces is granted and an explanation :return: A boolean telling if the acces is granted and an explanation
text text
""" """
if self.is_class_club and user_request.is_class_adherent: if self.is_class_club and user_request.is_class_adherent:
if self == user_request or \ if (self == user_request or
user_request.has_perm('users.view_user') or \ user_request.has_perm('users.view_user') or
user_request.adherent in self.club.administrators.all() or \ user_request.adherent in self.club.administrators.all() or
user_request.adherent in self.club.members.all(): user_request.adherent in self.club.members.all()):
return True, None return True, None
else: else:
return False, u"Vous n'avez pas le droit de voir ce club" return False, u"Vous n'avez pas le droit de voir ce club"
else: else:
if self == user_request or user_request.has_perm('users.view_user'): if (self == user_request or
user_request.has_perm('users.view_user')):
return True, None return True, None
else: else:
return False, u"Vous ne pouvez voir un autre utilisateur que vous même" return False, (u"Vous ne pouvez voir un autre utilisateur "
"que vous même")
def can_view_all(user_request, *args, **kwargs): @staticmethod
def can_view_all(user_request, *_args, **_kwargs):
"""Check if an user can access to the list of every user objects """Check if an user can access to the list of every user objects
:param user_request: The user who wants to view the list. :param user_request: The user who wants to view the list.
:return: True if the user can view the list and an explanation message. :return: True if the user can view the list and an explanation
message.
""" """
return user_request.has_perm('users.view_user'), u"Vous n'avez pas accès à la liste des utilisateurs." return (
user_request.has_perm('users.view_user'),
u"Vous n'avez pas accès à la liste des utilisateurs."
)
def can_delete(self, user_request, *args, **kwargs): def can_delete(self, user_request, *_args, **_kwargs):
"""Check if an user can delete an user object. """Check if an user can delete an user object.
:param self: The user who is to be deleted. :param self: The user who is to be deleted.
:param user_request: The user who requests deletion. :param user_request: The user who requests deletion.
:return: True if user_request has the right 'bureau', and a message. :return: True if user_request has the right 'bureau', and a
message.
""" """
return user_request.has_perm('users.delete_user'), u"Vous ne pouvez pas supprimer cet utilisateur." return (
user_request.has_perm('users.delete_user'),
u"Vous ne pouvez pas supprimer cet utilisateur."
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(User, self).__init__(*args, **kwargs) super(User, self).__init__(*args, **kwargs)
self.field_permissions = { self.field_permissions = {
'shell' : self.can_change_shell, 'shell': self.can_change_shell,
'force' : self.can_change_force, 'force': self.can_change_force,
'selfpasswd' : self.check_selfpasswd, 'selfpasswd': self.check_selfpasswd,
} }
def __str__(self): def __str__(self):
@ -790,6 +875,8 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
class Adherent(User): class Adherent(User):
""" A class representing a member (it's a user with special
informations) """
PRETTY_NAME = "Adhérents" PRETTY_NAME = "Adhérents"
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
room = models.OneToOneField( room = models.OneToOneField(
@ -799,32 +886,40 @@ class Adherent(User):
null=True null=True
) )
def get_instance(adherentid, *args, **kwargs): @classmethod
def get_instance(cls, adherentid, *_args, **_kwargs):
"""Try to find an instance of `Adherent` with the given id. """Try to find an instance of `Adherent` with the given id.
:param adherentid: The id of the adherent we are looking for. :param adherentid: The id of the adherent we are looking for.
:return: An adherent. :return: An adherent.
""" """
return Adherent.objects.get(pk=adherentid) return cls.objects.get(pk=adherentid)
def can_create(user_request, *args, **kwargs): @staticmethod
def can_create(user_request, *_args, **_kwargs):
"""Check if an user can create an user object. """Check if an user can create an user object.
:param user_request: The user who wants to create a user object. :param user_request: The user who wants to create a user object.
:return: a message and a boolean which is True if the user can create :return: a message and a boolean which is True if the user can create
an user or if the `options.all_can_create` is set. a user or if the `options.all_can_create` is set.
""" """
if(not user_request.is_authenticated and not OptionalUser.get_cached_value('self_adhesion')): if (not user_request.is_authenticated and
not OptionalUser.get_cached_value('self_adhesion')):
return False, None return False, None
else: else:
if(OptionalUser.get_cached_value('all_can_create_adherent') or OptionalUser.get_cached_value('self_adhesion')): if (OptionalUser.get_cached_value('all_can_create_adherent') or
OptionalUser.get_cached_value('self_adhesion')):
return True, None return True, None
else: else:
return user_request.has_perm('users.add_user'), u"Vous n'avez pas le\ return (
droit de créer un utilisateur" user_request.has_perm('users.add_user'),
u"Vous n'avez pas le droit de créer un utilisateur"
)
class Club(User): class Club(User):
""" A class representing a club (it is considered as a user
with special informations) """
PRETTY_NAME = "Clubs" PRETTY_NAME = "Clubs"
room = models.ForeignKey( room = models.ForeignKey(
'topologie.Room', 'topologie.Room',
@ -843,15 +938,16 @@ class Club(User):
related_name='club_members' related_name='club_members'
) )
mailing = models.BooleanField( mailing = models.BooleanField(
default = False default=False
) )
def can_create(user_request, *args, **kwargs): @staticmethod
def can_create(user_request, *_args, **_kwargs):
"""Check if an user can create an user object. """Check if an user can create an user object.
:param user_request: The user who wants to create a user object. :param user_request: The user who wants to create a user object.
:return: a message and a boolean which is True if the user can create :return: a message and a boolean which is True if the user can create
an user or if the `options.all_can_create` is set. an user or if the `options.all_can_create` is set.
""" """
if not user_request.is_authenticated: if not user_request.is_authenticated:
return False, None return False, None
@ -859,54 +955,68 @@ class Club(User):
if OptionalUser.get_cached_value('all_can_create_club'): if OptionalUser.get_cached_value('all_can_create_club'):
return True, None return True, None
else: else:
return user_request.has_perm('users.add_user'), u"Vous n'avez pas le\ return (
droit de créer un club" user_request.has_perm('users.add_user'),
u"Vous n'avez pas le droit de créer un club"
)
def can_view_all(user_request, *args, **kwargs): @staticmethod
def can_view_all(user_request, *_args, **_kwargs):
"""Check if an user can access to the list of every user objects """Check if an user can access to the list of every user objects
:param user_request: The user who wants to view the list. :param user_request: The user who wants to view the list.
:return: True if the user can view the list and an explanation message. :return: True if the user can view the list and an explanation
message.
""" """
if user_request.has_perm('users.view_user'): if user_request.has_perm('users.view_user'):
return True, None return True, None
if hasattr(user_request,'is_class_adherent') and user_request.is_class_adherent: if (hasattr(user_request, 'is_class_adherent') and
if user_request.adherent.club_administrator.all() or user_request.adherent.club_members.all(): user_request.is_class_adherent):
if (user_request.adherent.club_administrator.all() or
user_request.adherent.club_members.all()):
return True, None return True, None
return False, u"Vous n'avez pas accès à la liste des utilisateurs." return False, u"Vous n'avez pas accès à la liste des utilisateurs."
def get_instance(clubid, *args, **kwargs): @classmethod
def get_instance(cls, clubid, *_args, **_kwargs):
"""Try to find an instance of `Club` with the given id. """Try to find an instance of `Club` with the given id.
:param clubid: The id of the adherent we are looking for. :param clubid: The id of the adherent we are looking for.
:return: A club. :return: A club.
""" """
return Club.objects.get(pk=clubid) return cls.objects.get(pk=clubid)
@receiver(post_save, sender=Adherent) @receiver(post_save, sender=Adherent)
@receiver(post_save, sender=Club) @receiver(post_save, sender=Club)
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def user_post_save(sender, **kwargs): def user_post_save(**kwargs):
""" Synchronisation post_save : envoie le mail de bienvenue si creation """ Synchronisation post_save : envoie le mail de bienvenue si creation
Synchronise le ldap""" Synchronise le ldap"""
is_created = kwargs['created'] # is_created = kwargs['created']
user = kwargs['instance'] user = kwargs['instance']
#if is_created: # TODO : remove if unnecessary
#user.notif_inscription() # if is_created:
user.ldap_sync(base=True, access_refresh=True, mac_refresh=False, group_refresh=True) # user.notif_inscription()
user.ldap_sync(
base=True,
access_refresh=True,
mac_refresh=False,
group_refresh=True
)
regen('mailing') regen('mailing')
@receiver(post_delete, sender=Adherent) @receiver(post_delete, sender=Adherent)
@receiver(post_delete, sender=Club) @receiver(post_delete, sender=Club)
@receiver(post_delete, sender=User) @receiver(post_delete, sender=User)
def user_post_delete(sender, **kwargs): def user_post_delete(**kwargs):
"""Post delete d'un user, on supprime son instance ldap""" """Post delete d'un user, on supprime son instance ldap"""
user = kwargs['instance'] user = kwargs['instance']
user.ldap_del() user.ldap_del()
regen('mailing') regen('mailing')
class ServiceUser(RevMixin, AclMixin, AbstractBaseUser): class ServiceUser(RevMixin, AclMixin, AbstractBaseUser):
""" Classe des users daemons, règle leurs accès au ldap""" """ Classe des users daemons, règle leurs accès au ldap"""
readonly = 'readonly' readonly = 'readonly'
@ -943,6 +1053,14 @@ class ServiceUser(RevMixin, AclMixin, AbstractBaseUser):
("view_serviceuser", "Peut voir un objet serviceuser"), ("view_serviceuser", "Peut voir un objet serviceuser"),
) )
def get_full_name(self):
""" Renvoie le nom complet du serviceUser formaté nom/prénom"""
return "ServiceUser <{name}>".format(name=self.pseudo)
def get_short_name(self):
""" Renvoie seulement le nom"""
return self.pseudo
def ldap_sync(self): def ldap_sync(self):
""" Synchronisation du ServiceUser dans sa version ldap""" """ Synchronisation du ServiceUser dans sa version ldap"""
try: try:
@ -977,15 +1095,16 @@ class ServiceUser(RevMixin, AclMixin, AbstractBaseUser):
def __str__(self): def __str__(self):
return self.pseudo return self.pseudo
@receiver(post_save, sender=ServiceUser) @receiver(post_save, sender=ServiceUser)
def service_user_post_save(sender, **kwargs): def service_user_post_save(**kwargs):
""" Synchronise un service user ldap après modification django""" """ Synchronise un service user ldap après modification django"""
service_user = kwargs['instance'] service_user = kwargs['instance']
service_user.ldap_sync() service_user.ldap_sync()
@receiver(post_delete, sender=ServiceUser) @receiver(post_delete, sender=ServiceUser)
def service_user_post_delete(sender, **kwargs): def service_user_post_delete(**kwargs):
""" Supprime un service user ldap après suppression django""" """ Supprime un service user ldap après suppression django"""
service_user = kwargs['instance'] service_user = kwargs['instance']
service_user.ldap_del() service_user.ldap_del()
@ -1019,8 +1138,8 @@ class ListRight(RevMixin, AclMixin, Group):
unique=True, unique=True,
validators=[RegexValidator( validators=[RegexValidator(
'^[a-z]+$', '^[a-z]+$',
message="Les groupes unix ne peuvent contenir\ message=("Les groupes unix ne peuvent contenir que des lettres "
que des lettres minuscules" "minuscules")
)] )]
) )
gid = models.PositiveIntegerField(unique=True, null=True) gid = models.PositiveIntegerField(unique=True, null=True)
@ -1060,14 +1179,14 @@ class ListRight(RevMixin, AclMixin, Group):
@receiver(post_save, sender=ListRight) @receiver(post_save, sender=ListRight)
def listright_post_save(sender, **kwargs): def listright_post_save(**kwargs):
""" Synchronise le droit ldap quand il est modifié""" """ Synchronise le droit ldap quand il est modifié"""
right = kwargs['instance'] right = kwargs['instance']
right.ldap_sync() right.ldap_sync()
@receiver(post_delete, sender=ListRight) @receiver(post_delete, sender=ListRight)
def listright_post_delete(sender, **kwargs): def listright_post_delete(**kwargs):
"""Suppression d'un groupe ldap après suppression coté django""" """Suppression d'un groupe ldap après suppression coté django"""
right = kwargs['instance'] right = kwargs['instance']
right.ldap_del() right.ldap_del()
@ -1140,7 +1259,7 @@ class Ban(RevMixin, AclMixin, models.Model):
"""Ce ban est-il actif?""" """Ce ban est-il actif?"""
return self.date_end > timezone.now() return self.date_end > timezone.now()
def can_view(self, user_request, *args, **kwargs): def can_view(self, user_request, *_args, **_kwargs):
"""Check if an user can view a Ban object. """Check if an user can view a Ban object.
:param self: The targeted object. :param self: The targeted object.
@ -1148,10 +1267,10 @@ class Ban(RevMixin, AclMixin, models.Model):
:return: A boolean telling if the acces is granted and an explanation :return: A boolean telling if the acces is granted and an explanation
text text
""" """
if not user_request.has_perm('users.view_ban') and\ if (not user_request.has_perm('users.view_ban') and
self.user != user_request: self.user != user_request):
return False, u"Vous n'avez pas le droit de voir les bannissements\ return False, (u"Vous n'avez pas le droit de voir les "
autre que les vôtres" "bannissements autre que les vôtres")
else: else:
return True, None return True, None
@ -1160,7 +1279,7 @@ class Ban(RevMixin, AclMixin, models.Model):
@receiver(post_save, sender=Ban) @receiver(post_save, sender=Ban)
def ban_post_save(sender, **kwargs): def ban_post_save(**kwargs):
""" Regeneration de tous les services après modification d'un ban""" """ Regeneration de tous les services après modification d'un ban"""
ban = kwargs['instance'] ban = kwargs['instance']
is_created = kwargs['created'] is_created = kwargs['created']
@ -1177,7 +1296,7 @@ def ban_post_save(sender, **kwargs):
@receiver(post_delete, sender=Ban) @receiver(post_delete, sender=Ban)
def ban_post_delete(sender, **kwargs): def ban_post_delete(**kwargs):
""" Regen de tous les services après suppression d'un ban""" """ Regen de tous les services après suppression d'un ban"""
user = kwargs['instance'].user user = kwargs['instance'].user
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False) user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
@ -1203,9 +1322,10 @@ class Whitelist(RevMixin, AclMixin, models.Model):
) )
def is_active(self): def is_active(self):
""" Is this whitelisting active ? """
return self.date_end > timezone.now() return self.date_end > timezone.now()
def can_view(self, user_request, *args, **kwargs): def can_view(self, user_request, *_args, **_kwargs):
"""Check if an user can view a Whitelist object. """Check if an user can view a Whitelist object.
:param self: The targeted object. :param self: The targeted object.
@ -1213,10 +1333,10 @@ class Whitelist(RevMixin, AclMixin, models.Model):
:return: A boolean telling if the acces is granted and an explanation :return: A boolean telling if the acces is granted and an explanation
text text
""" """
if not user_request.has_perm('users.view_whitelist') and\ if (not user_request.has_perm('users.view_whitelist') and
self.user != user_request: self.user != user_request):
return False, u"Vous n'avez pas le droit de voir les accès\ return False, (u"Vous n'avez pas le droit de voir les accès "
gracieux autre que les vôtres" "gracieux autre que les vôtres")
else: else:
return True, None return True, None
@ -1225,7 +1345,7 @@ class Whitelist(RevMixin, AclMixin, models.Model):
@receiver(post_save, sender=Whitelist) @receiver(post_save, sender=Whitelist)
def whitelist_post_save(sender, **kwargs): def whitelist_post_save(**kwargs):
"""Après modification d'une whitelist, on synchronise les services """Après modification d'une whitelist, on synchronise les services
et on lui permet d'avoir internet""" et on lui permet d'avoir internet"""
whitelist = kwargs['instance'] whitelist = kwargs['instance']
@ -1242,7 +1362,7 @@ def whitelist_post_save(sender, **kwargs):
@receiver(post_delete, sender=Whitelist) @receiver(post_delete, sender=Whitelist)
def whitelist_post_delete(sender, **kwargs): def whitelist_post_delete(**kwargs):
"""Après suppression d'une whitelist, on supprime l'accès internet """Après suppression d'une whitelist, on supprime l'accès internet
en forçant la régénration""" en forçant la régénration"""
user = kwargs['instance'].user user = kwargs['instance'].user
@ -1270,8 +1390,12 @@ class Request(models.Model):
def save(self): def save(self):
if not self.expires_at: if not self.expires_at:
self.expires_at = timezone.now() \ self.expires_at = (timezone.now() +
+ datetime.timedelta(hours=GeneralOption.get_cached_value('req_expire_hrs')) datetime.timedelta(
hours=GeneralOption.get_cached_value(
'req_expire_hrs'
)
))
if not self.token: if not self.token:
self.token = str(uuid.uuid4()).replace('-', '') # remove hyphens self.token = str(uuid.uuid4()).replace('-', '') # remove hyphens
super(Request, self).save() super(Request, self).save()
@ -1375,7 +1499,10 @@ class LdapUserGroup(ldapdb.models.Model):
# attributes # attributes
gid = ldapdb.models.fields.IntegerField(db_column='gidNumber') gid = ldapdb.models.fields.IntegerField(db_column='gidNumber')
members = ldapdb.models.fields.ListField(db_column='memberUid', blank=True) members = ldapdb.models.fields.ListField(
db_column='memberUid',
blank=True
)
name = ldapdb.models.fields.CharField( name = ldapdb.models.fields.CharField(
db_column='cn', db_column='cn',
max_length=200, max_length=200,

View file

@ -20,19 +20,30 @@
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#Maël Kervella # Maël Kervella
"""users.serializers
Serializers for the User app
"""
from rest_framework import serializers from rest_framework import serializers
from users.models import Club, Adherent from users.models import Club, Adherent
class MailingSerializer(serializers.ModelSerializer): class MailingSerializer(serializers.ModelSerializer):
""" Serializer to build Mailing objects """
name = serializers.CharField(source='pseudo') name = serializers.CharField(source='pseudo')
class Meta: class Meta:
model = Club model = Club
fields = ('name',) fields = ('name',)
class MailingMemberSerializer(serializers.ModelSerializer): class MailingMemberSerializer(serializers.ModelSerializer):
""" Serializer fot the Adherent objects (who belong to a
Mailing) """
class Meta: class Meta:
model = Adherent model = Adherent
fields = ('email',) fields = ('email',)

View file

@ -82,8 +82,17 @@ non adhérent</span>{% endif %} et votre connexion est {% if users.has_access %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped"> <table class="table table-striped">
<tr> <tr>
<th>Prénom</th> {% if users.is_class_club %}
<th>Mailing</th>
{% if users.club.mailing %}
<td>{{ users.pseudo }}(-admin)</td>
{% else %}
<td>Mailing désactivée</td>
{% endif %}
{% else %}
<th>Prénom</th>
<td>{{ users.name }}</td> <td>{{ users.name }}</td>
{% endif %}
<th>Nom</th> <th>Nom</th>
<td>{{ users.surname }}</td> <td>{{ users.surname }}</td>
</tr> </tr>

View file

@ -19,7 +19,10 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""users.tests
The tests for the Users module.
"""
from django.test import TestCase # from django.test import TestCase
# Create your tests here. # Create your tests here.

View file

@ -34,109 +34,79 @@ urlpatterns = [
url(r'^new_user/$', views.new_user, name='new-user'), url(r'^new_user/$', views.new_user, name='new-user'),
url(r'^new_club/$', views.new_club, name='new-club'), url(r'^new_club/$', views.new_club, name='new-club'),
url(r'^edit_info/(?P<userid>[0-9]+)$', views.edit_info, name='edit-info'), url(r'^edit_info/(?P<userid>[0-9]+)$', views.edit_info, name='edit-info'),
url( url(r'^edit_club_admin_members/(?P<clubid>[0-9]+)$',
r'^edit_club_admin_members/(?P<clubid>[0-9]+)$',
views.edit_club_admin_members, views.edit_club_admin_members,
name='edit-club-admin-members' name='edit-club-admin-members'),
),
url(r'^state/(?P<userid>[0-9]+)$', views.state, name='state'), url(r'^state/(?P<userid>[0-9]+)$', views.state, name='state'),
url(r'^groups/(?P<userid>[0-9]+)$', views.groups, name='groups'), url(r'^groups/(?P<userid>[0-9]+)$', views.groups, name='groups'),
url(r'^password/(?P<userid>[0-9]+)$', views.password, name='password'), url(r'^password/(?P<userid>[0-9]+)$', views.password, name='password'),
url(r'^del_group/(?P<userid>[0-9]+)/(?P<listrightid>[0-9]+)$', views.del_group, name='del-group'), url(r'^del_group/(?P<userid>[0-9]+)/(?P<listrightid>[0-9]+)$',
views.del_group,
name='del-group'),
url(r'^new_serviceuser/$', views.new_serviceuser, name='new-serviceuser'), url(r'^new_serviceuser/$', views.new_serviceuser, name='new-serviceuser'),
url( url(r'^edit_serviceuser/(?P<serviceuserid>[0-9]+)$',
r'^edit_serviceuser/(?P<serviceuserid>[0-9]+)$',
views.edit_serviceuser, views.edit_serviceuser,
name='edit-serviceuser' name='edit-serviceuser'),
), url(r'^del_serviceuser/(?P<serviceuserid>[0-9]+)$',
url(
r'^del_serviceuser/(?P<serviceuserid>[0-9]+)$',
views.del_serviceuser, views.del_serviceuser,
name='del-serviceuser' name='del-serviceuser'),
),
url(r'^add_ban/(?P<userid>[0-9]+)$', views.add_ban, name='add-ban'), url(r'^add_ban/(?P<userid>[0-9]+)$', views.add_ban, name='add-ban'),
url(r'^edit_ban/(?P<banid>[0-9]+)$', views.edit_ban, name='edit-ban'), url(r'^edit_ban/(?P<banid>[0-9]+)$', views.edit_ban, name='edit-ban'),
url( url(r'^add_whitelist/(?P<userid>[0-9]+)$',
r'^add_whitelist/(?P<userid>[0-9]+)$',
views.add_whitelist, views.add_whitelist,
name='add-whitelist' name='add-whitelist'),
), url(r'^edit_whitelist/(?P<whitelistid>[0-9]+)$',
url(
r'^edit_whitelist/(?P<whitelistid>[0-9]+)$',
views.edit_whitelist, views.edit_whitelist,
name='edit-whitelist' name='edit-whitelist'),
),
url(r'^add_school/$', views.add_school, name='add-school'), url(r'^add_school/$', views.add_school, name='add-school'),
url( url(r'^edit_school/(?P<schoolid>[0-9]+)$',
r'^edit_school/(?P<schoolid>[0-9]+)$',
views.edit_school, views.edit_school,
name='edit-school' name='edit-school'),
),
url(r'^del_school/$', views.del_school, name='del-school'), url(r'^del_school/$', views.del_school, name='del-school'),
url(r'^add_listright/$', views.add_listright, name='add-listright'), url(r'^add_listright/$', views.add_listright, name='add-listright'),
url( url(r'^edit_listright/(?P<listrightid>[0-9]+)$',
r'^edit_listright/(?P<listrightid>[0-9]+)$',
views.edit_listright, views.edit_listright,
name='edit-listright' name='edit-listright'),
),
url(r'^del_listright/$', views.del_listright, name='del-listright'), url(r'^del_listright/$', views.del_listright, name='del-listright'),
url(r'^add_shell/$', views.add_shell, name='add-shell'), url(r'^add_shell/$', views.add_shell, name='add-shell'),
url( url(r'^edit_shell/(?P<listshellid>[0-9]+)$',
r'^edit_shell/(?P<listshellid>[0-9]+)$',
views.edit_shell, views.edit_shell,
name='edit-shell' name='edit-shell'),
), url(r'^del_shell/(?P<listshellid>[0-9]+)$',
url(
r'^del_shell/(?P<listshellid>[0-9]+)$',
views.del_shell, views.del_shell,
name='del-shell' name='del-shell'),
),
url(r'^profil/(?P<userid>[0-9]+)$', views.profil, name='profil'), url(r'^profil/(?P<userid>[0-9]+)$', views.profil, name='profil'),
url(r'^index_ban/$', views.index_ban, name='index-ban'), url(r'^index_ban/$', views.index_ban, name='index-ban'),
url(r'^index_white/$', views.index_white, name='index-white'), url(r'^index_white/$', views.index_white, name='index-white'),
url(r'^index_school/$', views.index_school, name='index-school'), url(r'^index_school/$', views.index_school, name='index-school'),
url(r'^index_shell/$', views.index_shell, name='index-shell'), url(r'^index_shell/$', views.index_shell, name='index-shell'),
url(r'^index_listright/$', views.index_listright, name='index-listright'), url(r'^index_listright/$', views.index_listright, name='index-listright'),
url( url(r'^index_serviceusers/$',
r'^index_serviceusers/$',
views.index_serviceusers, views.index_serviceusers,
name='index-serviceusers' name='index-serviceusers'),
),
url(r'^mon_profil/$', views.mon_profil, name='mon-profil'), url(r'^mon_profil/$', views.mon_profil, name='mon-profil'),
url(r'^process/(?P<token>[a-z0-9]{32})/$', views.process, name='process'), url(r'^process/(?P<token>[a-z0-9]{32})/$', views.process, name='process'),
url(r'^reset_password/$', views.reset_password, name='reset-password'), url(r'^reset_password/$', views.reset_password, name='reset-password'),
url(r'^mass_archive/$', views.mass_archive, name='mass-archive'), url(r'^mass_archive/$', views.mass_archive, name='mass-archive'),
url( url(r'^history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
r'^history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
re2o.views.history, re2o.views.history,
name='history', name='history',
kwargs={'application':'users'}, kwargs={'application': 'users'}),
),
url(r'^$', views.index, name='index'), url(r'^$', views.index, name='index'),
url(r'^index_clubs/$', views.index_clubs, name='index-clubs'), url(r'^index_clubs/$', views.index_clubs, name='index-clubs'),
url( url(r'^rest/ml/std/$',
r'^rest/ml/std/$',
views.ml_std_list, views.ml_std_list,
name='ml-std-list' name='ml-std-list'),
), url(r'^rest/ml/std/member/(?P<ml_name>\w+)/$',
url(
r'^rest/ml/std/member/(?P<ml_name>\w+)/$',
views.ml_std_members, views.ml_std_members,
name='ml-std-members' name='ml-std-members'),
), url(r'^rest/ml/club/$',
url(
r'^rest/ml/club/$',
views.ml_club_list, views.ml_club_list,
name='ml-club-list' name='ml-club-list'),
), url(r'^rest/ml/club/admin/(?P<ml_name>\w+)/$',
url(
r'^rest/ml/club/admin/(?P<ml_name>\w+)/$',
views.ml_club_admins, views.ml_club_admins,
name='ml-club-admins' name='ml-club-admins'),
), url(r'^rest/ml/club/member/(?P<ml_name>\w+)/$',
url(
r'^rest/ml/club/member/(?P<ml_name>\w+)/$',
views.ml_club_members, views.ml_club_members,
name='ml-club-members' name='ml-club-members'),
),
] ]

View file

@ -39,22 +39,37 @@ from django.urls import reverse
from django.shortcuts import get_object_or_404, render, redirect from django.shortcuts import get_object_or_404, render, redirect
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.db.models import ProtectedError, Q from django.db.models import ProtectedError
from django.db import IntegrityError
from django.utils import timezone from django.utils import timezone
from django.db import transaction from django.db import transaction
from django.http import HttpResponse from django.http import HttpResponse
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer from rest_framework.renderers import JSONRenderer
from reversion.models import Version
from reversion import revisions as reversion from reversion import revisions as reversion
from users.serializers import MailingSerializer, MailingMemberSerializer
from users.models import ( from cotisations.models import Facture
from machines.models import Machine
from preferences.models import OptionalUser, GeneralOption, AssoOption
from re2o.views import form
from re2o.utils import (
all_has_access,
SortTable,
re2o_paginator
)
from re2o.acl import (
can_create,
can_edit,
can_delete_set,
can_delete,
can_view,
can_view_all,
can_change
)
from .serializers import MailingSerializer, MailingMemberSerializer
from .models import (
User, User,
Ban, Ban,
Whitelist, Whitelist,
@ -66,7 +81,7 @@ from users.models import (
Club, Club,
ListShell, ListShell,
) )
from users.forms import ( from .forms import (
BanForm, BanForm,
WhitelistForm, WhitelistForm,
DelSchoolForm, DelSchoolForm,
@ -86,25 +101,7 @@ from users.forms import (
ClubAdminandMembersForm, ClubAdminandMembersForm,
GroupForm GroupForm
) )
from cotisations.models import Facture
from machines.models import Machine
from preferences.models import OptionalUser, GeneralOption, AssoOption
from re2o.views import form
from re2o.utils import (
all_has_access,
SortTable,
re2o_paginator
)
from re2o.acl import (
can_create,
can_edit,
can_delete_set,
can_delete,
can_view,
can_view_all,
can_change
)
@can_create(Adherent) @can_create(Adherent)
def new_user(request): def new_user(request):
@ -121,9 +118,19 @@ def new_user(request):
pour l'initialisation du mot de passe a été envoyé" % user.pseudo) pour l'initialisation du mot de passe a été envoyé" % user.pseudo)
return redirect(reverse( return redirect(reverse(
'users:profil', 'users:profil',
kwargs={'userid':str(user.id)} kwargs={'userid': str(user.id)}
)) ))
return form({'userform': user,'GTU_sum_up':GTU_sum_up,'GTU':GTU,'showCGU':True, 'action_name':'Créer un utilisateur'}, 'users/user.html', request) return form(
{
'userform': user,
'GTU_sum_up': GTU_sum_up,
'GTU': GTU,
'showCGU': True,
'action_name': 'Créer un utilisateur'
},
'users/user.html',
request
)
@login_required @login_required
@ -140,26 +147,41 @@ def new_club(request):
pour l'initialisation du mot de passe a été envoyé" % club.pseudo) pour l'initialisation du mot de passe a été envoyé" % club.pseudo)
return redirect(reverse( return redirect(reverse(
'users:profil', 'users:profil',
kwargs={'userid':str(club.id)} kwargs={'userid': str(club.id)}
)) ))
return form({'userform': club, 'showCGU':False, 'action_name':'Créer un club'}, 'users/user.html', request) return form(
{'userform': club, 'showCGU': False, 'action_name': 'Créer un club'},
'users/user.html',
request
)
@login_required @login_required
@can_edit(Club) @can_edit(Club)
def edit_club_admin_members(request, club_instance, clubid): def edit_club_admin_members(request, club_instance, **_kwargs):
"""Vue d'edition de la liste des users administrateurs et """Vue d'edition de la liste des users administrateurs et
membres d'un club""" membres d'un club"""
club = ClubAdminandMembersForm(request.POST or None, instance=club_instance) club = ClubAdminandMembersForm(
request.POST or None,
instance=club_instance
)
if club.is_valid(): if club.is_valid():
if club.changed_data: if club.changed_data:
club.save() club.save()
messages.success(request, "Le club a bien été modifié") messages.success(request, "Le club a bien été modifié")
return redirect(reverse( return redirect(reverse(
'users:profil', 'users:profil',
kwargs={'userid':str(club_instance.id)} kwargs={'userid': str(club_instance.id)}
)) ))
return form({'userform': club, 'showCGU':False, 'action_name':'Editer les admin et membres'}, 'users/user.html', request) return form(
{
'userform': club,
'showCGU': False,
'action_name': 'Editer les admin et membres'
},
'users/user.html',
request
)
@login_required @login_required
@ -169,26 +191,30 @@ def edit_info(request, user, userid):
si l'id est différent de request.user, vérifie la si l'id est différent de request.user, vérifie la
possession du droit cableur """ possession du droit cableur """
if user.is_class_adherent: if user.is_class_adherent:
user = AdherentForm( user_form = AdherentForm(
request.POST or None, request.POST or None,
instance=user.adherent, instance=user.adherent,
user=request.user user=request.user
) )
elif user.is_class_club: else:
user = ClubForm( user_form = ClubForm(
request.POST or None, request.POST or None,
instance=user.club, instance=user.club,
user=request.user user=request.user
) )
if user.is_valid(): if user_form.is_valid():
if user.changed_data: if user_form.changed_data:
user.save() user_form.save()
messages.success(request, "L'user a bien été modifié") messages.success(request, "L'user a bien été modifié")
return redirect(reverse( return redirect(reverse(
'users:profil', 'users:profil',
kwargs={'userid':str(userid)} kwargs={'userid': str(userid)}
)) ))
return form({'userform': user, 'action_name': "Editer l'utilisateur"}, 'users/user.html', request) return form(
{'userform': user_form, 'action_name': "Editer l'utilisateur"},
'users/user.html',
request
)
@login_required @login_required
@ -196,35 +222,44 @@ def edit_info(request, user, userid):
def state(request, user, userid): def state(request, user, userid):
""" Changer l'etat actif/desactivé/archivé d'un user, """ Changer l'etat actif/desactivé/archivé d'un user,
need droit bureau """ need droit bureau """
state = StateForm(request.POST or None, instance=user) state_form = StateForm(request.POST or None, instance=user)
if state.is_valid(): if state_form.is_valid():
if state.changed_data: if state_form.changed_data:
if state.cleaned_data['state'] == User.STATE_ARCHIVE: if state_form.cleaned_data['state'] == User.STATE_ARCHIVE:
user.archive() user.archive()
elif state.cleaned_data['state'] == User.STATE_ACTIVE: elif state_form.cleaned_data['state'] == User.STATE_ACTIVE:
user.unarchive() user.unarchive()
state.save() state_form.save()
messages.success(request, "Etat changé avec succès") messages.success(request, "Etat changé avec succès")
return redirect(reverse( return redirect(reverse(
'users:profil', 'users:profil',
kwargs={'userid':str(userid)} kwargs={'userid': str(userid)}
)) ))
return form({'userform': state, 'action_name': "Editer l'état"}, 'users/user.html', request) return form(
{'userform': state_form, 'action_name': "Editer l'état"},
'users/user.html',
request
)
@login_required @login_required
@can_edit(User, 'groups') @can_edit(User, 'groups')
def groups(request, user, userid): def groups(request, user, userid):
group = GroupForm(request.POST or None, instance=user) """ View to edit the groups of a user """
if group.is_valid(): group_form = GroupForm(request.POST or None, instance=user)
if group.changed_data: if group_form.is_valid():
group.save() if group_form.changed_data:
group_form.save()
messages.success(request, "Groupes changés avec succès") messages.success(request, "Groupes changés avec succès")
return redirect(reverse( return redirect(reverse(
'users:profil', 'users:profil',
kwargs={'userid':str(userid)} kwargs={'userid': str(userid)}
)) ))
return form({'userform': group, 'action_name':'Editer les groupes'}, 'users/user.html', request) return form(
{'userform': group_form, 'action_name': 'Editer les groupes'},
'users/user.html',
request
)
@login_required @login_required
@ -239,15 +274,20 @@ def password(request, user, userid):
u_form.save() u_form.save()
messages.success(request, "Le mot de passe a changé") messages.success(request, "Le mot de passe a changé")
return redirect(reverse( return redirect(reverse(
'users:profil', 'users:profil',
kwargs={'userid':str(user.id)} kwargs={'userid': str(userid)}
)) ))
return form({'userform': u_form, 'action_name':'Changer le mot de passe'}, 'users/user.html', request) return form(
{'userform': u_form, 'action_name': 'Changer le mot de passe'},
'users/user.html',
request
)
@login_required @login_required
@can_edit(User, 'groups') @can_edit(User, 'groups')
def del_group(request, user, userid, listrightid): def del_group(request, user, listrightid, **_kwargs):
""" View used to delete a group """
user.groups.remove(ListRight.objects.get(id=listrightid)) user.groups.remove(ListRight.objects.get(id=listrightid))
user.save() user.save()
messages.success(request, "Droit supprimé à %s" % user) messages.success(request, "Droit supprimé à %s" % user)
@ -268,14 +308,21 @@ def new_serviceuser(request):
"L'utilisateur %s a été crée" % user_object.pseudo "L'utilisateur %s a été crée" % user_object.pseudo
) )
return redirect(reverse('users:index-serviceusers')) return redirect(reverse('users:index-serviceusers'))
return form({'userform': user, 'action_name':'Créer un serviceuser'}, 'users/user.html', request) return form(
{'userform': user, 'action_name': 'Créer un serviceuser'},
'users/user.html',
request
)
@login_required @login_required
@can_edit(ServiceUser) @can_edit(ServiceUser)
def edit_serviceuser(request, serviceuser, serviceuserid): def edit_serviceuser(request, serviceuser, **_kwargs):
""" Edit a ServiceUser """ """ Edit a ServiceUser """
serviceuser = EditServiceUserForm(request.POST or None, instance=serviceuser) serviceuser = EditServiceUserForm(
request.POST or None,
instance=serviceuser
)
if serviceuser.is_valid(): if serviceuser.is_valid():
user_object = serviceuser.save(commit=False) user_object = serviceuser.save(commit=False)
if serviceuser.cleaned_data['password']: if serviceuser.cleaned_data['password']:
@ -284,12 +331,16 @@ def edit_serviceuser(request, serviceuser, serviceuserid):
user_object.save() user_object.save()
messages.success(request, "L'user a bien été modifié") messages.success(request, "L'user a bien été modifié")
return redirect(reverse('users:index-serviceusers')) return redirect(reverse('users:index-serviceusers'))
return form({'userform': serviceuser, 'action_name':'Editer un serviceuser'}, 'users/user.html', request) return form(
{'userform': serviceuser, 'action_name': 'Editer un serviceuser'},
'users/user.html',
request
)
@login_required @login_required
@can_delete(ServiceUser) @can_delete(ServiceUser)
def del_serviceuser(request, serviceuser, serviceuserid): def del_serviceuser(request, serviceuser, **_kwargs):
"""Suppression d'un ou plusieurs serviceusers""" """Suppression d'un ou plusieurs serviceusers"""
if request.method == "POST": if request.method == "POST":
serviceuser.delete() serviceuser.delete()
@ -312,22 +363,27 @@ def add_ban(request, user, userid):
ban_instance = Ban(user=user) ban_instance = Ban(user=user)
ban = BanForm(request.POST or None, instance=ban_instance) ban = BanForm(request.POST or None, instance=ban_instance)
if ban.is_valid(): if ban.is_valid():
_ban_object = ban.save() ban.save()
messages.success(request, "Bannissement ajouté") messages.success(request, "Bannissement ajouté")
return redirect(reverse( return redirect(reverse(
'users:profil', 'users:profil',
kwargs={'userid':str(userid)} kwargs={'userid': str(userid)}
)) ))
if user.is_ban(): if user.is_ban():
messages.error( messages.error(
request, request,
"Attention, cet utilisateur a deja un bannissement actif" "Attention, cet utilisateur a deja un bannissement actif"
) )
return form({'userform': ban, 'action_name': 'Ajouter un ban'}, 'users/user.html', request) return form(
{'userform': ban, 'action_name': 'Ajouter un ban'},
'users/user.html',
request
)
@login_required @login_required
@can_edit(Ban) @can_edit(Ban)
def edit_ban(request, ban_instance, banid): def edit_ban(request, ban_instance, **_kwargs):
""" Editer un bannissement, nécessite au moins le droit bofh """ Editer un bannissement, nécessite au moins le droit bofh
(a fortiori bureau) (a fortiori bureau)
Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement""" Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement"""
@ -337,7 +393,11 @@ def edit_ban(request, ban_instance, banid):
ban.save() ban.save()
messages.success(request, "Bannissement modifié") messages.success(request, "Bannissement modifié")
return redirect(reverse('users:index')) return redirect(reverse('users:index'))
return form({'userform': ban, 'action_name': 'Editer un ban'}, 'users/user.html', request) return form(
{'userform': ban, 'action_name': 'Editer un ban'},
'users/user.html',
request
)
@login_required @login_required
@ -358,19 +418,23 @@ def add_whitelist(request, user, userid):
messages.success(request, "Accès à titre gracieux accordé") messages.success(request, "Accès à titre gracieux accordé")
return redirect(reverse( return redirect(reverse(
'users:profil', 'users:profil',
kwargs={'userid':str(userid)} kwargs={'userid': str(userid)}
)) ))
if user.is_whitelisted(): if user.is_whitelisted():
messages.error( messages.error(
request, request,
"Attention, cet utilisateur a deja un accès gracieux actif" "Attention, cet utilisateur a deja un accès gracieux actif"
) )
return form({'userform': whitelist, 'action_name': 'Ajouter une whitelist'}, 'users/user.html', request) return form(
{'userform': whitelist, 'action_name': 'Ajouter une whitelist'},
'users/user.html',
request
)
@login_required @login_required
@can_edit(Whitelist) @can_edit(Whitelist)
def edit_whitelist(request, whitelist_instance, whitelistid): def edit_whitelist(request, whitelist_instance, **_kwargs):
""" Editer un accès gracieux, temporaire ou permanent. """ Editer un accès gracieux, temporaire ou permanent.
Need droit cableur Need droit cableur
Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement, Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement,
@ -384,7 +448,11 @@ def edit_whitelist(request, whitelist_instance, whitelistid):
whitelist.save() whitelist.save()
messages.success(request, "Whitelist modifiée") messages.success(request, "Whitelist modifiée")
return redirect(reverse('users:index')) return redirect(reverse('users:index'))
return form({'userform': whitelist, 'action_name': 'Editer une whitelist'}, 'users/user.html', request) return form(
{'userform': whitelist, 'action_name': 'Editer une whitelist'},
'users/user.html',
request
)
@login_required @login_required
@ -397,12 +465,16 @@ def add_school(request):
school.save() school.save()
messages.success(request, "L'établissement a été ajouté") messages.success(request, "L'établissement a été ajouté")
return redirect(reverse('users:index-school')) return redirect(reverse('users:index-school'))
return form({'userform': school, 'action_name':'Ajouter'}, 'users/user.html', request) return form(
{'userform': school, 'action_name': 'Ajouter'},
'users/user.html',
request
)
@login_required @login_required
@can_edit(School) @can_edit(School)
def edit_school(request, school_instance, schoolid): def edit_school(request, school_instance, **_kwargs):
""" Editer un établissement d'enseignement à partir du schoolid dans """ Editer un établissement d'enseignement à partir du schoolid dans
la base de donnée, need cableur""" la base de donnée, need cableur"""
school = SchoolForm(request.POST or None, instance=school_instance) school = SchoolForm(request.POST or None, instance=school_instance)
@ -411,7 +483,11 @@ def edit_school(request, school_instance, schoolid):
school.save() school.save()
messages.success(request, "Établissement modifié") messages.success(request, "Établissement modifié")
return redirect(reverse('users:index-school')) return redirect(reverse('users:index-school'))
return form({'userform': school, 'action_name':'Editer'}, 'users/user.html', request) return form(
{'userform': school, 'action_name': 'Editer'},
'users/user.html',
request
)
@login_required @login_required
@ -434,7 +510,11 @@ def del_school(request, instances):
"L'établissement %s est affecté à au moins un user, \ "L'établissement %s est affecté à au moins un user, \
vous ne pouvez pas le supprimer" % school_del) vous ne pouvez pas le supprimer" % school_del)
return redirect(reverse('users:index-school')) return redirect(reverse('users:index-school'))
return form({'userform': school, 'action_name': 'Supprimer'}, 'users/user.html', request) return form(
{'userform': school, 'action_name': 'Supprimer'},
'users/user.html',
request
)
@login_required @login_required
@ -446,12 +526,16 @@ def add_shell(request):
shell.save() shell.save()
messages.success(request, "Le shell a été ajouté") messages.success(request, "Le shell a été ajouté")
return redirect(reverse('users:index-shell')) return redirect(reverse('users:index-shell'))
return form({'userform': shell, 'action_name':'Ajouter'}, 'users/user.html', request) return form(
{'userform': shell, 'action_name': 'Ajouter'},
'users/user.html',
request
)
@login_required @login_required
@can_edit(ListShell) @can_edit(ListShell)
def edit_shell(request, shell_instance, listshellid): def edit_shell(request, shell_instance, **_kwargs):
""" Editer un shell à partir du listshellid""" """ Editer un shell à partir du listshellid"""
shell = ShellForm(request.POST or None, instance=shell_instance) shell = ShellForm(request.POST or None, instance=shell_instance)
if shell.is_valid(): if shell.is_valid():
@ -459,12 +543,16 @@ def edit_shell(request, shell_instance, listshellid):
shell.save() shell.save()
messages.success(request, "Le shell a été modifié") messages.success(request, "Le shell a été modifié")
return redirect(reverse('users:index-shell')) return redirect(reverse('users:index-shell'))
return form({'userform': shell, 'action_name':'Editer'}, 'users/user.html', request) return form(
{'userform': shell, 'action_name': 'Editer'},
'users/user.html',
request
)
@login_required @login_required
@can_delete(ListShell) @can_delete(ListShell)
def del_shell(request, shell, listshellid): def del_shell(request, shell, **_kwargs):
"""Destruction d'un shell""" """Destruction d'un shell"""
if request.method == "POST": if request.method == "POST":
shell.delete() shell.delete()
@ -487,12 +575,16 @@ def add_listright(request):
listright.save() listright.save()
messages.success(request, "Le droit/groupe a été ajouté") messages.success(request, "Le droit/groupe a été ajouté")
return redirect(reverse('users:index-listright')) return redirect(reverse('users:index-listright'))
return form({'userform': listright, 'action_name': 'Ajouter'}, 'users/user.html', request) return form(
{'userform': listright, 'action_name': 'Ajouter'},
'users/user.html',
request
)
@login_required @login_required
@can_edit(ListRight) @can_edit(ListRight)
def edit_listright(request, listright_instance, listrightid): def edit_listright(request, listright_instance, **_kwargs):
""" Editer un groupe/droit, necessite droit bureau, """ Editer un groupe/droit, necessite droit bureau,
à partir du listright id """ à partir du listright id """
listright = ListRightForm( listright = ListRightForm(
@ -504,7 +596,11 @@ def edit_listright(request, listright_instance, listrightid):
listright.save() listright.save()
messages.success(request, "Droit modifié") messages.success(request, "Droit modifié")
return redirect(reverse('users:index-listright')) return redirect(reverse('users:index-listright'))
return form({'userform': listright, 'action_name': 'Editer'}, 'users/user.html', request) return form(
{'userform': listright, 'action_name': 'Editer'},
'users/user.html',
request
)
@login_required @login_required
@ -525,7 +621,11 @@ def del_listright(request, instances):
"Le groupe %s est affecté à au moins un user, \ "Le groupe %s est affecté à au moins un user, \
vous ne pouvez pas le supprimer" % listright_del) vous ne pouvez pas le supprimer" % listright_del)
return redirect(reverse('users:index-listright')) return redirect(reverse('users:index-listright'))
return form({'userform': listright, 'action_name': 'Supprimer'}, 'users/user.html', request) return form(
{'userform': listright, 'action_name': 'Supprimer'},
'users/user.html',
request
)
@login_required @login_required
@ -587,7 +687,11 @@ def index_clubs(request):
SortTable.USERS_INDEX SortTable.USERS_INDEX
) )
clubs_list = re2o_paginator(request, clubs_list, pagination_number) clubs_list = re2o_paginator(request, clubs_list, pagination_number)
return render(request, 'users/index_clubs.html', {'clubs_list': clubs_list}) return render(
request,
'users/index_clubs.html',
{'clubs_list': clubs_list}
)
@login_required @login_required
@ -688,13 +792,13 @@ def mon_profil(request):
""" Lien vers profil, renvoie request.id à la fonction """ """ Lien vers profil, renvoie request.id à la fonction """
return redirect(reverse( return redirect(reverse(
'users:profil', 'users:profil',
kwargs={'userid':str(request.user.id)} kwargs={'userid': str(request.user.id)}
)) ))
@login_required @login_required
@can_view(User) @can_view(User)
def profil(request, users, userid): def profil(request, users, **_kwargs):
""" Affiche un profil, self or cableur, prend un userid en argument """ """ Affiche un profil, self or cableur, prend un userid en argument """
machines = Machine.objects.filter(user=users).select_related('user')\ machines = Machine.objects.filter(user=users).select_related('user')\
.prefetch_related('interface_set__domain__extension')\ .prefetch_related('interface_set__domain__extension')\
@ -707,7 +811,9 @@ def profil(request, users, userid):
request.GET.get('order'), request.GET.get('order'),
SortTable.MACHINES_INDEX SortTable.MACHINES_INDEX
) )
pagination_large_number = GeneralOption.get_cached_value('pagination_large_number') pagination_large_number = GeneralOption.get_cached_value(
'pagination_large_number'
)
machines = re2o_paginator(request, machines, pagination_large_number) machines = re2o_paginator(request, machines, pagination_large_number)
factures = Facture.objects.filter(user=users) factures = Facture.objects.filter(user=users)
factures = SortTable.sort( factures = SortTable.sort(
@ -742,7 +848,7 @@ def profil(request, users, userid):
'ban_list': bans, 'ban_list': bans,
'white_list': whitelists, 'white_list': whitelists,
'user_solde': user_solde, 'user_solde': user_solde,
'allow_online_payment' : allow_online_payment, 'allow_online_payment': allow_online_payment,
} }
) )
@ -758,12 +864,20 @@ def reset_password(request):
) )
except User.DoesNotExist: except User.DoesNotExist:
messages.error(request, "Cet utilisateur n'existe pas") messages.error(request, "Cet utilisateur n'existe pas")
return form({'userform': userform, 'action_name': 'Réinitialiser'}, 'users/user.html', request) return form(
{'userform': userform, 'action_name': 'Réinitialiser'},
'users/user.html',
request
)
user.reset_passwd_mail(request) user.reset_passwd_mail(request)
messages.success(request, "Un mail pour l'initialisation du mot\ messages.success(request, "Un mail pour l'initialisation du mot\
de passe a été envoyé") de passe a été envoyé")
redirect(reverse('index')) redirect(reverse('index'))
return form({'userform': userform, 'action_name': 'Réinitialiser'}, 'users/user.html', request) return form(
{'userform': userform, 'action_name': 'Réinitialiser'},
'users/user.html',
request
)
def process(request, token): def process(request, token):
@ -790,7 +904,11 @@ def process_passwd(request, req):
req.delete() req.delete()
messages.success(request, "Le mot de passe a changé") messages.success(request, "Le mot de passe a changé")
return redirect(reverse('index')) return redirect(reverse('index'))
return form({'userform': u_form, 'action_name': 'Changer le mot de passe'}, 'users/user.html', request) return form(
{'userform': u_form, 'action_name': 'Changer le mot de passe'},
'users/user.html',
request
)
class JSONResponse(HttpResponse): class JSONResponse(HttpResponse):
@ -804,7 +922,7 @@ class JSONResponse(HttpResponse):
@csrf_exempt @csrf_exempt
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
def ml_std_list(request): def ml_std_list(_request):
""" API view sending all the available standard mailings""" """ API view sending all the available standard mailings"""
return JSONResponse([ return JSONResponse([
{'name': 'adherents'} {'name': 'adherents'}
@ -830,7 +948,7 @@ def ml_std_members(request, ml_name):
@csrf_exempt @csrf_exempt
@login_required @login_required
@permission_required('machines.serveur') @permission_required('machines.serveur')
def ml_club_list(request): def ml_club_list(_request):
""" API view sending all the available club mailings""" """ API view sending all the available club mailings"""
clubs = Club.objects.filter(mailing=True).values('pseudo') clubs = Club.objects.filter(mailing=True).values('pseudo')
seria = MailingSerializer(clubs, many=True) seria = MailingSerializer(clubs, many=True)
@ -862,6 +980,9 @@ def ml_club_members(request, ml_name):
except Club.DoesNotExist: except Club.DoesNotExist:
messages.error(request, "Cette mailing n'existe pas") messages.error(request, "Cette mailing n'existe pas")
return redirect(reverse('index')) return redirect(reverse('index'))
members = club.administrators.all().values('email').distinct() | club.members.all().values('email').distinct() members = (
club.administrators.all().values('email').distinct() |
club.members.all().values('email').distinct()
)
seria = MailingMemberSerializer(members, many=True) seria = MailingMemberSerializer(members, many=True)
return JSONResponse(seria.data) return JSONResponse(seria.data)