8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-22 03:13:12 +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,
Extension,
IpList,
MachineType,
Domain,
Txt,
Mx,
Srv,
Service_link,
Ns,
OuverturePortList,
OuverturePort,
Ipv6List
)
class ServiceLinkSerializer(serializers.ModelSerializer):
""" Serializer for the ServiceLink objects """
name = serializers.CharField(source='service.service_type')
class Meta:
@ -51,6 +51,8 @@ class ServiceLinkSerializer(serializers.ModelSerializer):
class MailingSerializer(serializers.ModelSerializer):
""" Serializer to build Mailing objects """
name = serializers.CharField(source='pseudo')
class Meta:
@ -59,20 +61,27 @@ class MailingSerializer(serializers.ModelSerializer):
class MailingMemberSerializer(serializers.ModelSerializer):
""" Serializer fot the Adherent objects (who belong to a
Mailing) """
class Meta:
model = Adherent
fields = ('email', 'name', 'surname', 'pseudo',)
fields = ('email',)
class IpTypeField(serializers.RelatedField):
"""Serialisation d'une iptype, renvoie son evaluation str"""
""" Serializer for an IpType object field """
def to_representation(self, value):
return value.type
def to_internal_value(self, data):
pass
class IpListSerializer(serializers.ModelSerializer):
"""Serialisation d'une iplist, ip_type etant une foreign_key,
on evalue sa methode str"""
""" Serializer for an Ipv4List obejct using the IpType serialization """
ip_type = IpTypeField(read_only=True)
class Meta:
@ -81,16 +90,19 @@ class IpListSerializer(serializers.ModelSerializer):
class Ipv6ListSerializer(serializers.ModelSerializer):
""" Serializer for an Ipv6List object """
class Meta:
model = Ipv6List
fields = ('ipv6', 'slaac_ip')
class InterfaceSerializer(serializers.ModelSerializer):
"""Serialisation d'une interface, ipv4, domain et extension sont
des foreign_key, on les override et on les evalue avec des fonctions
get_..."""
""" Serializer for an Interface object. Use SerializerMethodField
to get ForeignKey values """
ipv4 = IpListSerializer(read_only=True)
# TODO : use serializer.RelatedField to avoid duplicate code
mac_address = serializers.SerializerMethodField('get_macaddress')
domain = serializers.SerializerMethodField('get_dns')
extension = serializers.SerializerMethodField('get_interface_extension')
@ -99,20 +111,29 @@ class InterfaceSerializer(serializers.ModelSerializer):
model = Interface
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
def get_interface_extension(self, obj):
@staticmethod
def get_interface_extension(obj):
""" The name of the associated Interface object """
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)
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)
ipv6 = Ipv6ListSerializer(read_only=True, many=True)
# TODO : use serializer.RelatedField to avoid duplicate code
mac_address = serializers.SerializerMethodField('get_macaddress')
domain = serializers.SerializerMethodField('get_dns')
extension = serializers.SerializerMethodField('get_interface_extension')
@ -121,26 +142,36 @@ class FullInterfaceSerializer(serializers.ModelSerializer):
model = Interface
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
def get_interface_extension(self, obj):
@staticmethod
def get_interface_extension(obj):
""" The name of the associated Extension object """
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)
class ExtensionNameField(serializers.RelatedField):
"""Evaluation str d'un objet extension (.example.org)"""
""" Serializer for Extension object field """
def to_representation(self, value):
return value.name
def to_internal_value(self, data):
pass
class TypeSerializer(serializers.ModelSerializer):
"""Serialisation d'un iptype : extension et la liste des
ouvertures de port son evalués en get_... etant des
foreign_key ou des relations manytomany"""
""" Serializer for an IpType object. Use SerializerMethodField to
get ForeignKey values """
extension = ExtensionNameField(read_only=True)
ouverture_ports_tcp_in = serializers\
.SerializerMethodField('get_port_policy_input_tcp')
@ -158,7 +189,10 @@ class TypeSerializer(serializers.ModelSerializer):
'ouverture_ports_tcp_in', 'ouverture_ports_tcp_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:
return []
return map(
@ -196,14 +230,20 @@ class ExtensionSerializer(serializers.ModelSerializer):
model = Extension
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
def get_zone_name(self, obj):
@staticmethod
def get_zone_name(obj):
""" The name of the associated zone """
return str(obj.dns_entry)
def get_soa_data(self, obj):
return { 'mail': obj.soa.dns_soa_mail, 'param': obj.soa.dns_soa_param }
@staticmethod
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):
@ -217,13 +257,19 @@ class MxSerializer(serializers.ModelSerializer):
model = Mx
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)
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
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)
@ -237,10 +283,14 @@ class TxtSerializer(serializers.ModelSerializer):
model = Txt
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)
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)
@ -263,10 +313,14 @@ class SrvSerializer(serializers.ModelSerializer):
'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)
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)
@ -281,13 +335,19 @@ class NsSerializer(serializers.ModelSerializer):
model = Ns
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
def get_domain_name(self, obj):
@staticmethod
def get_domain_name(obj):
""" The name of the associated NS target """
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)
@ -302,13 +362,19 @@ class DomainSerializer(serializers.ModelSerializer):
model = Domain
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
def get_alias_name(self, obj):
@staticmethod
def get_alias_name(obj):
""" The name of the associated alias """
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)
@ -322,13 +388,19 @@ class ServicesSerializer(serializers.ModelSerializer):
model = Service_link
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)
def get_service_name(self, obj):
@staticmethod
def get_service_name(obj):
""" The name of the service name """
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()
@ -337,24 +409,38 @@ class OuverturePortsSerializer(serializers.Serializer):
ipv4 = 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():
return {i.ipv4.ipv4:
{
"tcp_in":[j.tcp_ports_in() for j in i.port_lists.all()],
"tcp_out":[j.tcp_ports_out()for j in i.port_lists.all()],
"udp_in":[j.udp_ports_in() for j in i.port_lists.all()],
"udp_out":[j.udp_ports_out() for j in i.port_lists.all()],
""" The representation of the policy for the IPv4 addresses """
return {
i.ipv4.ipv4: {
"tcp_in": [j.tcp_ports_in() for j in i.port_lists.all()],
"tcp_out": [j.tcp_ports_out()for j in i.port_lists.all()],
"udp_in": [j.udp_ports_in() for j in i.port_lists.all()],
"udp_out": [j.udp_ports_out() for j in i.port_lists.all()],
}
for i in Interface.objects.all() if i.ipv4
for i in Interface.objects.all() if i.ipv4
}
@staticmethod
def get_ipv6():
return {i.ipv6:
{
"tcp_in":[j.tcp_ports_in() for j in i.port_lists.all()],
"tcp_out":[j.tcp_ports_out()for j in i.port_lists.all()],
"udp_in":[j.udp_ports_in() for j in i.port_lists.all()],
"udp_out":[j.udp_ports_out() for j in i.port_lists.all()],
""" The representation of the policy for the IPv6 addresses """
return {
i.ipv6: {
"tcp_in": [j.tcp_ports_in() for j in i.port_lists.all()],
"tcp_out": [j.tcp_ports_out()for j in i.port_lists.all()],
"udp_in": [j.udp_ports_in() for j in i.port_lists.all()],
"udp_out": [j.udp_ports_out() for j in i.port_lists.all()],
}
for i in Interface.objects.all() if i.ipv6
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
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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.

View file

@ -32,7 +32,10 @@ from . import views
urlpatterns = [
# Services
url(r'^services/$', views.services),
url(r'^services/(?P<server_name>\w+)/(?P<service_name>\w+)/regen/$', views.services_server_service_regen),
url(
r'^services/(?P<server_name>\w+)/(?P<service_name>\w+)/regen/$',
views.services_server_service_regen
),
url(r'^services/(?P<server_name>\w+)/$', views.services_server),
# DNS
@ -56,7 +59,13 @@ urlpatterns = [
# Mailings
url(r'^mailing/standard/$', views.mailing_standard),
url(r'^mailing/standard/(?P<ml_name>\w+)/members/$', views.mailing_standard_ml_members),
url(
r'^mailing/standard/(?P<ml_name>\w+)/members/$',
views.mailing_standard_ml_members
),
url(r'^mailing/club/$', views.mailing_club),
url(r'^mailing/club/(?P<ml_name>\w+)/members/$', views.mailing_club_ml_members),
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 django.http import HttpResponse
class JSONResponse(HttpResponse):
"""A JSON response that can be send as an HTTP response.
Usefull in case of REST API.
@ -51,23 +52,23 @@ class JSONResponse(HttpResponse):
class JSONError(JSONResponse):
"""A JSON response when the request failed.
"""
def __init__(self, error_msg, data=None, **kwargs):
"""Initialise a JSONError object.
Args:
error_msg: A message explaining where the error is.
data: An optional field for further data to send along.
Creates:
A JSONResponse containing a field `status` set to `error` and a field
`reason` containing `error_msg`. If `data` argument has been given,
a field `data` containing it is added to the JSON response.
A JSONResponse containing a field `status` set to `error` and a
field `reason` containing `error_msg`. If `data` argument has been
given, a field `data` containing it is added to the JSON response.
"""
response = {
'status' : 'error',
'reason' : error_msg
'status': 'error',
'reason': error_msg
}
if data is not None:
response['data'] = data
@ -77,22 +78,22 @@ class JSONError(JSONResponse):
class JSONSuccess(JSONResponse):
"""A JSON response when the request suceeded.
"""
def __init__(self, data=None, **kwargs):
"""Initialise a JSONSucess object.
Args:
error_msg: A message explaining where the error is.
data: An optional field for further data to send along.
Creates:
A JSONResponse containing a field `status` set to `sucess`. If `data`
argument has been given, a field `data` containing it is added to the
JSON response.
A JSONResponse containing a field `status` set to `sucess`. If
`data` argument has been given, a field `data` containing it is
added to the JSON response.
"""
response = {
'status' : 'success',
'status': 'success',
}
if data is not None:
response['data'] = data
@ -103,12 +104,20 @@ def accept_method(methods):
"""Decorator to set a list of accepted request method.
Check if the method used is accepted. If not, send a NotAllowed response.
"""
def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
if request.method in methods:
return view(request, *args, **kwargs)
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 wrapper
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.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 machines.models import (Service_link, Service, Interface, Domain,
OuverturePortList)
from machines.models import (
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
@ -41,21 +70,26 @@ from .utils import JSONError, JSONSuccess, accept_method
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def services(request):
def services(_request):
"""The list of the different services and servers couples
Return:
GET:
A JSONSuccess response with a field `data` containing:
* a list of dictionnaries (one for each service-server couple) containing:
* a list of dictionnaries (one for each service-server couple)
containing:
* a field `server`: the server name
* a field `service`: the service name
* a field `need_regen`: does the service need a regeneration ?
"""
service_link = Service_link.objects.all().select_related('server__domain').select_related('service')
service_link = (Service_link.objects.all()
.select_related('server__domain')
.select_related('service'))
seria = ServicesSerializer(service_link, many=True)
return JSONSuccess(seria.data)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@ -72,6 +106,7 @@ def services_server_service_regen(request, server_name, service_name):
POST:
An empty JSONSuccess response.
"""
query = Service_link.objects.filter(
service__in=Service.objects.filter(service_type=service_name),
server__in=Interface.objects.filter(
@ -80,7 +115,7 @@ def services_server_service_regen(request, server_name, service_name):
)
if not query:
return JSONError("This service is not active for this server")
service = query.first()
if request.method == 'GET':
return JSONSuccess({'need_regen': service.need_regen()})
@ -93,7 +128,7 @@ def services_server_service_regen(request, server_name, service_name):
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def services_server(request, server_name):
def services_server(_request, server_name):
"""The list of services attached to a specific server
Returns:
@ -102,6 +137,7 @@ def services_server(request, server_name):
* a list of dictionnaries (one for each service) containing:
* a field `name`: the name of a service
"""
query = Service_link.objects.filter(
server__in=Interface.objects.filter(
domain__in=Domain.objects.filter(name=server_name)
@ -109,9 +145,9 @@ def services_server(request, server_name):
)
if not query:
return JSONError("This service is not active for this server")
services = query.all()
seria = ServiceLinkSerializer(services, many=True)
services_objects = query.all()
seria = ServiceLinkSerializer(services_objects, many=True)
return JSONSuccess(seria.data)
@ -119,7 +155,7 @@ def services_server(request, server_name):
@login_required
@permission_required('machines.serveur')
@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
(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 `mac_address`: the MAC of this interface
* a field `domain`: the DNS name for this interface
* a field `extension`: the extension for the DNS zone of this interface
* a field `extension`: the extension for the DNS zone of this
interface
"""
interfaces = all_active_assigned_interfaces(full=True)
seria = FullInterfaceSerializer(interfaces, many=True)
return JSONSuccess(seria.data)
@ -146,7 +184,7 @@ def dns_mac_ip_dns(request):
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def dns_alias(request):
def dns_alias(_request):
"""The list of all the alias used and the DNS info associated
Returns:
@ -154,11 +192,23 @@ def dns_alias(request):
A JSON Success response with a field `data` containing:
* a list of dictionnaries (one for each alias) containing:
* a field `name`: the alias used
* a field `cname`: the target of the alias (real name of the interface)
* a field `cname_entry`: the entry to write in the DNS to have the alias
* a field `extension`: the extension for the DNS zone of this interface
* a field `cname`: the target of the alias (real name of the
interface)
* a field `cname_entry`: the entry to write in the DNS to have
the alias
* a field `extension`: the extension for the DNS zone of this
interface
"""
alias = Domain.objects.filter(interface_parent=None).filter(cname__in=Domain.objects.filter(interface_parent__in=Interface.objects.exclude(ipv4=None))).select_related('extension').select_related('cname__extension')
alias = (Domain.objects
.filter(interface_parent=None)
.filter(
cname__in=Domain.objects.filter(
interface_parent__in=Interface.objects.exclude(ipv4=None)
)
)
.select_related('extension')
.select_related('cname__extension'))
seria = DomainSerializer(alias, many=True)
return JSONSuccess(seria.data)
@ -167,7 +217,7 @@ def dns_alias(request):
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def accesspoint_ip_dns(request):
def accesspoint_ip_dns(_request):
"""The list of all active interfaces with all the associated infos
(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 `mac_address`: the MAC of this interface
* a field `domain`: the DNS name for this interface
* a field `extension`: the extension for the DNS zone of this interface
* 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)
return JSONSuccess(seria.data)
@ -197,7 +249,7 @@ def accesspoint_ip_dns(request):
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def dns_corresp(request):
def dns_corresp(_request):
"""The list of the IpTypes possible with the infos about each
Returns:
@ -208,12 +260,14 @@ def dns_corresp(request):
* a field `extension`: the DNS extension associated
* a field `domain_ip_start`: the first ip to use for this type
* a field `domain_ip_stop`: the last ip to use for this type
* a field `prefix_v6`: `null` if IPv6 is deactivated else the prefix to use
* a field `prefix_v6`: `null` if IPv6 is deactivated else the
prefix to use
* a field `ouverture_ports_tcp_in`: the policy for TCP IN ports
* a field `ouverture_ports_tcp_out`: the policy for TCP OUT ports
* a field `ouverture_ports_udp_in`: the policy for UDP IN ports
* a field `ouverture_ports_udp_out`: the policy for UDP OUT ports
"""
ip_type = IpType.objects.all().select_related('extension')
seria = TypeSerializer(ip_type, many=True)
return JSONSuccess(seria.data)
@ -223,7 +277,7 @@ def dns_corresp(request):
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def dns_mx(request):
def dns_mx(_request):
"""The list of MX record to add to the DNS
Returns:
@ -233,9 +287,13 @@ def dns_mx(request):
* a field `zone`: the extension for the concerned zone
* a field `priority`: the priority to use
* a field `name`: the name of the target
* a field `mx_entry`: the full entry to add in the DNS for this MX record
* 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)
return JSONSuccess(seria.data)
@ -244,7 +302,7 @@ def dns_mx(request):
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def dns_ns(request):
def dns_ns(_request):
"""The list of NS record to add to the DNS
Returns:
@ -253,9 +311,18 @@ def dns_ns(request):
* a list of dictionnaries (one for each NS record) containing:
* a field `zone`: the extension for the concerned zone
* a field `ns`: the DNS name for the NS server targeted
* a field `ns_entry`: the full entry to add in the DNS for this NS record
* 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)
return JSONSuccess(seria.data)
@ -264,7 +331,7 @@ def dns_ns(request):
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def dns_txt(request):
def dns_txt(_request):
"""The list of TXT record to add to the DNS
Returns:
@ -274,8 +341,10 @@ def dns_txt(request):
* a field `zone`: the extension for the concerned zone
* a field `field1`: the first field in the record (target)
* a field `field2`: the second field in the record (value)
* a field `txt_entry`: the full entry to add in the DNS for this TXT record
* a field `txt_entry`: the full entry to add in the DNS for this
TXT record
"""
txt = Txt.objects.all().select_related('zone')
seria = TxtSerializer(txt, many=True)
return JSONSuccess(seria.data)
@ -285,7 +354,7 @@ def dns_txt(request):
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def dns_srv(request):
def dns_srv(_request):
"""The list of SRV record to add to the DNS
Returns:
@ -300,9 +369,13 @@ def dns_srv(request):
* a field `weight`: the weight for same priority entries
* a field `port`: the port targeted
* a field `target`: the interface targeted by this service
* a field `srv_entry`: the full entry to add in the DNS for this SRV record
* 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)
return JSONSuccess(seria.data)
@ -311,8 +384,8 @@ def dns_srv(request):
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def dns_zones(request):
"""The list of the zones managed
def dns_zones(_request):
"""The list of the zones managed
Returns:
GET:
@ -320,21 +393,27 @@ def dns_zones(request):
* a list of dictionnaries (one for each zone) containing:
* a field `name`: the extension for the zone
* a field `origin`: the server IPv4 for the orgin of the zone
* a field `origin_v6`: `null` if ipv6 is deactivated else the server IPv6 for the origin of the zone
* a field `origin_v6`: `null` if ipv6 is deactivated else the
server IPv6 for the origin of the zone
* a field `soa` containing:
* a field `mail` containing the mail to contact in case of problem with the zone
* a field `param` containing the full soa paramters to use in the DNS for this zone
* a field `zone_entry`: the full entry to add in the DNS for the origin of the zone
* a field `mail` containing the mail to contact in case of
problem with the zone
* a field `param` containing the full soa paramters to use
in the DNS for this zone
* a field `zone_entry`: the full entry to add in the DNS for the
origin of the zone
"""
zones = Extension.objects.all().select_related('origin')
seria = ExtensionSerializer(zones, many=True)
return JSONSuccess(seria.data)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def firewall_ouverture_ports(request):
def firewall_ouverture_ports(_request):
"""The list of the ports authorized to be openned by the firewall
Returns:
@ -359,37 +438,73 @@ def firewall_ouverture_ports(request):
* a field `udp_out` containing:
* a list of port number where ipv6 udp out should be ok
"""
r = {'ipv4':{}, 'ipv6':{}}
for o in OuverturePortList.objects.all().prefetch_related('ouvertureport_set').prefetch_related('interface_set', 'interface_set__ipv4'):
r = {'ipv4': {}, 'ipv6': {}}
for o in (OuverturePortList.objects.all()
.prefetch_related('ouvertureport_set')
.prefetch_related('interface_set', 'interface_set__ipv4')):
pl = {
"tcp_in":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.TCP, io=OuverturePort.IN))),
"tcp_out":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.TCP, io=OuverturePort.OUT))),
"udp_in":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.UDP, io=OuverturePort.IN))),
"udp_out":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.UDP, io=OuverturePort.OUT))),
"tcp_in": set(map(
str,
o.ouvertureport_set.filter(
protocole=OuverturePort.TCP,
io=OuverturePort.IN
)
)),
"tcp_out": set(map(
str,
o.ouvertureport_set.filter(
protocole=OuverturePort.TCP,
io=OuverturePort.OUT
)
)),
"udp_in": set(map(
str,
o.ouvertureport_set.filter(
protocole=OuverturePort.UDP,
io=OuverturePort.IN
)
)),
"udp_out": set(map(
str,
o.ouvertureport_set.filter(
protocole=OuverturePort.UDP,
io=OuverturePort.OUT
)
)),
}
for i in filter_active_interfaces(o.interface_set):
if i.may_have_port_open():
d = r['ipv4'].get(i.ipv4.ipv4, {})
d["tcp_in"] = d.get("tcp_in",set()).union(pl["tcp_in"])
d["tcp_out"] = d.get("tcp_out",set()).union(pl["tcp_out"])
d["udp_in"] = d.get("udp_in",set()).union(pl["udp_in"])
d["udp_out"] = d.get("udp_out",set()).union(pl["udp_out"])
d["tcp_in"] = (d.get("tcp_in", set())
.union(pl["tcp_in"]))
d["tcp_out"] = (d.get("tcp_out", set())
.union(pl["tcp_out"]))
d["udp_in"] = (d.get("udp_in", set())
.union(pl["udp_in"]))
d["udp_out"] = (d.get("udp_out", set())
.union(pl["udp_out"]))
r['ipv4'][i.ipv4.ipv4] = d
if i.ipv6():
for ipv6 in i.ipv6():
d = r['ipv6'].get(ipv6.ipv6, {})
d["tcp_in"] = d.get("tcp_in",set()).union(pl["tcp_in"])
d["tcp_out"] = d.get("tcp_out",set()).union(pl["tcp_out"])
d["udp_in"] = d.get("udp_in",set()).union(pl["udp_in"])
d["udp_out"] = d.get("udp_out",set()).union(pl["udp_out"])
d["tcp_in"] = (d.get("tcp_in", set())
.union(pl["tcp_in"]))
d["tcp_out"] = (d.get("tcp_out", set())
.union(pl["tcp_out"]))
d["udp_in"] = (d.get("udp_in", set())
.union(pl["udp_in"]))
d["udp_out"] = (d.get("udp_out", set())
.union(pl["udp_out"]))
r['ipv6'][ipv6.ipv6] = d
return JSONSuccess(r)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def dhcp_mac_ip(request):
def dhcp_mac_ip(_request):
"""The list of all active interfaces with all the associated infos
(MAC, IP, IpType, DNS name and associated zone extension)
@ -402,8 +517,10 @@ def dhcp_mac_ip(request):
* a field `ip_type`: the name of the IpType of this interface
* a field `mac_address`: the MAC of this interface
* a field `domain`: the DNS name for this interface
* a field `extension`: the extension for the DNS zone of this interface
* a field `extension`: the extension for the DNS zone of this
interface
"""
interfaces = all_active_assigned_interfaces()
seria = InterfaceSerializer(interfaces, many=True)
return JSONSuccess(seria.data)
@ -413,7 +530,7 @@ def dhcp_mac_ip(request):
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def mailing_standard(request):
def mailing_standard(_request):
"""All the available standard mailings.
Returns:
@ -422,15 +539,17 @@ def mailing_standard(request):
* a list of dictionnaries (one for each mailing) containing:
* a field `name`: the name of a mailing
"""
return JSONSuccess([
{'name': 'adherents'}
])
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def mailing_standard_ml_members(request):
def mailing_standard_ml_members(_request, ml_name):
"""All the members of a specific standard mailing
Returns:
@ -442,6 +561,7 @@ def mailing_standard_ml_members(request):
* a field `surname`: the surname of the member
* a field `pseudo`: the pseudo of the member
"""
# All with active connextion
if ml_name == 'adherents':
members = all_has_access().values('email').distinct()
@ -456,7 +576,7 @@ def mailing_standard_ml_members(request):
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def mailing_club(request):
def mailing_club(_request):
"""All the available club mailings.
Returns:
@ -465,15 +585,17 @@ def mailing_club(request):
* a list of dictionnaries (one for each mailing) containing:
* a field `name` indicating the name of a mailing
"""
clubs = Club.objects.filter(mailing=True).values('pseudo')
seria = MailingSerializer(clubs, many=True)
return JSONSuccess(seria.data)
@csrf_exempt
@login_required
@permission_required('machines.serveur')
@accept_method(['GET'])
def mailing_club_ml_members(request):
def mailing_club_ml_members(_request, ml_name):
"""All the members of a specific club mailing
Returns:
@ -485,6 +607,7 @@ def mailing_club_ml_members(request):
* a field `surname`: the surname of the member
* a field `pseudo`: the pseudo of the member
"""
try:
club = Club.objects.get(mailing=True, pseudo=ml_name)
except Club.DoesNotExist:

View file

@ -20,5 +20,8 @@
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""cotisations
The app in charge of all the members's cotisations
"""
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 _
def can_view(user):
"""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).
"""
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
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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

View file

@ -20,7 +20,7 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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.
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.db.models import Q
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_lazy as _l
from .models import Article, Paiement, Facture, Banque
from preferences.models import OptionalUser
from users.models import User
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):
"""
@ -109,12 +108,16 @@ class CreditSoldeForm(NewFactureForm):
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(
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"),
required=True
)
@ -127,10 +130,13 @@ class SelectUserArticleForm(FormRevMixin, 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(
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"),
required=True
)
@ -140,6 +146,7 @@ class SelectClubArticleForm(Form):
required=True
)
# TODO : change Facture to Invoice
class NewFactureFormPdf(Form):
"""
@ -147,9 +154,18 @@ class NewFactureFormPdf(Form):
"""
paid = forms.BooleanField(label=_l("Paid"), required=False)
# 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
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
class EditFactureForm(FieldPermissionFormMixin, NewFactureForm):
@ -295,6 +311,11 @@ class NewFactureSoldeForm(NewFactureForm):
"""
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(NewFactureSoldeForm, self).__init__(
*args,
prefix=prefix,
**kwargs
)
self.fields['cheque'].required = False
self.fields['banque'].required = False
self.fields['cheque'].label = _('Cheque number')
@ -313,7 +334,6 @@ class NewFactureSoldeForm(NewFactureForm):
# TODO : change paiement to payment and baque to bank
fields = ['paiement', 'banque']
def clean(self):
cleaned_data = super(NewFactureSoldeForm, self).clean()
# TODO : change paiement to payment
@ -342,7 +362,7 @@ class RechargeForm(FormRevMixin, Form):
value = forms.FloatField(
label=_l("Amount"),
min_value=0.01,
validators = []
validators=[]
)
def __init__(self, *args, **kwargs):
@ -350,6 +370,10 @@ class RechargeForm(FormRevMixin, Form):
super(RechargeForm, self).__init__(*args, **kwargs)
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']
if value < OptionalUser.get_cached_value('min_online_payment'):
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(
_("Requested amount is too high. Your balance can't exceed \
%(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 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.dispatch import receiver
from django.forms import ValidationError
from django.core.validators import MinValueValidator
from django.db.models import Max
from django.utils import timezone
from machines.models import regen
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _l
from machines.models import regen
from re2o.field_permissions import FieldPermissionModelMixin
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
something (it can be multiple article paid at once).
An invoice is linked to :
* one or more purchases (one for each article sold that time)
* a user (the one who bought those articles)
@ -105,11 +104,16 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
abstract = False
permissions = (
# TODO : change facture to invoice
('change_facture_control', _l("Can change the \"controlled\" state")),
# TODO : seems more likely to be call create_facture_pdf or create_invoice_pdf
('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")),
('change_facture_control',
_l("Can change the \"controlled\" state")),
# TODO : seems more likely to be call create_facture_pdf
# or create_invoice_pdf
('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_plural = _l("Invoices")
@ -159,11 +163,14 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
def can_edit(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.change_facture'):
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]:
return False, _("You don't have the right to edit this user's invoices.")
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.")
elif not user_request.has_perm('cotisations.change_all_facture') and \
not self.user.can_edit(user_request, *args, **kwargs)[0]:
return False, _("You don't have the right to edit this user's "
"invoices.")
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:
return True, None
@ -171,33 +178,45 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
if not user_request.has_perm('cotisations.delete_facture'):
return False, _("You don't have the right to delete an invoice.")
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:
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:
return True, None
def can_view(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.view_facture') and\
self.user != user_request:
return False, _("You don't have the right to see someone else's invoices history.")
def can_view(self, user_request, *_args, **_kwargs):
if not user_request.has_perm('cotisations.view_facture') and \
self.user != user_request:
return False, _("You don't have the right to see someone else's "
"invoices history.")
elif not self.valid:
return False, _("The invoice has been invalidated.")
else:
return True, None
@staticmethod
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.")
def can_change_control(user_request, *_args, **_kwargs):
""" 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
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.")
def can_change_pdf(user_request, *_args, **_kwargs):
""" 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):
super(Facture, self).__init__(*args, **kwargs)
self.field_permissions = {
'control' : self.can_change_control,
'control': self.can_change_control,
}
def __str__(self):
@ -205,7 +224,7 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
@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.
"""
@ -215,7 +234,7 @@ def facture_post_save(sender, **kwargs):
@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.
"""
@ -228,7 +247,7 @@ class Vente(RevMixin, AclMixin, models.Model):
"""
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.
It's reprensentated by:
* an amount (the number of items sold)
* an invoice (whose the purchase is part of)
@ -289,7 +308,6 @@ class Vente(RevMixin, AclMixin, models.Model):
verbose_name = _l("Purchase")
verbose_name_plural = _l("Purchases")
# TODO : change prix_total to total_price
def prix_total(self):
"""
@ -323,11 +341,13 @@ class Vente(RevMixin, AclMixin, models.Model):
facture__in=Facture.objects.filter(
user=self.facture.user
).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(
date_start__lt=date_start
).aggregate(Max('date_end'))['date_end__max']
elif self.type_cotisation=="Adhesion":
elif self.type_cotisation == "Adhesion":
end_cotisation = self.facture.user.end_adhesion()
else:
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):
if not user_request.has_perm('cotisations.change_vente'):
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]:
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.")
elif (not user_request.has_perm('cotisations.change_all_facture') and
not self.facture.user.can_edit(
user_request, *args, **kwargs
)[0]):
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:
return True, None
@ -369,16 +394,19 @@ class Vente(RevMixin, AclMixin, models.Model):
if not user_request.has_perm('cotisations.delete_vente'):
return False, _("You don't have the right to delete a purchase.")
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:
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:
return True, None
def can_view(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.view_vente') and\
self.facture.user != user_request:
return False, _("You don't have the right to see someone else's purchase history.")
def can_view(self, user_request, *_args, **_kwargs):
if (not user_request.has_perm('cotisations.view_vente') and
self.facture.user != user_request):
return False, _("You don't have the right to see someone "
"else's purchase history.")
else:
return True, None
@ -388,7 +416,7 @@ class Vente(RevMixin, AclMixin, models.Model):
# TODO : change vente to purchase
@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
LDAP user when a purchase has been saved.
@ -406,7 +434,7 @@ def vente_post_save(sender, **kwargs):
# TODO : change vente to purchase
@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.
"""
@ -418,12 +446,14 @@ def vente_post_delete(sender, **kwargs):
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:
* a name
* 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 type of user (indicating what kind of user can buy this article)
"""
@ -513,8 +543,8 @@ class Banque(RevMixin, AclMixin, models.Model):
permissions = (
('view_banque', _l("Can see a bank's details")),
)
verbose_name=_l("Bank")
verbose_name_plural=_l("Banks")
verbose_name = _l("Bank")
verbose_name_plural = _l("Banks")
def __str__(self):
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
and an account number in related models)
"""
PAYMENT_TYPES = (
(0, _l("Standard")),
(1, _l("Cheque")),
@ -619,27 +649,32 @@ class Cotisation(RevMixin, AclMixin, models.Model):
('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'):
return False, _("You don't have the right to edit a cotisation.")
elif not user_request.has_perm('cotisations.change_all_cotisation') and\
(self.vente.facture.control or not self.vente.facture.valid):
return False, _("You don't have the right to edit a cotisation already controlled or invalidated.")
elif not user_request.has_perm('cotisations.change_all_cotisation') \
and (self.vente.facture.control or
not self.vente.facture.valid):
return False, _("You don't have the right to edit a cotisation "
"already controlled or invalidated.")
else:
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'):
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:
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:
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\
self.vente.facture.user != user_request:
return False, _("You don't have the right to see someone else's cotisation history.")
self.vente.facture.user != user_request:
return False, _("You don't have the right to see someone else's "
"cotisation history.")
else:
return True, None
@ -648,7 +683,7 @@ class Cotisation(RevMixin, AclMixin, models.Model):
@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
cotisation. Indeed the membership status may have changed.
@ -659,13 +694,11 @@ def cotisation_post_save(sender, **kwargs):
regen('mailing')
# TODO : should be name cotisation_post_delete
@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
cotisation. Indeed the membership status may have changed.
"""
cotisation = kwargs['instance']
regen('mac_ip_list')
regen('mailing')

View file

@ -2,6 +2,9 @@
Here are defined some views dedicated to online payement.
"""
from collections import OrderedDict
from django.urls import reverse
from django.shortcuts import redirect, get_object_or_404
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.http import HttpResponse, HttpResponseBadRequest
from collections import OrderedDict
from preferences.models import AssoOption
from .models import Facture
from .payment_utils.comnpay import Payment as ComnpayPayment
@csrf_exempt
@login_required
def accept_payment(request, factureid):
@ -30,7 +32,10 @@ def accept_payment(request, factureid):
'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
@ -43,7 +48,11 @@ def refuse_payment(request):
request,
_("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
def ipn(request):
@ -105,7 +114,7 @@ def comnpay(facture, request):
str(AssoOption.get_cached_value('payment_pass')),
'https://' + host + reverse(
'cotisations:accept_payment',
kwargs={'factureid':facture.id}
kwargs={'factureid': facture.id}
),
'https://' + host + reverse('cotisations:refuse_payment'),
'https://' + host + reverse('cotisations:ipn'),
@ -113,20 +122,20 @@ def comnpay(facture, request):
"D"
)
r = {
'action' : 'https://secure.homologation.comnpay.com',
'method' : 'POST',
'content' : p.buildSecretHTML(
'action': 'https://secure.homologation.comnpay.com',
'method': 'POST',
'content': p.buildSecretHTML(
"Rechargement du solde",
facture.prix(),
idTransaction=str(facture.id)
),
'amount' : facture.prix,
'amount': facture.prix,
}
return r
# The payment systems supported by re2o
PAYMENT_SYSTEM = {
'COMNPAY' : comnpay,
'NONE' : None
'COMNPAY': comnpay,
'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
from random import randrange
import base64
import hashlib
from collections import OrderedDict
from itertools import chain
class Payment():
""" The class representing a transaction with all the functions
used during the negociation
"""
vad_number = ""
secret_key = ""
urlRetourOK = ""
urlRetourNOK = ""
urlIPN = ""
source = ""
typeTr = "D"
def __init__(self, vad_number = "", secret_key = "", urlRetourOK = "", urlRetourNOK = "", urlIPN = "", source="", typeTr="D"):
def __init__(self, vad_number="", secret_key="", urlRetourOK="",
urlRetourNOK="", urlIPN="", source="", typeTr="D"):
self.vad_number = vad_number
self.secret_key = secret_key
self.urlRetourOK = urlRetourOK
@ -23,46 +24,63 @@ class Payment():
self.urlIPN = urlIPN
self.source = source
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 == "":
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:
self.idTransaction = idTransaction
array_tpe = OrderedDict(
montant= str(montant),
idTPE= self.vad_number,
idTransaction= self.idTransaction,
devise= "EUR",
lang= 'fr',
nom_produit= produit,
source= self.source,
urlRetourOK= self.urlRetourOK,
urlRetourNOK= self.urlRetourNOK,
typeTr= str(self.typeTr)
array_tpe = OrderedDict(
montant=str(montant),
idTPE=self.vad_number,
idTransaction=self.idTransaction,
devise="EUR",
lang='fr',
nom_produit=produit,
source=self.source,
urlRetourOK=self.urlRetourOK,
urlRetourNOK=self.urlRetourNOK,
typeTr=str(self.typeTr)
)
if self.urlIPN!="":
if self.urlIPN != "":
array_tpe['urlIPN'] = self.urlIPN
array_tpe['key'] = self.secret_key;
strWithKey = base64.b64encode(bytes('|'.join(array_tpe.values()), 'utf-8'))
array_tpe['key'] = self.secret_key
strWithKey = base64.b64encode(bytes(
'|'.join(array_tpe.values()),
'utf-8'
))
del array_tpe["key"]
array_tpe['sec'] = hashlib.sha512(strWithKey).hexdigest()
ret = ""
for key in array_tpe:
ret += '<input type="hidden" name="'+key+'" value="'+array_tpe[key]+'"/>'
ret += '<input type="hidden" name="{k}" value="{v}"/>'.format(
k=key,
v=array_tpe[key]
)
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:
sec = 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()
else:
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">
{% csrf_token %}
{% if articlesformset %}
<h3>{% trans "Invoice's articles" %}</h3>
<div id="form_set" class="form-group">
{{ 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>
{% endblocktrans %}
</p>
{% endif %}
{% bootstrap_form factureform %}
{% bootstrap_button action_name button_type='submit' icon='star' %}
</form>
{% if articlesformset %}
<script type="text/javascript">
var prices = {};
{% for article in articles %}
@ -133,6 +135,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
update_price();
});
</script>
{% endif %}
{% endblock %}

View file

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

View file

@ -24,40 +24,44 @@ Module in charge of rendering some LaTex templates.
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 import Context
from django.http import HttpResponse
from django.conf import settings
from django.utils.text import slugify
import tempfile
from subprocess import Popen, PIPE
import os
TEMP_PREFIX = getattr(settings, 'TEX_TEMP_PREFIX', 'render_tex-')
CACHE_PREFIX = getattr(settings, 'TEX_CACHE_PREFIX', 'render-tex')
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
date, the user, the articles, the prices, ...
"""
filename = '_'.join([
'invoice',
slugify(ctx['asso_name']),
slugify(ctx['recipient_name']),
str(ctx['DATE'].year),
str(ctx['DATE'].month),
str(ctx['DATE'].day),
'invoice',
slugify(ctx.get('asso_name', "")),
slugify(ctx.get('recipient_name', "")),
str(ctx.get('DATE', datetime.now()).year),
str(ctx.get('DATE', datetime.now()).month),
str(ctx.get('DATE', datetime.now()).day),
])
r = render_tex(request, 'cotisations/factures.tex', ctx)
r['Content-Disposition'] = ''.join(['attachment; filename="',filename,'.pdf"'])
r = render_tex(_request, 'cotisations/factures.tex', ctx)
r['Content-Disposition'] = 'attachment; filename="{name}.pdf"'.format(
name=filename
)
return r
def render_tex(request, template, ctx={}):
def render_tex(_request, template, ctx={}):
"""
Creates a PDF from a LaTex templates using pdflatex.
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)
template = get_template(template)
rendered_tpl = template.render(context).encode('utf-8')
with tempfile.TemporaryDirectory() as tempdir:
for i in range(2):
process = Popen(
['pdflatex', '-output-directory', tempdir],
stdin = PIPE,
stdout = PIPE,
stdin=PIPE,
stdout=PIPE,
)
process.communicate(rendered_tpl)
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
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""cotisations.urls
The defined URLs for the Cotisations app
"""
from __future__ import unicode_literals
@ -29,107 +32,131 @@ from . import views
from . import payment
urlpatterns = [
url(r'^new_facture/(?P<userid>[0-9]+)$',
url(
r'^new_facture/(?P<userid>[0-9]+)$',
views.new_facture,
name='new-facture'
),
url(r'^edit_facture/(?P<factureid>[0-9]+)$',
),
url(
r'^edit_facture/(?P<factureid>[0-9]+)$',
views.edit_facture,
name='edit-facture'
),
url(r'^del_facture/(?P<factureid>[0-9]+)$',
),
url(
r'^del_facture/(?P<factureid>[0-9]+)$',
views.del_facture,
name='del-facture'
),
url(r'^facture_pdf/(?P<factureid>[0-9]+)$',
),
url(
r'^facture_pdf/(?P<factureid>[0-9]+)$',
views.facture_pdf,
name='facture-pdf'
),
url(r'^new_facture_pdf/$',
),
url(
r'^new_facture_pdf/$',
views.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,
name='credit-solde'
),
url(r'^add_article/$',
),
url(
r'^add_article/$',
views.add_article,
name='add-article'
),
url(r'^edit_article/(?P<articleid>[0-9]+)$',
),
url(
r'^edit_article/(?P<articleid>[0-9]+)$',
views.edit_article,
name='edit-article'
),
url(r'^del_article/$',
),
url(
r'^del_article/$',
views.del_article,
name='del-article'
),
url(r'^add_paiement/$',
),
url(
r'^add_paiement/$',
views.add_paiement,
name='add-paiement'
),
url(r'^edit_paiement/(?P<paiementid>[0-9]+)$',
),
url(
r'^edit_paiement/(?P<paiementid>[0-9]+)$',
views.edit_paiement,
name='edit-paiement'
),
url(r'^del_paiement/$',
),
url(
r'^del_paiement/$',
views.del_paiement,
name='del-paiement'
),
url(r'^add_banque/$',
),
url(
r'^add_banque/$',
views.add_banque,
name='add-banque'
),
url(r'^edit_banque/(?P<banqueid>[0-9]+)$',
),
url(
r'^edit_banque/(?P<banqueid>[0-9]+)$',
views.edit_banque,
name='edit-banque'
),
url(r'^del_banque/$',
),
url(
r'^del_banque/$',
views.del_banque,
name='del-banque'
),
url(r'^index_article/$',
),
url(
r'^index_article/$',
views.index_article,
name='index-article'
),
url(r'^index_banque/$',
),
url(
r'^index_banque/$',
views.index_banque,
name='index-banque'
),
url(r'^index_paiement/$',
),
url(
r'^index_paiement/$',
views.index_paiement,
name='index-paiement'
),
),
url(
r'history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
re2o.views.history,
name='history',
kwargs={'application':'cotisations'},
kwargs={'application': 'cotisations'},
),
url(r'^control/$',
url(
r'^control/$',
views.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,
name='new_facture_solde'
),
url(r'^recharge/$',
),
url(
r'^recharge/$',
views.recharge,
name='recharge'
),
url(r'^payment/accept/(?P<factureid>[0-9]+)$',
),
url(
r'^payment/accept/(?P<factureid>[0-9]+)$',
payment.accept_payment,
name='accept_payment'
),
url(r'^payment/refuse/$',
),
url(
r'^payment/refuse/$',
payment.refuse_payment,
name='refuse_payment'
),
url(r'^payment/ipn/$',
),
url(
r'^payment/ipn/$',
payment.ipn,
name='ipn'
),
),
url(r'^$', views.index, name='index'),
]

View file

@ -23,22 +23,23 @@
# App de gestion des users pour re2o
# Goulven Kermarec, Gabriel Détraz
# Gplv2
"""cotisations.views
The different views used in the Cotisations module
"""
from __future__ import unicode_literals
import os
from django.urls import reverse
from django.shortcuts import render, redirect
from django.core.validators import MaxValueValidator
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.db.models import ProtectedError
from django.db import transaction
from django.db.models import Q
from django.forms import modelformset_factory, formset_factory
from django.utils import timezone
from 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
from reversion import revisions as reversion
from users.models import User
@ -70,14 +71,12 @@ from .forms import (
SelectUserArticleForm,
SelectClubArticleForm,
CreditSoldeForm,
NewFactureSoldeForm,
RechargeForm
)
from . import payment as online_payment
from .tex import render_invoice
@login_required
@can_create(Facture)
@can_edit(User)
@ -102,9 +101,13 @@ def new_facture(request, user, userid):
# Building the invocie form and the article formset
invoice_form = NewFactureForm(request.POST or None, instance=invoice)
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:
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():
new_invoice_instance = invoice_form.save(commit=False)
@ -118,15 +121,18 @@ def new_facture(request, user, userid):
# the authorized minimum (negative_balance)
if user_balance:
# TODO : change Paiement to Payment
if new_invoice_instance.paiement == Paiement.objects.get_or_create(
moyen='solde'
)[0]:
if new_invoice_instance.paiement == (
Paiement.objects.get_or_create(moyen='solde')[0]
):
total_price = 0
for art_item in articles:
if art_item.cleaned_data:
total_price += art_item.cleaned_data['article']\
.prix*art_item.cleaned_data['quantity']
if float(user.solde) - float(total_price) < negative_balance:
total_price += (
art_item.cleaned_data['article'].prix *
art_item.cleaned_data['quantity']
)
if (float(user.solde) - float(total_price)
< negative_balance):
messages.error(
request,
_("Your balance is too low for this operation.")
@ -194,7 +200,7 @@ def new_facture(request, user, userid):
@can_change(Facture, 'pdf')
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
point of view.
"""
@ -205,9 +211,13 @@ def new_facture_pdf(request):
# Building the invocie form and the article formset
invoice_form = NewFactureFormPdf(request.POST or None)
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:
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():
# Get the article list and build an list out of it
# contiaining (article_name, article_price, quantity, total_price)
@ -253,7 +263,7 @@ def new_facture_pdf(request):
# TODO : change facture to invoice
@login_required
@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
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
@login_required
@can_edit(Facture)
def edit_facture(request, facture, factureid):
def edit_facture(request, facture, **_kwargs):
"""
View used to edit an existing invoice.
Articles can be added or remove to the invoice and quantity
can be set as desired. This is also the view used to invalidate
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)
purchase_form_set = modelformset_factory(
Vente,
@ -311,7 +325,10 @@ def edit_facture(request, facture, factureid):
extra=0,
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.changed_data:
invoice_form.save()
@ -330,7 +347,7 @@ def edit_facture(request, facture, factureid):
# TODO : change facture to invoice
@login_required
@can_delete(Facture)
def del_facture(request, facture, factureid):
def del_facture(request, facture, **_kwargs):
"""
View used to delete an existing invocie.
"""
@ -351,7 +368,7 @@ def del_facture(request, facture, factureid):
@login_required
@can_create(Facture)
@can_edit(User)
def credit_solde(request, user, userid):
def credit_solde(request, user, **_kwargs):
"""
View used to edit the balance of a user.
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 form({
'factureform': facture,
'factureform': invoice,
'action_name': _("Edit")
}, 'cotisations/facture.html', request)
@ -385,7 +402,7 @@ def credit_solde(request, user, userid):
def add_article(request):
"""
View used to add an article.
.. 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
later, it won't change the invoice. That is really important to keep
@ -408,7 +425,7 @@ def add_article(request):
@login_required
@can_edit(Article)
def edit_article(request, article_instance, articleid):
def edit_article(request, article_instance, **_kwargs):
"""
View used to edit an article.
"""
@ -472,7 +489,7 @@ def add_paiement(request):
# TODO : chnage paiement to Payment
@login_required
@can_edit(Paiement)
def edit_paiement(request, paiement_instance, paiementid):
def edit_paiement(request, paiement_instance, **_kwargs):
"""
View used to edit a payment method.
"""
@ -550,7 +567,7 @@ def add_banque(request):
# TODO : change banque to bank
@login_required
@can_edit(Banque)
def edit_banque(request, banque_instance, banqueid):
def edit_banque(request, banque_instance, **_kwargs):
"""
View used to edit a bank.
"""
@ -613,7 +630,8 @@ def control(request):
View used to control the invoices all at once.
"""
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,
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)
)
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:
article_formset = formset_factory(SelectUserArticleForm)(request.POST or None)
article_formset = formset_factory(SelectUserArticleForm)(
request.POST or None
)
if article_formset.is_valid():
articles = article_formset
@ -826,7 +848,9 @@ def recharge(request):
refill_form = RechargeForm(request.POST or None, user=request.user)
if refill_form.is_valid():
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.valid = False
invoice.save()
@ -837,7 +861,9 @@ def recharge(request):
number=1
)
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 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
# se veut agnostique au réseau considéré, de manière à être installable en
# 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
"""
import os
import sys
import logging
import netaddr
import radiusd # Module magique freeradius (radiusd.py is dummy)
import binascii
import hashlib
import os, sys
import radiusd # Module magique freeradius (radiusd.py is dummy)
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/"
# This is so Django knows where to find stuff.
@ -52,28 +57,17 @@ sys.path.append(proj_path)
os.chdir(proj_path)
# This is so models get loaded.
from django.core.wsgi import 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()
VLAN_NOK = options.vlan_decision_nok.vlan_id
VLAN_OK = options.vlan_decision_ok.vlan_id
#: Serveur radius de test (pas la prod)
TEST_SERVER = bool(os.getenv('DBG_FREERADIUS', False))
## -*- Logging -*-
# Logging
class RadiusdHandler(logging.Handler):
"""Handler de logs pour freeradius"""
@ -87,6 +81,7 @@ class RadiusdHandler(logging.Handler):
rad_sig = radiusd.L_DBG
radiusd.radlog(rad_sig, record.msg)
# Initialisation d'un logger (pour logguer unifié)
logger = logging.getLogger('auth.py')
logger.setLevel(logging.DEBUG)
@ -95,10 +90,11 @@ handler = RadiusdHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
def radius_event(fun):
"""Décorateur pour les fonctions d'interfaces avec radius.
Une telle fonction prend un uniquement argument, qui est une liste de tuples
(clé, valeur) et renvoie un triplet dont les composantes sont :
Une telle fonction prend un uniquement argument, qui est une liste de
tuples (clé, valeur) et renvoie un triplet dont les composantes sont :
* le code de retour (voir radiusd.RLM_MODULE_* )
* un tuple de couples (clé, valeur) pour les valeurs de réponse (accès ok
et autres trucs du genre)
@ -109,7 +105,8 @@ def radius_event(fun):
tuples en entrée en un dictionnaire."""
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
else:
data = dict()
@ -118,8 +115,8 @@ def radius_event(fun):
# Ex: Calling-Station-Id: "une_adresse_mac"
data[key] = value.replace('"', '')
try:
# TODO s'assurer ici que les tuples renvoyés sont bien des (str,str)
# rlm_python ne digère PAS les unicodes
# TODO s'assurer ici que les tuples renvoyés sont bien des
# (str,str) : rlm_python ne digère PAS les unicodes
return fun(data)
except Exception as err:
logger.error('Failed %r on data %r' % (err, auth_data))
@ -128,7 +125,6 @@ def radius_event(fun):
return new_f
@radius_event
def instantiate(*_):
"""Utile pour initialiser les connexions ldap une première fois (otherwise,
@ -137,12 +133,15 @@ def instantiate(*_):
if TEST_SERVER:
logger.info(u'DBG_FREERADIUS is enabled')
@radius_event
def authorize(data):
"""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 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
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 = user.split('@', 1)[0]
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(user.encode('utf-8'))
if not result:
return radiusd.RLM_MODULE_REJECT
else:
return (radiusd.RLM_MODULE_UPDATED,
(),
(
(str("NT-Password"), str(password)),
),
return (
radiusd.RLM_MODULE_UPDATED,
(),
(
(str("NT-Password"), str(password)),
),
)
else:
return (radiusd.RLM_MODULE_UPDATED,
(),
(
("Auth-Type", "Accept"),
),
return (
radiusd.RLM_MODULE_UPDATED,
(),
(
("Auth-Type", "Accept"),
),
)
@radius_event
def post_auth(data):
""" Function called after the user is authenticated
"""
nas = data.get('NAS-IP-Address', data.get('NAS-Identifier', None))
nas_instance = find_nas_from_request(nas)
# Toutes les reuquètes non proxifiées
@ -187,61 +196,89 @@ def post_auth(data):
return radiusd.RLM_MODULE_OK
nas_type = Nas.objects.filter(nas_type=nas_instance.type).first()
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
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
# Si il s'agit d'un switch
if hasattr(nas_machine, 'switch'):
port = data.get('NAS-Port-Id', data.get('NAS-Port', None))
#Pour les infrastructures possédant des switchs Juniper :
#On vérifie si le switch fait partie d'un stack Juniper
# Pour les infrastructures possédant des switchs Juniper :
# On vérifie si le switch fait partie d'un stack Juniper
instance_stack = nas_machine.switch.stack
if instance_stack:
# Si c'est le cas, on resélectionne le bon switch dans la stack
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()
# On récupère le numéro du port sur l'output de freeradius. La ligne suivante fonctionne pour cisco, HP et Juniper
nas_machine = (Switch.objects
.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:]
out = decide_vlan_and_register_switch(nas_machine, nas_type, port, mac)
sw_name, room, reason, vlan_id = out
log_message = '(fil) %s -> %s [%s%s]' % \
(sw_name + u":" + port + u"/" + unicode(room), mac, vlan_id, (reason and u': ' + reason).encode('utf-8'))
log_message = '(fil) %s -> %s [%s%s]' % (
sw_name + u":" + port + u"/" + str(room),
mac,
vlan_id,
(reason and u': ' + reason).encode('utf-8')
)
logger.info(log_message)
# Filaire
return (radiusd.RLM_MODULE_UPDATED,
return (
radiusd.RLM_MODULE_UPDATED,
(
("Tunnel-Type", "VLAN"),
("Tunnel-Medium-Type", "IEEE-802"),
("Tunnel-Private-Group-Id", '%d' % int(vlan_id)),
),
()
)
)
else:
return radiusd.RLM_MODULE_OK
# TODO : remove this function
@radius_event
def dummy_fun(_):
"""Do nothing, successfully. (C'est pour avoir un truc à mettre)"""
return radiusd.RLM_MODULE_OK
def detach(_=None):
"""Appelé lors du déchargement du module (enfin, normalement)"""
print "*** goodbye from auth.py ***"
print("*** goodbye from auth.py ***")
return radiusd.RLM_MODULE_OK
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()
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
Utilise pour les authentifications en 802.1X"""
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", '')
if interface:
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:
return (False, u"Machine desactivée", '')
elif not interface.ipv4:
@ -264,7 +304,9 @@ def check_user_machine_and_register(nas_type, username, mac_address):
if nas_type.autocapture_mac:
result, reason = user.autoregister_machine(mac_address, nas_type)
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:
return (False, u'Erreur dans le register mac %s' % reason, '')
else:
@ -273,8 +315,10 @@ def check_user_machine_and_register(nas_type, username, mac_address):
return (False, u"Machine inconnue", '')
def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, mac_address):
"""Fonction de placement vlan pour un switch en radius filaire auth par mac.
def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
mac_address):
"""Fonction de placement vlan pour un switch en radius filaire auth par
mac.
Plusieurs modes :
- nas inconnu, port inconnu : on place sur le vlan par defaut 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 :
- register mac désactivé : VLAN_NOK
- 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
"""
# 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)
port = Port.objects.filter(switch=Switch.objects.filter(machine_ptr=nas_machine), port=port_number).first()
#Si le port est inconnu, on place sur le vlan defaut
port = (Port.objects
.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:
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
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':
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:
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:
return (sw_name, room, u'Chambre non cotisante', VLAN_NOK)
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':
# 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:
room = port.room
# On essaye de register la mac
if not nas_type.autocapture_mac:
return (sw_name, "", u'Machine inconnue', VLAN_NOK)
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:
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:
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:
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():
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:
result, reason = room_user.first().autoregister_machine(mac_address, nas_type)
result, reason = (room_user
.first()
.autoregister_machine(
mac_address,
nas_type
))
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:
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:
room = port.room
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:
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:
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
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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 *

View file

@ -26,6 +26,7 @@
Here are defined some functions to check acl on the application.
"""
def can_view(user):
"""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
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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
register = template.Library()
@register.filter
def classname(obj):
""" Returns the object 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
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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.

View file

@ -46,8 +46,6 @@ from django.db.models import Count, Max
from reversion.models import Revision
from reversion.models import Version, ContentType
from time import time
from users.models import (
User,
ServiceUser,
@ -109,15 +107,6 @@ from re2o.acl import (
from re2o.utils import all_active_assigned_interfaces_count
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
@can_view_app('logs')
@ -227,66 +216,99 @@ def stats_general(request):
_all_baned = all_baned()
_all_whitelisted = all_whitelisted()
_all_active_interfaces_count = all_active_interfaces_count()
_all_active_assigned_interfaces_count = all_active_assigned_interfaces_count()
_all_active_assigned_interfaces_count = \
all_active_assigned_interfaces_count()
stats = [
[["Categorie", "Nombre d'utilisateurs (total club et adhérents)", "Nombre d'adhérents", "Nombre de clubs"], {
'active_users': [
"Users actifs",
User.objects.filter(state=User.STATE_ACTIVE).count(),
Adherent.objects.filter(state=Adherent.STATE_ACTIVE).count(),
Club.objects.filter(state=Club.STATE_ACTIVE).count()],
'inactive_users': [
"Users désactivés",
User.objects.filter(state=User.STATE_DISABLED).count(),
Adherent.objects.filter(state=Adherent.STATE_DISABLED).count(),
Club.objects.filter(state=Club.STATE_DISABLED).count()],
'archive_users': [
"Users archivés",
User.objects.filter(state=User.STATE_ARCHIVE).count(),
Adherent.objects.filter(state=Adherent.STATE_ARCHIVE).count(),
Club.objects.filter(state=Club.STATE_ARCHIVE).count()],
'adherent_users': [
"Cotisant à l'association",
_all_adherent.count(),
_all_adherent.exclude(adherent__isnull=True).count(),
_all_adherent.exclude(club__isnull=True).count()],
'connexion_users': [
"Utilisateurs bénéficiant d'une connexion",
_all_has_access.count(),
_all_has_access.exclude(adherent__isnull=True).count(),
_all_has_access.exclude(club__isnull=True).count()],
'ban_users': [
"Utilisateurs bannis",
_all_baned.count(),
_all_baned.exclude(adherent__isnull=True).count(),
_all_baned.exclude(club__isnull=True).count()],
'whitelisted_user': [
"Utilisateurs bénéficiant d'une connexion gracieuse",
_all_whitelisted.count(),
_all_whitelisted.exclude(adherent__isnull=True).count(),
_all_whitelisted.exclude(club__isnull=True).count()],
'actives_interfaces': [
"Interfaces actives (ayant accès au reseau)",
_all_active_interfaces_count.count(),
_all_active_interfaces_count.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()]
}],
[["Range d'ip", "Vlan", "Nombre d'ip totales", "Ip assignées",
"Ip assignées à une machine active", "Ip non assignées"], ip_dict]
[ # First set of data (about users)
[ # Headers
"Categorie",
"Nombre d'utilisateurs (total club et adhérents)",
"Nombre d'adhérents",
"Nombre de clubs"
],
{ # Data
'active_users': [
"Users actifs",
User.objects.filter(state=User.STATE_ACTIVE).count(),
(Adherent.objects
.filter(state=Adherent.STATE_ACTIVE)
.count()),
Club.objects.filter(state=Club.STATE_ACTIVE).count()
],
'inactive_users': [
"Users désactivés",
User.objects.filter(state=User.STATE_DISABLED).count(),
(Adherent.objects
.filter(state=Adherent.STATE_DISABLED)
.count()),
Club.objects.filter(state=Club.STATE_DISABLED).count()
],
'archive_users': [
"Users archivés",
User.objects.filter(state=User.STATE_ARCHIVE).count(),
(Adherent.objects
.filter(state=Adherent.STATE_ARCHIVE)
.count()),
Club.objects.filter(state=Club.STATE_ARCHIVE).count()
],
'adherent_users': [
"Cotisant à l'association",
_all_adherent.count(),
_all_adherent.exclude(adherent__isnull=True).count(),
_all_adherent.exclude(club__isnull=True).count()
],
'connexion_users': [
"Utilisateurs bénéficiant d'une connexion",
_all_has_access.count(),
_all_has_access.exclude(adherent__isnull=True).count(),
_all_has_access.exclude(club__isnull=True).count()
],
'ban_users': [
"Utilisateurs bannis",
_all_baned.count(),
_all_baned.exclude(adherent__isnull=True).count(),
_all_baned.exclude(club__isnull=True).count()
],
'whitelisted_user': [
"Utilisateurs bénéficiant d'une connexion gracieuse",
_all_whitelisted.count(),
_all_whitelisted.exclude(adherent__isnull=True).count(),
_all_whitelisted.exclude(club__isnull=True).count()
],
'actives_interfaces': [
"Interfaces actives (ayant accès au reseau)",
_all_active_interfaces_count.count(),
(_all_active_interfaces_count
.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})
@ -313,11 +335,26 @@ def stats_models(request):
'whitelist': [Whitelist.PRETTY_NAME, Whitelist.objects.count()]
},
'Cotisations': {
'factures': [Facture._meta.verbose_name.title(), Facture.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()],
'factures': [
Facture._meta.verbose_name.title(),
Facture.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': {
'machine': [Machine.PRETTY_NAME, Machine.objects.count()],
@ -370,12 +407,6 @@ def stats_users(request):
nombre de machines par user, d'etablissements par user,
de moyens de paiements par user, de banque par user,
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 = {
'Utilisateur': {
'Machines': User.objects.annotate(
@ -410,11 +441,7 @@ def stats_users(request):
).order_by('-num')[:10],
},
}
return render(request, 'logs/stats_users.html', {
'stats_list': stats,
'stats_dict': STATS_DICT,
'active_field': onglet
})
return render(request, 'logs/stats_users.html', {'stats_list': stats})
@login_required
@ -432,14 +459,21 @@ def stats_actions(request):
}
return render(request, 'logs/stats_users.html', {'stats_list': stats})
@login_required
@can_view_app('users')
def stats_droits(request):
"""Affiche la liste des droits et les users ayant chaque droit"""
depart=time()
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'))
stats_list = {}
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
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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 *

View file

@ -26,6 +26,7 @@
Here are defined some functions to check acl on the application.
"""
def can_view(user):
"""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
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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
@ -44,74 +47,92 @@ from .models import (
class MachineAdmin(VersionAdmin):
""" Admin view of a Machine object """
pass
class Ipv6ListAdmin(VersionAdmin):
""" Admin view of a Ipv6List object """
pass
class IpTypeAdmin(VersionAdmin):
""" Admin view of a IpType object """
pass
class MachineTypeAdmin(VersionAdmin):
""" Admin view of a MachineType object """
pass
class VlanAdmin(VersionAdmin):
""" Admin view of a Vlan object """
pass
class ExtensionAdmin(VersionAdmin):
""" Admin view of a Extension object """
pass
class SOAAdmin(VersionAdmin):
""" Admin view of a SOA object """
pass
class MxAdmin(VersionAdmin):
""" Admin view of a MX object """
pass
class NsAdmin(VersionAdmin):
""" Admin view of a NS object """
pass
class TxtAdmin(VersionAdmin):
""" Admin view of a TXT object """
pass
class SrvAdmin(VersionAdmin):
""" Admin view of a SRV object """
pass
class NasAdmin(VersionAdmin):
""" Admin view of a Nas object """
pass
class IpListAdmin(VersionAdmin):
""" Admin view of a Ipv4List object """
pass
class OuverturePortAdmin(VersionAdmin):
""" Admin view of a OuverturePort object """
pass
class OuverturePortListAdmin(VersionAdmin):
""" Admin view of a OuverturePortList object """
pass
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):
""" Admin view of a Domain object """
list_display = ('interface_parent', 'name', 'extension', 'cname')
class ServiceAdmin(VersionAdmin):
""" Admin view of a ServiceAdmin object """
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(OuverturePort, OuverturePortAdmin)
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'].empty_label = "Séléctionner un type de machine"
if "ipv4" in self.fields:
self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4"
self.fields['ipv4'].empty_label = ("Assignation automatique de "
"l'ipv4")
self.fields['ipv4'].queryset = IpList.objects.filter(
interface__isnull=True
)
@ -136,10 +137,10 @@ class AliasForm(FormRevMixin, ModelForm):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
user = kwargs.pop('user')
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:
self.fields['extension'].queryset = Extension.objects.filter(
need_infra=False
need_infra=False
)
@ -328,6 +329,7 @@ class MxForm(FormRevMixin, ModelForm):
interface_parent=None
).select_related('extension')
class DelMxForm(FormRevMixin, Form):
"""Suppression d'un ou plusieurs MX"""
mx = forms.ModelMultipleChoiceField(
@ -472,10 +474,14 @@ class ServiceForm(FormRevMixin, ModelForm):
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(ServiceForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['servers'].queryset = Interface.objects.all()\
.select_related('domain__extension')
self.fields['servers'].queryset = (Interface.objects.all()
.select_related(
'domain__extension'
))
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)
if commit:
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
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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 datetime import timedelta
import re
from netaddr import mac_bare, EUI, IPSet, IPRange, IPNetwork, IPAddress
from ipaddress import IPv6Address
from itertools import chain
from netaddr import mac_bare, EUI, IPSet, IPRange, IPNetwork, IPAddress
from django.db import models
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 re2o.field_permissions import FieldPermissionModelMixin
from re2o.mixins import AclMixin, RevMixin
from re2o.mixins import AclMixin, RevMixin
import users.models
import preferences.models
@ -63,23 +66,30 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model):
class Meta:
permissions = (
("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.
:param userid: The id
:return: The user
"""
return Machine.objects.get(pk=machineid)
return cls.objects.get(pk=machineid)
def linked_objects(self):
"""Return linked objects : machine and domain.
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
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
Machine.
@ -90,18 +100,22 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model):
A tuple with a boolean stating if edition is allowed and an
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,
droit particulier correspondant
:param user_request: instance user qui fait l'edition
:return: True ou False avec la raison de l'échec le cas échéant"""
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
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
et n'a pas atteint son quota, et crée bien une machine à lui
: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)
except users.models.User.DoesNotExist:
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 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"
if user != user_request:
return False, u"Vous ne pouvez pas ajouter une machine à un\
autre user que vous sans droit"
return False, (u"Vous ne pouvez pas ajouter une machine à un "
"autre user que vous sans droit")
if user.user_interfaces().count() >= max_lambdauser_interfaces:
return False, u"Vous avez atteint le maximum d'interfaces\
autorisées que vous pouvez créer vous même (%s) "\
% max_lambdauser_interfaces
return False, (u"Vous avez atteint le maximum d'interfaces "
"autorisées que vous pouvez créer vous même "
"(%s) " % max_lambdauser_interfaces)
return True, None
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
:return: True ou False avec la raison le cas échéant"""
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]:
return False, u"Vous ne pouvez pas éditer une machine\
d'un autre user que vous sans droit"
if (not user_request.has_perm('machines.change_interface') or
not self.user.can_edit(
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
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
:return: True ou False avec la raison de l'échec le cas échéant"""
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]:
return False, u"Vous ne pouvez pas éditer une machine\
d'un autre user que vous sans droit"
if (not user_request.has_perm('machines.change_interface') or
not self.user.can_edit(
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
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
machine de soi, soit droit particulier
:param self: instance machine à éditer
:param user_request: instance user qui fait l'edition
: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:
return False, u"Vous n'avez pas droit de voir les machines autre\
que les vôtres"
if (not user_request.has_perm('machines.view_machine') and
self.user != user_request):
return False, (u"Vous n'avez pas droit de voir les machines autre "
"que les vôtres")
return True, None
def __init__(self, *args, **kwargs):
super(Machine, self).__init__(*args, **kwargs)
self.field_permissions = {
'user' : self.can_change_user,
'user': self.can_change_user,
}
def __str__(self):
@ -184,7 +215,8 @@ class MachineType(RevMixin, AclMixin, models.Model):
class Meta:
permissions = (
("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):
@ -192,7 +224,8 @@ class MachineType(RevMixin, AclMixin, models.Model):
machinetype"""
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.
Args:
@ -202,7 +235,8 @@ class MachineType(RevMixin, AclMixin, models.Model):
message is acces is not allowed.
"""
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
def __str__(self):
@ -300,7 +334,11 @@ class IpType(RevMixin, AclMixin, models.Model):
if not self.prefix_v6:
return
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)
def clean(self):
@ -329,8 +367,10 @@ class IpType(RevMixin, AclMixin, models.Model):
self.clean()
super(IpType, self).save(*args, **kwargs)
def can_use_all(user_request, *args, **kwargs):
"""Superdroit qui permet d'utiliser toutes les extensions sans restrictions
@staticmethod
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
:return: True ou False avec la raison de l'échec le cas échéant"""
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'
)
refresh = models.PositiveIntegerField(
default=86400, # 24 hours
default=86400, # 24 hours
help_text='Secondes avant que les DNS secondaires doivent demander le\
serial du DNS primaire pour détecter une modification'
)
retry = models.PositiveIntegerField(
default=7200, # 2 hours
default=7200, # 2 hours
help_text='Secondes avant que les DNS secondaires fassent une nouvelle\
demande de serial en cas de timeout du DNS primaire'
)
expire = models.PositiveIntegerField(
default=3600000, # 1000 hours
default=3600000, # 1000 hours
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'
)
@ -469,8 +509,10 @@ class SOA(RevMixin, AclMixin, models.Model):
extensions .
/!\ Ne jamais supprimer ou renommer cette fonction car elle est
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):
@ -521,8 +563,10 @@ class Extension(RevMixin, AclMixin, models.Model):
entry += "@ IN AAAA " + str(self.origin_v6)
return entry
def can_use_all(user_request, *args, **kwargs):
"""Superdroit qui permet d'utiliser toutes les extensions sans restrictions
@staticmethod
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
:return: True ou False avec la raison de l'échec le cas échéant"""
return user_request.has_perm('machines.use_all_extension'), None
@ -555,7 +599,10 @@ class Mx(RevMixin, AclMixin, models.Model):
def dns_entry(self):
"""Renvoie l'entrée DNS complète pour un MX à mettre dans les
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):
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):
""" A SRV record """
PRETTY_NAME = "Enregistrement Srv"
TCP = 'TCP'
UDP = 'UDP'
service = models.CharField(max_length=31)
service = models.CharField(max_length=31)
protocole = models.CharField(
max_length=3,
choices=(
@ -628,9 +676,9 @@ class Srv(RevMixin, AclMixin, models.Model):
priority = models.PositiveIntegerField(
default=0,
validators=[MaxValueValidator(65535)],
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)"
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)")
)
weight = models.PositiveIntegerField(
default=0,
@ -667,7 +715,7 @@ class Srv(RevMixin, AclMixin, models.Model):
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 address mac unique. Possibilité de la rendre unique avec le
typemachine
@ -692,7 +740,8 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
class Meta:
permissions = (
("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
@ -719,7 +768,10 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
prefix_v6 = self.type.ip_type.prefix_v6
if not prefix_v6:
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):
"""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
if not ipv6_slaac:
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:
ipv6_object = Ipv6List(interface=self, slaac_ip=True)
if ipv6_object.ipv6 != str(ipv6_slaac):
@ -750,19 +804,24 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
def sync_ipv6(self):
"""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()
elif preferences.models.OptionalMachine.get_cached_value('ipv6_mode') == 'DHCPV6':
elif (preferences.models.OptionalMachine
.get_cached_value('ipv6_mode') == 'DHCPV6'):
self.sync_ipv6_dhcpv6()
else:
return
def ipv6(self):
""" Renvoie le queryset de la liste des ipv6
On renvoie l'ipv6 slaac que si le mode slaac est activé (et non dhcpv6)"""
if preferences.models.OptionalMachine.get_cached_value('ipv6_mode') == 'SLAAC':
On renvoie l'ipv6 slaac que si le mode slaac est activé
(et non dhcpv6)"""
if (preferences.models.OptionalMachine
.get_cached_value('ipv6_mode') == 'SLAAC'):
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)
else:
return None
@ -789,7 +848,7 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
# instance.
# But in our case, it's impossible to create a type value so we raise
# the error.
if not hasattr(self, 'type') :
if not hasattr(self, 'type'):
raise ValidationError("Le type d'ip choisi n'est pas valide")
self.filter_macaddress()
self.mac_address = str(EUI(self.mac_address)) or None
@ -825,7 +884,8 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
correspondent pas")
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
une interface, ou bien que la machine appartient bien à l'user
:param macineid: Id de la machine parente de l'interface
@ -836,21 +896,29 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
except Machine.DoesNotExist:
return False, u"Machine inexistante"
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"
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:
return False, u"Vous ne pouvez pas ajouter une interface à une\
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\
autorisées que vous pouvez créer vous même (%s) "\
% max_lambdauser_interfaces
return True, None
@staticmethod
def can_change_machine(user_request, *args, **kwargs):
return user_request.has_perm('machines.change_interface_machine'), "Droit requis pour changer la machine"
def can_change_machine(user_request, *_args, **_kwargs):
"""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):
"""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
:return: soit True, soit False avec la raison de l'échec"""
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]:
return False, u"Vous ne pouvez pas éditer une machine\
d'un autre user que vous sans droit"
if (not user_request.has_perm('machines.change_interface') or
not self.machine.user.can_edit(
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
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
:return: soit True, soit False avec la raison de l'échec"""
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]:
return False, u"Vous ne pouvez pas éditer une machine\
d'un autre user que vous sans droit"
if (not user_request.has_perm('machines.change_interface') or
not self.machine.user.can_edit(
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
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
droit view objet ou qu'elle appartient à l'user
:param self: instance interface à voir
:param user_request: instance user qui fait l'edition
: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:
return False, u"Vous n'avez pas le droit de voir des machines autre\
que les vôtres"
if (not user_request.has_perm('machines.view_interface') and
self.machine.user != user_request):
return False, (u"Vous n'avez pas le droit de voir des machines "
"autre que les vôtres")
return True, None
def __init__(self, *args, **kwargs):
super(Interface, self).__init__(*args, **kwargs)
self.field_permissions = {
'machine' : self.can_change_machine,
'machine': self.can_change_machine,
}
def __str__(self):
@ -915,22 +994,29 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin,models.Model):
class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
""" A list of IPv6 """
PRETTY_NAME = 'Enregistrements Ipv6 des machines'
ipv6 = models.GenericIPAddressField(
protocol='IPv6',
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)
class Meta:
permissions = (
("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
une ipv6, ou possède l'interface associée
: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
@staticmethod
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"
def can_change_slaac_ip(user_request, *_args, **_kwargs):
""" 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):
"""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
:return: soit True, soit False avec la raison de l'échec"""
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]:
return False, u"Vous ne pouvez pas éditer une machine\
d'un autre user que vous sans droit"
if (not user_request.has_perm('machines.change_ipv6list') or
not self.interface.machine.user.can_edit(
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
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
:return: soit True, soit False avec la raison de l'échec"""
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]:
return False, u"Vous ne pouvez pas éditer une machine\
d'un autre user que vous sans droit"
if (not user_request.has_perm('machines.change_ipv6list') or
not self.interface.machine.user.can_edit(
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
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
droit view objet ou qu'elle appartient à l'user
:param self: instance interface à voir
:param user_request: instance user qui fait l'edition
: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:
return False, u"Vous n'avez pas le droit de voir des machines autre\
que les vôtres"
if (not user_request.has_perm('machines.view_ipv6list') and
self.interface.machine.user != user_request):
return False, (u"Vous n'avez pas le droit de voir des machines "
"autre que les vôtres")
return True, None
def __init__(self, *args, **kwargs):
super(Ipv6List, self).__init__(*args, **kwargs)
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):
@ -996,17 +1095,27 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
prefix_v6 = prefix or self.interface.type.ip_type.prefix_v6
if not prefix_v6:
return
if IPv6Address(self.ipv6).exploded[:20] != IPv6Address(prefix_v6).exploded[:20]:
self.ipv6 = IPv6Address(IPv6Address(prefix_v6).exploded[:20] + IPv6Address(self.ipv6).exploded[20:])
if (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()
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")
prefix_v6 = self.interface.type.ip_type.prefix_v6
if prefix_v6:
if IPv6Address(self.ipv6).exploded[:20] != IPv6Address(prefix_v6).exploded[:20]:
raise ValidationError("Le prefixv6 est incorrect et ne correspond pas au type associé à la machine")
if (IPv6Address(self.ipv6).exploded[:20] !=
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)
def save(self, *args, **kwargs):
@ -1072,7 +1181,7 @@ class Domain(RevMixin, AclMixin, models.Model):
if self.cname == self:
raise ValidationError("On ne peut créer un cname sur lui même")
HOSTNAME_LABEL_PATTERN = re.compile(
"(?!-)[A-Z\d-]+(?<!-)$",
r"(?!-)[A-Z\d-]+(?<!-)$",
re.IGNORECASE
)
dns = self.name.lower()
@ -1089,7 +1198,10 @@ class Domain(RevMixin, AclMixin, models.Model):
def dns_entry(self):
""" Une entrée DNS"""
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):
""" Empèche le save sans extension valide.
@ -1111,7 +1223,8 @@ class Domain(RevMixin, AclMixin, models.Model):
else:
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
un domain, ou possède l'interface associée
:param interfaceid: Id de l'interface associée à cet objet domain
@ -1122,54 +1235,58 @@ class Domain(RevMixin, AclMixin, models.Model):
except Interface.DoesNotExist:
return False, u"Interface inexistante"
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:
return False, u"Vous ne pouvez pas ajouter un alias à une\
machine d'un autre user que vous sans droit"
return False, (u"Vous ne pouvez pas ajouter un alias à une "
"machine d'un autre user que vous sans droit")
if 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:
return False, u"Vous avez atteint le maximum d'alias\
autorisés que vous pouvez créer vous même (%s) "\
% max_lambdauser_aliases
return False, (u"Vous avez atteint le maximum d'alias "
"autorisés que vous pouvez créer vous même "
"(%s) " % max_lambdauser_aliases)
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
cette instance domain
:param self: Instance domain à editer
:param user_request: Utilisateur qui fait la requête
:return: soit True, soit False avec la raison de l'échec"""
if not user_request.has_perm('machines.change_domain') and\
self.get_source_interface.machine.user != user_request:
return False, u"Vous ne pouvez pas editer un alias à une machine\
d'un autre user que vous sans droit"
if (not user_request.has_perm('machines.change_domain') and
self.get_source_interface.machine.user != user_request):
return False, (u"Vous ne pouvez pas editer un alias à une machine "
"d'un autre user que vous sans droit")
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
cette instance domain, ou qu'elle lui appartient
:param self: Instance domain à del
:param user_request: Utilisateur qui fait la requête
:return: soit True, soit False avec la raison de l'échec"""
if not user_request.has_perm('machines.delete_domain') and\
self.get_source_interface.machine.user != user_request:
return False, u"Vous ne pouvez pas supprimer un alias à une machine\
d'un autre user que vous sans droit"
if (not user_request.has_perm('machines.delete_domain') and
self.get_source_interface.machine.user != user_request):
return False, (u"Vous ne pouvez pas supprimer un alias à une "
"machine d'un autre user que vous sans droit")
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
droit view objet ou qu'elle appartient à l'user
:param self: instance domain à voir
:param user_request: instance user qui fait l'edition
:return: True ou False avec la raison de l'échec le cas échéant"""
if not user_request.has_perm('machines.view_domain') and\
self.get_source_interface.machine.user != user_request:
return False, u"Vous n'avez pas le droit de voir des machines autre\
que les vôtres"
if (not user_request.has_perm('machines.view_domain') and
self.get_source_interface.machine.user != user_request):
return False, (u"Vous n'avez pas le droit de voir des machines "
"autre que les vôtres")
return True, None
def __str__(self):
@ -1177,6 +1294,7 @@ class Domain(RevMixin, AclMixin, models.Model):
class IpList(RevMixin, AclMixin, models.Model):
""" A list of IPv4 """
PRETTY_NAME = "Addresses ipv4"
ipv4 = models.GenericIPAddressField(protocol='IPv4', unique=True)
@ -1307,15 +1425,15 @@ class OuverturePortList(RevMixin, AclMixin, models.Model):
("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
cette instance ouvertureportlist
:param self: Instance ouvertureportlist à delete
:param user_request: Utilisateur qui fait la requête
:return: soit True, soit False avec la raison de l'échec"""
if not user_request.has_perm('machines.delete_ouvertureportlist'):
return False, u"Vous n'avez pas le droit de supprimer une ouverture\
de port"
return False, (u"Vous n'avez pas le droit de supprimer une "
"ouverture de port")
if self.interface_set.all():
return False, u"Cette liste de ports est utilisée"
return True, None
@ -1401,7 +1519,7 @@ class OuverturePort(RevMixin, AclMixin, models.Model):
@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
d'une machine"""
user = kwargs['instance'].user
@ -1411,7 +1529,7 @@ def machine_post_save(sender, **kwargs):
@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
d'une machine"""
machine = kwargs['instance']
@ -1422,7 +1540,7 @@ def machine_post_delete(sender, **kwargs):
@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
d'une interface"""
interface = kwargs['instance']
@ -1435,7 +1553,7 @@ def interface_post_save(sender, **kwargs):
@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
d'une interface"""
interface = kwargs['instance']
@ -1444,7 +1562,7 @@ def interface_post_delete(sender, **kwargs):
@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"""
iptype = kwargs['instance']
iptype.gen_ip_range()
@ -1452,7 +1570,7 @@ def iptype_post_save(sender, **kwargs):
@receiver(post_save, sender=MachineType)
def machine_post_save(sender, **kwargs):
def machinetype_post_save(**kwargs):
"""Mise à jour des interfaces lorsque changement d'attribution
d'une machinetype (changement iptype parent)"""
machinetype = kwargs['instance']
@ -1461,85 +1579,84 @@ def machine_post_save(sender, **kwargs):
@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"""
regen('dns')
@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"""
regen('dns')
@receiver(post_save, sender=Extension)
def extension_post_save(sender, **kwargs):
def extension_post_save(**_kwargs):
"""Regeneration dns après modification d'une extension"""
regen('dns')
@receiver(post_delete, sender=Extension)
def extension_post_selete(sender, **kwargs):
def extension_post_selete(**_kwargs):
"""Regeneration dns après suppression d'une extension"""
regen('dns')
@receiver(post_save, sender=SOA)
def soa_post_save(sender, **kwargs):
def soa_post_save(**_kwargs):
"""Regeneration dns après modification d'un SOA"""
regen('dns')
@receiver(post_delete, sender=SOA)
def soa_post_delete(sender, **kwargs):
def soa_post_delete(**_kwargs):
"""Regeneration dns après suppresson d'un SOA"""
regen('dns')
@receiver(post_save, sender=Mx)
def mx_post_save(sender, **kwargs):
def mx_post_save(**_kwargs):
"""Regeneration dns après modification d'un MX"""
regen('dns')
@receiver(post_delete, sender=Mx)
def mx_post_delete(sender, **kwargs):
def mx_post_delete(**_kwargs):
"""Regeneration dns après suppresson d'un MX"""
regen('dns')
@receiver(post_save, sender=Ns)
def ns_post_save(sender, **kwargs):
def ns_post_save(**_kwargs):
"""Regeneration dns après modification d'un NS"""
regen('dns')
@receiver(post_delete, sender=Ns)
def ns_post_delete(sender, **kwargs):
def ns_post_delete(**_kwargs):
"""Regeneration dns après modification d'un NS"""
regen('dns')
@receiver(post_save, sender=Txt)
def text_post_save(sender, **kwargs):
def text_post_save(**_kwargs):
"""Regeneration dns après modification d'un TXT"""
regen('dns')
@receiver(post_delete, sender=Txt)
def text_post_delete(sender, **kwargs):
def text_post_delete(**_kwargs):
"""Regeneration dns après modification d'un TX"""
regen('dns')
@receiver(post_save, sender=Srv)
def srv_post_save(sender, **kwargs):
def srv_post_save(**_kwargs):
"""Regeneration dns après modification d'un SRV"""
regen('dns')
@receiver(post_delete, sender=Srv)
def text_post_delete(sender, **kwargs):
def srv_post_delete(**_kwargs):
"""Regeneration dns après modification d'un SRV"""
regen('dns')

View file

@ -21,7 +21,11 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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 machines.models import (
@ -29,28 +33,30 @@ from machines.models import (
IpType,
Extension,
IpList,
MachineType,
Domain,
Txt,
Mx,
Srv,
Service_link,
Ns,
OuverturePortList,
OuverturePort,
Ipv6List
)
class IpTypeField(serializers.RelatedField):
"""Serialisation d'une iptype, renvoie son evaluation str"""
""" Serializer for an IpType object field """
def to_representation(self, value):
return value.type
def to_internal_value(self, data):
pass
class IpListSerializer(serializers.ModelSerializer):
"""Serialisation d'une iplist, ip_type etant une foreign_key,
on evalue sa methode str"""
""" Serializer for an Ipv4List obejct using the IpType serialization """
ip_type = IpTypeField(read_only=True)
class Meta:
@ -59,16 +65,19 @@ class IpListSerializer(serializers.ModelSerializer):
class Ipv6ListSerializer(serializers.ModelSerializer):
""" Serializer for an Ipv6List object """
class Meta:
model = Ipv6List
fields = ('ipv6', 'slaac_ip')
class InterfaceSerializer(serializers.ModelSerializer):
"""Serialisation d'une interface, ipv4, domain et extension sont
des foreign_key, on les override et on les evalue avec des fonctions
get_..."""
""" Serializer for an Interface object. Use SerializerMethodField
to get ForeignKey values """
ipv4 = IpListSerializer(read_only=True)
# TODO : use serializer.RelatedField to avoid duplicate code
mac_address = serializers.SerializerMethodField('get_macaddress')
domain = serializers.SerializerMethodField('get_dns')
extension = serializers.SerializerMethodField('get_interface_extension')
@ -77,20 +86,29 @@ class InterfaceSerializer(serializers.ModelSerializer):
model = Interface
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
def get_interface_extension(self, obj):
@staticmethod
def get_interface_extension(obj):
""" The name of the associated Interface object """
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)
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)
ipv6 = Ipv6ListSerializer(read_only=True, many=True)
# TODO : use serializer.RelatedField to avoid duplicate code
mac_address = serializers.SerializerMethodField('get_macaddress')
domain = serializers.SerializerMethodField('get_dns')
extension = serializers.SerializerMethodField('get_interface_extension')
@ -99,26 +117,36 @@ class FullInterfaceSerializer(serializers.ModelSerializer):
model = Interface
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
def get_interface_extension(self, obj):
@staticmethod
def get_interface_extension(obj):
""" The name of the associated Extension object """
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)
class ExtensionNameField(serializers.RelatedField):
"""Evaluation str d'un objet extension (.example.org)"""
""" Serializer for Extension object field """
def to_representation(self, value):
return value.name
def to_internal_value(self, data):
pass
class TypeSerializer(serializers.ModelSerializer):
"""Serialisation d'un iptype : extension et la liste des
ouvertures de port son evalués en get_... etant des
foreign_key ou des relations manytomany"""
""" Serializer for an IpType object. Use SerializerMethodField to
get ForeignKey values. Infos about the general port policy is added """
extension = ExtensionNameField(read_only=True)
ouverture_ports_tcp_in = serializers\
.SerializerMethodField('get_port_policy_input_tcp')
@ -136,7 +164,10 @@ class TypeSerializer(serializers.ModelSerializer):
'ouverture_ports_tcp_in', 'ouverture_ports_tcp_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:
return []
return map(
@ -174,14 +205,20 @@ class ExtensionSerializer(serializers.ModelSerializer):
model = Extension
fields = ('name', 'origin', 'origin_v6', 'zone_entry', 'soa')
def get_origin_ip(self, obj):
return getattr(obj.origin, 'ipv4', None)
@staticmethod
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)
def get_soa_data(self, obj):
return { 'mail': obj.soa.dns_soa_mail, 'param': obj.soa.dns_soa_param }
@staticmethod
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):
@ -195,13 +232,19 @@ class MxSerializer(serializers.ModelSerializer):
model = Mx
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)
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
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)
@ -215,10 +258,14 @@ class TxtSerializer(serializers.ModelSerializer):
model = Txt
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)
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)
@ -241,10 +288,14 @@ class SrvSerializer(serializers.ModelSerializer):
'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)
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)
@ -259,13 +310,19 @@ class NsSerializer(serializers.ModelSerializer):
model = Ns
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
def get_domain_name(self, obj):
@staticmethod
def get_domain_name(obj):
""" The name of the associated NS target """
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)
@ -280,13 +337,19 @@ class DomainSerializer(serializers.ModelSerializer):
model = Domain
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
def get_alias_name(self, obj):
@staticmethod
def get_alias_name(obj):
""" The name of the associated alias """
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)
@ -300,13 +363,19 @@ class ServiceServersSerializer(serializers.ModelSerializer):
model = Service_link
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)
def get_service_name(self, obj):
@staticmethod
def get_service_name(obj):
""" The name of the service name """
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()
@ -315,24 +384,38 @@ class OuverturePortsSerializer(serializers.Serializer):
ipv4 = 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():
return {i.ipv4.ipv4:
{
"tcp_in":[j.tcp_ports_in() for j in i.port_lists.all()],
"tcp_out":[j.tcp_ports_out()for j in i.port_lists.all()],
"udp_in":[j.udp_ports_in() for j in i.port_lists.all()],
"udp_out":[j.udp_ports_out() for j in i.port_lists.all()],
""" The representation of the policy for the IPv4 addresses """
return {
i.ipv4.ipv4: {
"tcp_in": [j.tcp_ports_in() for j in i.port_lists.all()],
"tcp_out": [j.tcp_ports_out()for j in i.port_lists.all()],
"udp_in": [j.udp_ports_in() for j in i.port_lists.all()],
"udp_out": [j.udp_ports_out() for j in i.port_lists.all()],
}
for i in Interface.objects.all() if i.ipv4
for i in Interface.objects.all() if i.ipv4
}
@staticmethod
def get_ipv6():
return {i.ipv6:
{
"tcp_in":[j.tcp_ports_in() for j in i.port_lists.all()],
"tcp_out":[j.tcp_ports_out()for j in i.port_lists.all()],
"udp_in":[j.udp_ports_in() for j in i.port_lists.all()],
"udp_out":[j.udp_ports_out() for j in i.port_lists.all()],
""" The representation of the policy for the IPv6 addresses """
return {
i.ipv6: {
"tcp_in": [j.tcp_ports_in() for j in i.port_lists.all()],
"tcp_out": [j.tcp_ports_out()for j in i.port_lists.all()],
"udp_in": [j.udp_ports_in() for j in i.port_lists.all()],
"udp_out": [j.udp_ports_out() for j in i.port_lists.all()],
}
for i in Interface.objects.all() if i.ipv6
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
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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.

View file

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

View file

@ -26,6 +26,7 @@
Here are defined some functions to check acl on the application.
"""
def can_view(user):
"""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 binascii
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):
""" Generate a random string of length `length` and composed of
the characters in `chars` """
return ''.join([choice(chars) for i in range(length)])
def encrypt(key, s):
""" AES Encrypt a secret `s` with the key `key` """
obj = AES.new(key)
datalength = len(s) + len(EOD)
if datalength < 16:
@ -25,12 +59,15 @@ def encrypt(key, s):
def decrypt(key, s):
""" AES Decrypt a secret `s` with the key `key` """
obj = AES.new(key)
ss = obj.decrypt(s)
return ss.split(bytes(EOD, 'utf-8'))[0]
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):
setattr(instance, self.name,
binascii.b2a_base64(encrypt(settings.AES_KEY, data)))
@ -41,16 +78,10 @@ class AESEncryptedField(models.CharField):
return decrypt(settings.AES_KEY,
binascii.a2b_base64(value)).decode('utf-8')
def from_db_value(self, value, expression, connection, *args):
if value is None:
return value
return decrypt(settings.AES_KEY,
binascii.a2b_base64(value)).decode('utf-8')
def get_prep_value(self, value):
if value is None:
return value
return binascii.b2a_base64(encrypt(
settings.AES_KEY,
value
settings.AES_KEY,
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,
**kwargs
)
self.fields['is_tel_mandatory'].label = 'Exiger un numéro de\
téléphone'
self.fields['user_solde'].label = 'Activation du solde pour\
les utilisateurs'
self.fields['is_tel_mandatory'].label = (
'Exiger un numéro de téléphone'
)
self.fields['user_solde'].label = (
'Activation du solde pour les utilisateurs'
)
self.fields['max_solde'].label = 'Solde maximum'
self.fields['min_online_payment'].label = 'Montant de rechargement minimum en ligne'
self.fields['min_online_payment'].label = (
'Montant de rechargement minimum en ligne'
)
self.fields['self_adhesion'].label = 'Auto inscription'
@ -162,7 +166,6 @@ class EditAssoOptionForm(ModelForm):
return cleaned_data
class EditMailMessageOptionForm(ModelForm):
"""Formulaire d'edition des messages de bienvenue personnalisés"""
class Meta:

View file

@ -27,26 +27,33 @@ from __future__ import unicode_literals
from django.utils.functional import cached_property
from django.db import models
import cotisations.models
import machines.models
from django.db.models.signals import post_save, post_delete
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.cache import cache
from .aes_field import AESEncryptedField
import cotisations.models
import machines.models
from re2o.mixins import AclMixin
from .aes_field import AESEncryptedField
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
def set_in_cache(cls):
""" Save the preferences in a server-side cache """
instance, _created = cls.objects.get_or_create()
cache.set(cls().__class__.__name__.lower(), instance, None)
return instance
@classmethod
def get_cached_value(cls, key):
""" Get the preferences from the server-side cache """
instance = cache.get(cls().__class__.__name__.lower())
if instance == None:
if instance is None:
instance = cls.set_in_cache()
return getattr(instance, key)
@ -111,7 +118,7 @@ class OptionalUser(AclMixin, PreferencesModel):
@receiver(post_save, sender=OptionalUser)
def optionaluser_post_save(sender, **kwargs):
def optionaluser_post_save(**kwargs):
"""Ecriture dans le cache"""
user_pref = kwargs['instance']
user_pref.set_in_cache()
@ -146,7 +153,8 @@ class OptionalMachine(AclMixin, PreferencesModel):
@cached_property
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:
permissions = (
@ -155,7 +163,7 @@ class OptionalMachine(AclMixin, PreferencesModel):
@receiver(post_save, sender=OptionalMachine)
def optionalmachine_post_save(sender, **kwargs):
def optionalmachine_post_save(**kwargs):
"""Synchronisation ipv6 et ecriture dans le cache"""
machine_pref = kwargs['instance']
machine_pref.set_in_cache()
@ -203,7 +211,7 @@ class OptionalTopologie(AclMixin, PreferencesModel):
@receiver(post_save, sender=OptionalTopologie)
def optionaltopologie_post_save(sender, **kwargs):
def optionaltopologie_post_save(**kwargs):
"""Ecriture dans le cache"""
topologie_pref = kwargs['instance']
topologie_pref.set_in_cache()
@ -230,7 +238,7 @@ class GeneralOption(AclMixin, PreferencesModel):
blank=True,
)
GTU = models.FileField(
upload_to = '',
upload_to='',
default="",
null=True,
blank=True,
@ -243,7 +251,7 @@ class GeneralOption(AclMixin, PreferencesModel):
@receiver(post_save, sender=GeneralOption)
def generaloption_post_save(sender, **kwargs):
def generaloption_post_save(**kwargs):
"""Ecriture dans le cache"""
general_pref = kwargs['instance']
general_pref.set_in_cache()
@ -317,7 +325,7 @@ class AssoOption(AclMixin, PreferencesModel):
@receiver(post_save, sender=AssoOption)
def assooption_post_save(sender, **kwargs):
def assooption_post_save(**kwargs):
"""Ecriture dans le cache"""
asso_pref = kwargs['instance']
asso_pref.set_in_cache()

View file

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

View file

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

View file

@ -31,17 +31,17 @@ topologie, users, service...)
from __future__ import unicode_literals
from django.urls import reverse
from django.shortcuts import render, redirect
from django.shortcuts import redirect
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 import transaction
from reversion.models import Version
from reversion import revisions as reversion
from re2o.views import form
from re2o.acl import can_create, can_edit, can_delete_set, can_view_all
from .forms import ServiceForm, DelServiceForm
from .models import Service, OptionalUser, OptionalMachine, AssoOption
from .models import MailMessageOption, GeneralOption, OptionalTopologie
@ -119,7 +119,7 @@ def edit_options(request, section):
@can_create(Service)
def add_service(request):
"""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():
with transaction.atomic(), reversion.create_revision():
service.save()
@ -128,7 +128,7 @@ def add_service(request):
messages.success(request, "Ce service a été ajouté")
return redirect(reverse('preferences:display-options'))
return form(
{'preferenceform': service, 'action_name' : 'Ajouter'},
{'preferenceform': service, 'action_name': 'Ajouter'},
'preferences/preferences.html',
request
)
@ -136,9 +136,9 @@ def add_service(request):
@login_required
@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"""
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():
with transaction.atomic(), reversion.create_revision():
service.save()
@ -151,7 +151,7 @@ def edit_service(request, service_instance, serviceid):
messages.success(request, "Service modifié")
return redirect(reverse('preferences:display-options'))
return form(
{'preferenceform': service, 'action_name' : 'Editer'},
{'preferenceform': service, 'action_name': 'Editer'},
'preferences/preferences.html',
request
)
@ -175,7 +175,7 @@ def del_services(request, instances):
suivant %s ne peut être supprimé" % services_del)
return redirect(reverse('preferences:display-options'))
return form(
{'preferenceform': services, 'action_name' : 'Supprimer'},
{'preferenceform': services, 'action_name': 'Supprimer'},
'preferences/preferences.html',
request
)

View file

@ -20,4 +20,15 @@
# 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
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.urls import reverse
import cotisations
import logs
import machines
import preferences
import search
import topologie
import users
def can_create(model):
"""Decorator to check if an user can create a model.
@ -49,7 +41,11 @@ def can_create(model):
of models.
"""
def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
can, msg = model.can_create(request.user, *args, **kwargs)
if not can:
messages.error(
@ -68,31 +64,37 @@ def can_edit(model, *field_list):
kind of models.
"""
def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
try:
instance = model.get_instance(*args, **kwargs)
except model.DoesNotExist:
messages.error(request, u"Entrée inexistante")
return redirect(reverse('users:profil',
kwargs={'userid': str(request.user.id)}
))
return redirect(reverse(
'users:profil',
kwargs={'userid': str(request.user.id)}
))
can, msg = instance.can_edit(request.user)
if not can:
messages.error(
request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil',
kwargs={'userid': str(request.user.id)}
))
return redirect(reverse(
'users:profil',
kwargs={'userid': str(request.user.id)}
))
for field in field_list:
can_change = getattr(instance, 'can_change_' + field)
can, msg = can_change(request.user, *args, **kwargs)
can_change_fct = getattr(instance, 'can_change_' + field)
can, msg = can_change_fct(request.user, *args, **kwargs)
if not can:
messages.error(
request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil',
kwargs={'userid': str(
request.user.id)}
))
return redirect(reverse(
'users:profil',
kwargs={'userid': str(request.user.id)}
))
return view(request, instance, *args, **kwargs)
return wrapper
return decorator
@ -103,17 +105,21 @@ def can_change(model, *field_list):
Difference with can_edit : take a class and not an instance
"""
def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
for field in field_list:
can_change = getattr(model, 'can_change_' + field)
can, msg = can_change(request.user, *args, **kwargs)
can_change_fct = getattr(model, 'can_change_' + field)
can, msg = can_change_fct(request.user, *args, **kwargs)
if not can:
messages.error(
request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil',
kwargs={'userid': str(
request.user.id)}
))
return redirect(reverse(
'users:profil',
kwargs={'userid': str(request.user.id)}
))
return view(request, *args, **kwargs)
return wrapper
return decorator
@ -127,21 +133,27 @@ def can_delete(model):
kind of models.
"""
def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
try:
instance = model.get_instance(*args, **kwargs)
except model.DoesNotExist:
messages.error(request, u"Entrée inexistante")
return redirect(reverse('users:profil',
kwargs={'userid': str(request.user.id)}
))
return redirect(reverse(
'users:profil',
kwargs={'userid': str(request.user.id)}
))
can, msg = instance.can_delete(request.user)
if not can:
messages.error(
request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil',
kwargs={'userid': str(request.user.id)}
))
return redirect(reverse(
'users:profil',
kwargs={'userid': str(request.user.id)}
))
return view(request, instance, *args, **kwargs)
return wrapper
return decorator
@ -151,19 +163,25 @@ def can_delete_set(model):
"""Decorator which returns a list of detable models by request user.
If none of them, return an error"""
def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
all_objects = model.objects.all()
instances_id = []
for instance in all_objects:
can, msg = instance.can_delete(request.user)
can, _msg = instance.can_delete(request.user)
if can:
instances_id.append(instance.id)
instances = model.objects.filter(id__in=instances_id)
if not instances:
messages.error(request, "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil',
kwargs={'userid': str(request.user.id)}
))
messages.error(
request, "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse(
'users:profil',
kwargs={'userid': str(request.user.id)}
))
return view(request, instances, *args, **kwargs)
return wrapper
return decorator
@ -177,21 +195,27 @@ def can_view(model):
kind of models.
"""
def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
try:
instance = model.get_instance(*args, **kwargs)
except model.DoesNotExist:
messages.error(request, u"Entrée inexistante")
return redirect(reverse('users:profil',
kwargs={'userid': str(request.user.id)}
))
return redirect(reverse(
'users:profil',
kwargs={'userid': str(request.user.id)}
))
can, msg = instance.can_view(request.user)
if not can:
messages.error(
request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil',
kwargs={'userid': str(request.user.id)}
))
return redirect(reverse(
'users:profil',
kwargs={'userid': str(request.user.id)}
))
return view(request, instance, *args, **kwargs)
return wrapper
return decorator
@ -201,14 +225,19 @@ def can_view_all(model):
"""Decorator to check if an user can view a class of model.
"""
def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
can, msg = model.can_view_all(request.user)
if not can:
messages.error(
request, msg or "Vous ne pouvez pas accéder à ce menu")
return redirect(reverse('users:profil',
kwargs={'userid': str(request.user.id)}
))
return redirect(reverse(
'users:profil',
kwargs={'userid': str(request.user.id)}
))
return view(request, *args, **kwargs)
return wrapper
return decorator
@ -220,15 +249,20 @@ def can_view_app(app_name):
assert app_name in sys.modules.keys()
def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
app = sys.modules[app_name]
can, msg = app.can_view(request.user)
if can:
return view(request, *args, **kwargs)
messages.error(request, msg)
return redirect(reverse('users:profil',
kwargs={'userid': str(request.user.id)}
))
return redirect(reverse(
'users:profil',
kwargs={'userid': str(request.user.id)}
))
return wrapper
return decorator
@ -236,13 +270,16 @@ def can_view_app(app_name):
def can_edit_history(view):
"""Decorator to check if an user can edit history."""
def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
if request.user.has_perm('admin.change_logentry'):
return view(request, *args, **kwargs)
messages.error(
request,
"Vous ne pouvez pas éditer l'historique."
)
return redirect(reverse('users:profil',
kwargs={'userid': str(request.user.id)}
))
return redirect(reverse(
'users:profil',
kwargs={'userid': str(request.user.id)}
))
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
from django import forms
from functools import partial
# -*- 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.
"""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:
""" The model mixin. Defines the `has_field_perm` function """
field_permissions = {} # {'field_name': callable}
FIELD_PERM_CODENAME = 'can_change_{model}_{name}'
FIELD_PERMISSION_GETTER = 'can_change_{name}'
FIELD_PERMISSION_MISSING_DEFAULT = True
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:
checks = self.field_permissions[field]
if not isinstance(checks, (list, tuple)):
@ -39,21 +69,18 @@ class FieldPermissionModelMixin:
# Try to find a user setting that qualifies them for permission.
for perm in checks:
if callable(perm):
result, reason = perm(user_request=user)
result, _reason = perm(user_request=user)
if result is not None:
return result
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:
return True
# If no requirement can be met, then permission is denied.
return False
class FieldPermissionModel(FieldPermissionModelMixin, models.Model):
class Meta:
abstract = True
class FieldPermissionFormMixin:
"""
@ -71,9 +98,5 @@ class FieldPermissionFormMixin:
self.remove_unauthorized_field(name)
def remove_unauthorized_field(self, name):
""" Remove one field from the fields of the form """
del self.fields[name]
class FieldPermissionForm(FieldPermissionFormMixin, forms.ModelForm):
pass

View file

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

View file

@ -20,20 +20,28 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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.
This list is extracted from the FedeRez gitlab repository.
Write in a python file the list of all contributors sorted by number of
commits. This list is extracted from the current gitlab repository.
"""
from django.core.management.base import BaseCommand, CommandError
import os
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = 'Update contributors list'
""" The command object for `gen_contrib` """
help = 'Update contributors list'
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"))
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("CONTRIBUTORS = " + str(contributeurs))

View file

@ -19,27 +19,44 @@
# 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.mixins
A set of mixins used all over the project to avoid duplicating code
"""
from reversion import revisions as reversion
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):
""" Creates a version of this object and save it to database """
if self.pk is None:
reversion.set_comment("Création")
return super(RevMixin, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
""" Creates a version of this object and delete it from database """
reversion.set_comment("Suppresion")
return super(RevMixin, self).delete(*args, **kwargs)
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):
""" Create a version of this object and save it to database """
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:
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)
@ -47,23 +64,29 @@ class AclMixin(object):
"""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
: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 do the creation
:can_edit: Applied on an instance, return if the user can edit the instance
:can_delete: Applied on an instance, return if the user can delete the 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"""
:can_create: Applied on a class, take the requested user, return if the
user can do the creation
:can_edit: Applied on an instance, return if the user can edit the
instance
:can_delete: Applied on an instance, return if the user can delete the
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
def get_classname(cls):
""" Returns the name of the class where this mixin is used """
return str(cls.__name__).lower()
@classmethod
def get_modulename(cls):
""" Returns the name of the module where this mixin is used """
return str(cls.__module__).split('.')[0].lower()
@classmethod
def get_instance(cls, *args, **kwargs):
def get_instance(cls, *_args, **kwargs):
"""Récupère une instance
:param objectid: Instance id à trouver
:return: Une instance de la classe évidemment"""
@ -71,42 +94,66 @@ class AclMixin(object):
return cls.objects.get(pk=object_id)
@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
un object
:param user_request: instance utilisateur qui fait la requête
: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\
de créer un " + cls.get_classname()
return (
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
cette instance
:param self: Instance à editer
:param user_request: Utilisateur qui fait la requête
: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
cette instance
:param self: Instance à delete
:param user_request: Utilisateur qui fait la requête
: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
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,
droit particulier view objet correspondant
:param user_request: instance user qui fait l'edition
: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
droit view objet
:param self: instance à voir
:param user_request: instance user qui fait l'edition
: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
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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
application = get_wsgi_application()
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 django.utils.html import strip_tags
from reversion import revisions as reversion
from django.db import transaction
from getpass import getpass
proj_path = dirname(dirname(__file__))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "re2o.settings")
sys.path.append(proj_path)
os.chdir(proj_path)
application = get_wsgi_application()
def get_user(pseudo):
"""Cherche un utilisateur re2o à partir de son pseudo"""
user = User.objects.filter(pseudo=pseudo)
if len(user)==0:
if len(user) == 0:
raise CommandError("Utilisateur invalide")
if len(user)>1:
raise CommandError("Plusieurs utilisateurs correspondant à ce pseudo. Ceci NE DEVRAIT PAS arriver")
if len(user) > 1:
raise CommandError("Plusieurs utilisateurs correspondant à ce "
"pseudo. Ceci NE DEVRAIT PAS arriver")
return user[0]
@ -53,7 +62,7 @@ def get_system_user():
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
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)
Les arguments suivants sont transmis tels quels au formulaire.
"""
data={}
dumb_form = Form(user=user,*args,**kwargs)
data = {}
dumb_form = Form(user=user, *args, **kwargs)
for key in dumb_form.fields:
if not dumb_form.fields[key].widget.input_type=='hidden':
if dumb_form.fields[key].widget.input_type=='password':
data[key]=getpass("%s : " % dumb_form.fields[key].label)
if not dumb_form.fields[key].widget.input_type == 'hidden':
if dumb_form.fields[key].widget.input_type == 'password':
data[key] = getpass("%s : " % dumb_form.fields[key].label)
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():
sys.stderr.write("Erreurs : \n")
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
sys.stderr.write("\t%s : %s\n" % (err,strip_tags(form.errors[err])))
# Oui, oui, on gère du HTML là où d'autres ont eu la
# lumineuse idée de le mettre
sys.stderr.write(
"\t%s : %s\n" % (err, strip_tags(form.errors[err]))
)
raise CommandError("Formulaire invalide")
with transaction.atomic(), reversion.create_revision():
form.save()
reversion.set_user(user)
reversion.set_comment(action)
form.save()
reversion.set_user(user)
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
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
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__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
# Auth definition
PASSWORD_HASHERS = (
're2o.login.SSHAPasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
)
AUTH_USER_MODEL = 'users.User'
LOGIN_URL = '/login/'
LOGIN_REDIRECT_URL = '/'
AUTH_USER_MODEL = 'users.User' # The class to use for authentication
LOGIN_URL = '/login/' # The URL for login page
LOGIN_REDIRECT_URL = '/' # The URL for redirecting after login
# Application definition
INSTALLED_APPS = (
DJANGO_CONTRIB_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
)
EXTERNAL_CONTRIB_APPS = (
'bootstrap3',
'rest_framework',
'reversion',
)
LOCAL_APPS = (
'users',
'machines',
'cotisations',
@ -75,11 +74,14 @@ INSTALLED_APPS = (
're2o',
'preferences',
'logs',
'rest_framework',
'reversion',
'api'
) + OPTIONNAL_APPS
'api',
)
INSTALLED_APPS = (
DJANGO_CONTRIB_APPS +
EXTERNAL_CONTRIB_APPS +
LOCAL_APPS +
OPTIONNAL_APPS
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
@ -93,14 +95,17 @@ MIDDLEWARE_CLASSES = (
'reversion.middleware.RevisionMiddleware',
)
# The root url module to define the project URLs
ROOT_URLCONF = 're2o.urls'
# The templates configuration (see Django documentation)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'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,
'OPTIONS': {
'context_processors': [
@ -115,62 +120,52 @@ TEMPLATES = [
},
]
# The WSGI module to use in a server environment
WSGI_APPLICATION = 're2o.wsgi.application'
# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'en'
USE_I18N = True
USE_L10N = True
# Proritary location search for translations
# then searches in {app}/locale/ for app in INSTALLED_APPS
# Use only absolute paths with '/' delimiters even on Windows
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'
USE_I18N = True
USE_L10N = True
# Should use time zone ?
USE_TZ = True
# Router config for database
DATABASE_ROUTERS = ['ldapdb.router.Router']
# django-bootstrap3 config dictionnary
# django-bootstrap3 config
BOOTSTRAP3 = {
'jquery_url': '/static/js/jquery-2.2.4.min.js',
'base_url': '/static/bootstrap/',
'include_jquery': True,
}
'jquery_url': '/static/js/jquery-2.2.4.min.js',
'base_url': '/static/bootstrap/',
'include_jquery': True,
}
BOOTSTRAP_BASE_URL = '/static/bootstrap/'
# Directories where collectstatic should look for static files
# Use only absolute paths with '/' delimiters even on Windows
STATICFILES_DIRS = (
# Put strings here, like "/home/html/static" or "C:/www/django/static".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
os.path.join(
BASE_DIR,
'static',
),
os.path.join(BASE_DIR, 'static').replace('\\', '/'),
)
MEDIA_ROOT = '/var/www/re2o/media'
STATIC_URL = '/static/'
# Directory where the static files served by the server are stored
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 = {
'cableur' : ['bureau','infra','bofh','tresorier'],
'bofh' : ['bureau','tresorier'],
}
# Models to use for graphs
GRAPH_MODELS = {
'all_applications': True,
'group_models': True,
'all_applications': True,
'group_models': True,
}

View file

@ -19,45 +19,56 @@
# 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.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
# A secret key used by the server.
SECRET_KEY = 'SUPER_SECRET_KEY'
# The password to access the project database
DB_PASSWORD = 'SUPER_SECRET_DB'
# AES key for secret key encryption length must be a multiple of 16
AES_KEY = 'THE_AES_KEY'
# AES key for secret key encryption.
# 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!
DEBUG = False
# A list of admins of the services. Receive mails when an error occurs
ADMINS = [('Example', 'rezo-admin@example.org')]
SERVER_EMAIL = 'no-reply@example.org'
# Obligatoire, liste des host autorisés
# The list of hostname the server will respond to.
ALLOWED_HOSTS = ['URL_SERVER']
# The time zone the server is runned in
TIME_ZONE = 'Europe/Paris'
# The storage systems parameters to use
DATABASES = {
'default': {
'default': { # The DB
'ENGINE': 'db_engine',
'NAME': 'db_name_value',
'USER': 'db_user_value',
'PASSWORD': DB_PASSWORD,
'HOST': 'db_host_value',
},
'ldap': {
'ldap': { # The LDAP
'ENGINE': 'ldapdb.backends.ldap',
'NAME': 'ldap://ldap_host_ip/',
'USER': 'ldap_dn',
# 'TLS': True,
'TLS': True,
'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_BROWSER_XSS_FILTER = False
SESSION_COOKIE_SECURE = False
@ -66,30 +77,33 @@ CSRF_COOKIE_HTTPONLY = False
X_FRAME_OPTIONS = 'DENY'
SESSION_COOKIE_AGE = 60 * 60 * 3
# The path where your organization logo is stored
LOGO_PATH = "static_files/logo.png"
EMAIL_HOST = 'MY_EMAIL_HOST'
EMAIL_PORT = MY_EMAIL_PORT
# The mail configuration for Re2o to send mails
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 = {
'base_user_dn' : 'cn=Utilisateurs,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_userservicegroup_dn' : 'ou=services,ou=groups,dc=example,dc=org',
'user_gid' : 500,
'base_user_dn': 'cn=Utilisateurs,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_userservicegroup_dn': 'ou=services,ou=groups,dc=example,dc=org',
'user_gid': 500,
}
# A range of UID to use. Used in linux environement
UID_RANGES = {
'users' : [21001,30000],
'service-users' : [20000,21000],
'users': [21001, 30000],
'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 = {
'posix' : [501, 600],
'posix': [501, 600],
}
# Some Django apps you want to add in you local project
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 %}
<div class="col-12">
<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">
<h3>{{ service.name }}</h3>
<p>{{ service.description }}</p>

View file

@ -18,4 +18,3 @@
# 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.

View file

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

View file

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

View file

@ -36,18 +36,13 @@ Fonction :
from __future__ import unicode_literals
from django.utils import timezone
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 cotisations.models import Cotisation, Facture, Paiement, Vente
from machines.models import Domain, Interface, Machine
from cotisations.models import Cotisation, Facture, Vente
from machines.models import Interface, Machine
from users.models import Adherent, User, Ban, Whitelist
from preferences.models import Service
def all_adherent(search_time=None):
@ -118,14 +113,18 @@ def all_has_access(search_time=None):
def filter_active_interfaces(interface_set):
"""Filtre les machines autorisées à sortir sur internet dans une requête"""
return interface_set.filter(
machine__in=Machine.objects.filter(
user__in=all_has_access()
).filter(active=True)
).select_related('domain').select_related('machine')\
.select_related('type').select_related('ipv4')\
.select_related('domain__extension').select_related('ipv4__ip_type')\
.distinct()
return (interface_set
.filter(
machine__in=Machine.objects.filter(
user__in=all_has_access()
).filter(active=True)
).select_related('domain')
.select_related('machine')
.select_related('type')
.select_related('ipv4')
.select_related('domain__extension')
.select_related('ipv4__ip_type')
.distinct())
def filter_complete_interfaces(interface_set):
@ -160,6 +159,7 @@ def all_active_assigned_interfaces_count():
""" Version light seulement pour compter"""
return all_active_interfaces_count().filter(ipv4__isnull=False)
class SortTable:
""" 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
@ -171,7 +171,8 @@ class SortTable:
# 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.
# 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 = {
'user_name': ['name'],
'user_surname': ['surname'],
@ -255,7 +256,7 @@ class SortTable:
}
TOPOLOGIE_INDEX_MODEL_SWITCH = {
'model-switch_name': ['reference'],
'model-switch_contructor' : ['constructor__name'],
'model-switch_contructor': ['constructor__name'],
'default': ['reference'],
}
TOPOLOGIE_INDEX_SWITCH_BAY = {
@ -290,6 +291,7 @@ class SortTable:
else:
return request
def re2o_paginator(request, query_set, pagination_number):
"""Paginator script for list display in re2o.
:request:
@ -307,6 +309,7 @@ def re2o_paginator(request, query_set, pagination_number):
results = paginator.page(paginator.num_pages)
return results
def remove_user_room(room):
""" Déménage de force l'ancien locataire de la chambre """
try:

View file

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

View file

@ -26,7 +26,8 @@
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.
Args:

View file

@ -19,7 +19,10 @@
# 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.
"""search.tests
The tests for the Search module.
"""
from django.test import TestCase
# from django.test import TestCase
# 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]:
filter_users &= Q(id=user.id)
filter_clubs = filter_users
filter_users |= Q(name__icontains=word)
filter_users |= Q(name__icontains=word)
filters['users'] |= filter_users
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
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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 *

View file

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

View file

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

View file

@ -47,9 +47,10 @@ from django.db import IntegrityError
from django.db import transaction
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
class Stack(AclMixin, RevMixin, models.Model):
"""Un objet stack. Regrouppe des switchs en foreign key
,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):
"""Define a wireless AP. Inherit from machines.interfaces
Definition pour une borne wifi , hérite de machines.interfaces
"""
PRETTY_NAME = "Borne WiFi"
location = models.CharField(
location = models.CharField(
max_length=255,
help_text="Détails sur la localisation de l'AP",
blank=True,
@ -120,7 +121,6 @@ class Switch(AclMixin, Machine):
id_max de la stack parente"""
PRETTY_NAME = "Switch / Commutateur"
number = models.PositiveIntegerField()
stack = models.ForeignKey(
'topologie.Stack',
@ -165,7 +165,8 @@ class Switch(AclMixin, Machine):
ne peut être nul"})
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
nb_ports = self.ports.count()
@ -192,6 +193,7 @@ class Switch(AclMixin, Machine):
ValidationError("Création d'un port existant.")
def main_interface(self):
""" Returns the 'main' interface of the switch """
return self.interface_set.first()
def __str__(self):
@ -331,14 +333,15 @@ class Port(AclMixin, RevMixin, models.Model):
("view_port", "Peut voir un objet port"),
)
def get_instance(portid, *args, **kwargs):
return Port.objects\
.select_related('machine_interface__domain__extension')\
.select_related('machine_interface__machine__switch')\
.select_related('room')\
.select_related('related')\
.prefetch_related('switch__interface_set__domain__extension')\
.get(pk=portid)
@classmethod
def get_instance(cls, portid, *_args, **kwargs):
return (cls.objects
.select_related('machine_interface__domain__extension')
.select_related('machine_interface__machine__switch')
.select_related('room')
.select_related('related')
.prefetch_related('switch__interface_set__domain__extension')
.get(pk=portid))
def make_port_related(self):
""" Synchronise le port distant sur self"""
@ -363,18 +366,24 @@ class Port(AclMixin, RevMixin, models.Model):
cohérence"""
if hasattr(self, 'switch'):
if self.port > self.switch.number:
raise ValidationError("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:
raise ValidationError("Chambre, interface et related_port sont\
mutuellement exclusifs")
raise ValidationError(
"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):
raise ValidationError(
"Chambre, interface et related_port sont mutuellement "
"exclusifs"
)
if self.related == self:
raise ValidationError("On ne peut relier un port à lui même")
if self.related and not self.related.related:
if self.related.machine_interface or self.related.room:
raise ValidationError("Le port relié est déjà occupé, veuillez\
le libérer avant de créer une relation")
raise ValidationError(
"Le port relié est déjà occupé, veuillez le libérer "
"avant de créer une relation"
)
else:
self.make_port_related()
elif hasattr(self, 'related_port'):
@ -402,18 +411,18 @@ class Room(AclMixin, RevMixin, models.Model):
@receiver(post_save, sender=AccessPoint)
def ap_post_save(sender, **kwargs):
def ap_post_save(**_kwargs):
"""Regeneration des noms des bornes vers le controleur"""
regen('unifi-ap-names')
@receiver(post_delete, sender=AccessPoint)
def ap_post_delete(sender, **kwargs):
def ap_post_delete(**_kwargs):
"""Regeneration des noms des bornes vers le controleur"""
regen('unifi-ap-names')
@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"""
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
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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.

View file

@ -51,12 +51,10 @@ urlpatterns = [
url(r'^switch/(?P<switchid>[0-9]+)$',
views.index_port,
name='index-port'),
url(
r'^history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
url(r'^history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
re2o.views.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'^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'),
@ -64,7 +62,9 @@ urlpatterns = [
views.edit_switch,
name='edit-switch'),
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]+)$',
views.edit_stack,
name='edit-stack'),
@ -73,16 +73,13 @@ urlpatterns = [
name='del-stack'),
url(r'^index_model_switch/$',
views.index_model_switch,
name='index-model-switch'
),
name='index-model-switch'),
url(r'^index_model_switch/$',
views.index_model_switch,
name='index-model-switch'
),
name='index-model-switch'),
url(r'^new_model_switch/$',
views.new_model_switch,
name='new-model-switch'
),
name='new-model-switch'),
url(r'^edit_model_switch/(?P<modelswitchid>[0-9]+)$',
views.edit_model_switch,
name='edit-model-switch'),
@ -91,8 +88,7 @@ urlpatterns = [
name='del-model-switch'),
url(r'^new_constructor_switch/$',
views.new_constructor_switch,
name='new-constructor-switch'
),
name='new-constructor-switch'),
url(r'^edit_constructor_switch/(?P<constructorswitchid>[0-9]+)$',
views.edit_constructor_switch,
name='edit-constructor-switch'),
@ -101,8 +97,7 @@ urlpatterns = [
name='del-constructor-switch'),
url(r'^new_switch_bay/$',
views.new_switch_bay,
name='new-switch-bay'
),
name='new-switch-bay'),
url(r'^edit_switch_bay/(?P<switchbayid>[0-9]+)$',
views.edit_switch_bay,
name='edit-switch-bay'),
@ -111,8 +106,7 @@ urlpatterns = [
name='del-switch-bay'),
url(r'^new_building/$',
views.new_building,
name='new-building'
),
name='new-building'),
url(r'^edit_building/(?P<buildingid>[0-9]+)$',
views.edit_building,
name='edit-building'),

View file

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

View file

@ -26,6 +26,7 @@
Here are defined some functions to check acl on the application.
"""
def can_view(user):
"""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):
"""Administration du ldapuser"""
list_display = ('name', 'uidNumber', 'login_shell')
@ -143,7 +130,8 @@ class UserAdmin(VersionAdmin, BaseUserAdmin):
'is_admin',
'shell'
)
list_display = ('pseudo',)
# Need to reset the settings from BaseUserAdmin
# They are using fields we don't use like 'is_staff'
list_filter = ()
fieldsets = (
(None, {'fields': ('pseudo', 'password')}),
@ -175,7 +163,7 @@ class UserAdmin(VersionAdmin, BaseUserAdmin):
}
),
)
search_fields = ('pseudo',)
search_fields = ('pseudo', 'surname')
ordering = ('pseudo',)
filter_horizontal = ()

View file

@ -41,6 +41,10 @@ from django.utils import timezone
from django.contrib.auth.models import Group, Permission
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 (
User,
ServiceUser,
@ -52,9 +56,6 @@ from .models import (
Adherent,
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):
@ -89,12 +90,16 @@ class PassForm(FormRevMixin, FieldPermissionFormMixin, forms.ModelForm):
password1 = self.cleaned_data.get("passwd1")
password2 = self.cleaned_data.get("passwd2")
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
def clean_selfpasswd(self):
"""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")
return
@ -386,7 +391,11 @@ class ClubAdminandMembersForm(FormRevMixin, ModelForm):
def __init__(self, *args, **kwargs):
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):

View file

@ -19,12 +19,14 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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 users.forms import PassForm
from re2o.script_utils import get_user, get_system_user, form_cli
class Command(BaseCommand):
help = "Changer le mot de passe d'un utilisateur"
@ -42,6 +44,13 @@ class Command(BaseCommand):
if not ok:
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.,
# 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.db import transaction
@ -28,6 +30,7 @@ from reversion import revisions as reversion
from users.models import User, ListShell
from re2o.script_utils import get_user, get_system_user
class Command(BaseCommand):
help = 'Change the default shell of a user'
@ -36,13 +39,13 @@ class Command(BaseCommand):
def handle(self, *args, **options):
current_username = get_system_user()
current_username = get_system_user()
current_user = get_user(current_username)
target_username = options["target_username"] or current_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)
if not ok:
raise CommandError(msg)
@ -52,9 +55,16 @@ class Command(BaseCommand):
current_shell = "inconnu"
if target_user.shell:
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:
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 : ")
try:
@ -72,4 +82,7 @@ class Command(BaseCommand):
reversion.set_user(current_user)
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.
#
# Copyright © 2018 Benjamin Graillot
#
# Copyright © 2013-2015 Raphaël-David Lasseri <lasseri@crans.org>
#
# 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.
# 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, [
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>.*)',
@ -48,8 +48,10 @@ DATE_FORMATS = [
"%a %b %d CEST %H:%M:%S%Y"
]
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):
@ -65,7 +67,9 @@ class Command(BaseCommand):
for i, regex in enumerate(COMPILED_REGEX):
m = regex.match(line)
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
parsed_log = parse_logs(sys.stdin)

View file

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

View file

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

View file

@ -37,6 +37,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='user',
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(
model_name='user',
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.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.field_permissions import FieldPermissionModelMixin
from re2o.mixins import AclMixin, RevMixin
@ -153,7 +153,7 @@ class UserManager(BaseUserManager):
user.set_password(password)
if su:
user.is_superuser=True
user.is_superuser = True
user.save(using=self._db)
return user
@ -171,7 +171,9 @@ class UserManager(BaseUserManager):
"""
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.
Champs principaux : name, surnname, pseudo, email, room, password
Herite du django BaseUser et du système d'auth django"""
@ -185,10 +187,6 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
(2, 'STATE_ARCHIVE'),
)
def auto_uid():
"""Renvoie un uid libre"""
return get_fresh_user_uid()
surname = models.CharField(max_length=255)
pseudo = models.CharField(
max_length=32,
@ -218,8 +216,15 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
state = models.IntegerField(choices=STATES, default=STATE_ACTIVE)
registered = models.DateTimeField(auto_now_add=True)
telephone = models.CharField(max_length=15, blank=True, null=True)
uid_number = models.PositiveIntegerField(default=auto_uid, unique=True)
rezo_rez_uid = models.PositiveIntegerField(unique=True, blank=True, null=True)
uid_number = models.PositiveIntegerField(
default=get_fresh_user_uid,
unique=True
)
rezo_rez_uid = models.PositiveIntegerField(
unique=True,
blank=True,
null=True
)
USERNAME_FIELD = 'pseudo'
REQUIRED_FIELDS = ['surname', 'email']
@ -228,13 +233,18 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
class Meta:
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_force", "Peut forcer un déménagement"),
("change_user_shell", "Peut éditer le shell d'un user"),
("change_user_groups", "Peut éditer les groupes d'un user ! Permission critique"),
("change_all_users", "Peut éditer tous les users, y compris ceux dotés de droits. Superdroit"),
("view_user", "Peut voir un objet user quelquonque"),
("change_user_groups",
"Peut éditer les groupes d'un user ! Permission critique"),
("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
@ -267,10 +277,14 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
@cached_property
def is_class_club(self):
""" Returns True if the object is a Club (subclassing User) """
# TODO : change to isinstance (cleaner)
return hasattr(self, 'club')
@cached_property
def is_class_adherent(self):
""" Returns True if the object is a Adherent (subclassing User) """
# TODO : change to isinstance (cleaner)
return hasattr(self, 'adherent')
@property
@ -286,7 +300,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
@property
def is_admin(self):
""" 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()
def get_full_name(self):
@ -393,8 +407,9 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
def has_access(self):
""" Renvoie si un utilisateur a accès à internet """
return self.state == User.STATE_ACTIVE\
and not self.is_ban() and (self.is_connected() or self.is_whitelisted())
return (self.state == User.STATE_ACTIVE and
not self.is_ban() and
(self.is_connected() or self.is_whitelisted()))
def end_access(self):
""" Renvoie la date de fin normale d'accès (adhésion ou whiteliste)"""
@ -480,7 +495,8 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
self.assign_ips()
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
self
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'),
'site_name': GeneralOption.get_cached_value('site_name'),
'url': request.build_absolute_uri(
reverse('users:process', kwargs={'token': req.token})),
'expire_in': str(GeneralOption.get_cached_value('req_expire_hrs')) + ' heures',
}
reverse('users:process', kwargs={'token': req.token})
),
'expire_in': str(
GeneralOption.get_cached_value('req_expire_hrs')
) + ' heures',
}
send_mail(
'Changement de mot de passe du %(name)s / Password\
renewal for %(name)s' % {'name': AssoOption.get_cached_value('name')},
'Changement de mot de passe du %(name)s / Password renewal for '
'%(name)s' % {'name': AssoOption.get_cached_value('name')},
template.render(context),
GeneralOption.get_cached_value('email_from'),
[req.user.email],
@ -590,7 +609,9 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
""" Fonction appellée par freeradius. Enregistre la mac pour
une machine inconnue sur le compte de l'user"""
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"
if not nas_type:
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')
context = Context({
'nom': self.get_full_name(),
'mac_address' : interface.mac_address,
'mac_address': interface.mac_address,
'asso_name': AssoOption.get_cached_value('name'),
'interface_name' : interface.domain,
'interface_name': interface.domain,
'asso_email': AssoOption.get_cached_value('contact'),
'pseudo': self.pseudo,
})
@ -668,19 +689,19 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
num += 1
return composed_pseudo(num)
def can_edit(self, user_request, *args, **kwargs):
"""Check if an user can edit an user object.
def can_edit(self, user_request, *_args, **_kwargs):
"""Check if a user can edit a user object.
:param self: The user which is to be edited.
: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
user_request one of its member, or if user_request is self, or if
user_request has the 'cableur' right.
user_request one of its member, or if user_request is self, or if
user_request has the 'cableur' right.
"""
if self.is_class_club and user_request.is_class_adherent:
if self == user_request or \
user_request.has_perm('users.change_user') or \
user_request.adherent in self.club.administrators.all():
if (self == user_request or
user_request.has_perm('users.change_user') or
user_request.adherent in self.club.administrators.all()):
return True, None
else:
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
elif user_request.has_perm('users.change_user'):
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'):
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:
return True, None
elif user_request.has_perm('users.change_all_users'):
return True, None
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 == user_request or \
user_request.has_perm('users.change_user_password') or \
user_request.adherent in self.club.administrators.all():
if (self == user_request or
user_request.has_perm('users.change_user_password') or
user_request.adherent in self.club.administrators.all()):
return True, None
else:
return False, u"Vous n'avez pas le droit d'éditer ce club"
else:
if self == user_request or \
user_request.has_perm('users.change_user_groups'):
# Peut éditer les groupes d'un user, c'est un privilège élevé, True
if (self == user_request or
user_request.has_perm('users.change_user_groups')):
# Peut éditer les groupes d'un user,
# c'est un privilège élevé, True
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
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
@staticmethod
def can_change_state(user_request, *args, **kwargs):
return user_request.has_perm('users.change_user_state'), "Droit requis pour changer l'état"
def can_change_state(user_request, *_args, **_kwargs):
""" 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
def can_change_shell(user_request, *args, **kwargs):
return user_request.has_perm('users.change_user_shell'), "Droit requis pour changer le shell"
def can_change_shell(user_request, *_args, **_kwargs):
""" 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
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"
def can_change_force(user_request, *_args, **_kwargs):
""" 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
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"
def can_change_groups(user_request, *_args, **_kwargs):
""" 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.
:param self: The targeted user.
:param user_request: The user who ask for viewing the target.
: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 == user_request or \
user_request.has_perm('users.view_user') or \
user_request.adherent in self.club.administrators.all() or \
user_request.adherent in self.club.members.all():
if (self == user_request or
user_request.has_perm('users.view_user') or
user_request.adherent in self.club.administrators.all() or
user_request.adherent in self.club.members.all()):
return True, None
else:
return False, u"Vous n'avez pas le droit de voir ce club"
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
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
: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.
:param self: The user who is to be deleted.
: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):
super(User, self).__init__(*args, **kwargs)
self.field_permissions = {
'shell' : self.can_change_shell,
'force' : self.can_change_force,
'selfpasswd' : self.check_selfpasswd,
'shell': self.can_change_shell,
'force': self.can_change_force,
'selfpasswd': self.check_selfpasswd,
}
def __str__(self):
@ -790,6 +875,8 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
class Adherent(User):
""" A class representing a member (it's a user with special
informations) """
PRETTY_NAME = "Adhérents"
name = models.CharField(max_length=255)
room = models.OneToOneField(
@ -799,32 +886,40 @@ class Adherent(User):
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.
:param adherentid: The id of the adherent we are looking for.
: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.
: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
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
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
else:
return user_request.has_perm('users.add_user'), u"Vous n'avez pas le\
droit de créer un utilisateur"
return (
user_request.has_perm('users.add_user'),
u"Vous n'avez pas le droit de créer un utilisateur"
)
class Club(User):
""" A class representing a club (it is considered as a user
with special informations) """
PRETTY_NAME = "Clubs"
room = models.ForeignKey(
'topologie.Room',
@ -843,15 +938,16 @@ class Club(User):
related_name='club_members'
)
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.
: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
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:
return False, None
@ -859,54 +955,68 @@ class Club(User):
if OptionalUser.get_cached_value('all_can_create_club'):
return True, None
else:
return user_request.has_perm('users.add_user'), u"Vous n'avez pas le\
droit de créer un club"
return (
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
: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'):
return True, None
if hasattr(user_request,'is_class_adherent') and user_request.is_class_adherent:
if user_request.adherent.club_administrator.all() or user_request.adherent.club_members.all():
if (hasattr(user_request, 'is_class_adherent') and
user_request.is_class_adherent):
if (user_request.adherent.club_administrator.all() or
user_request.adherent.club_members.all()):
return True, None
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.
:param clubid: The id of the adherent we are looking for.
:return: A club.
"""
return Club.objects.get(pk=clubid)
return cls.objects.get(pk=clubid)
@receiver(post_save, sender=Adherent)
@receiver(post_save, sender=Club)
@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
Synchronise le ldap"""
is_created = kwargs['created']
# is_created = kwargs['created']
user = kwargs['instance']
#if is_created:
#user.notif_inscription()
user.ldap_sync(base=True, access_refresh=True, mac_refresh=False, group_refresh=True)
# TODO : remove if unnecessary
# if is_created:
# user.notif_inscription()
user.ldap_sync(
base=True,
access_refresh=True,
mac_refresh=False,
group_refresh=True
)
regen('mailing')
@receiver(post_delete, sender=Adherent)
@receiver(post_delete, sender=Club)
@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"""
user = kwargs['instance']
user.ldap_del()
regen('mailing')
class ServiceUser(RevMixin, AclMixin, AbstractBaseUser):
""" Classe des users daemons, règle leurs accès au ldap"""
readonly = 'readonly'
@ -943,6 +1053,14 @@ class ServiceUser(RevMixin, AclMixin, AbstractBaseUser):
("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):
""" Synchronisation du ServiceUser dans sa version ldap"""
try:
@ -977,15 +1095,16 @@ class ServiceUser(RevMixin, AclMixin, AbstractBaseUser):
def __str__(self):
return self.pseudo
@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"""
service_user = kwargs['instance']
service_user.ldap_sync()
@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"""
service_user = kwargs['instance']
service_user.ldap_del()
@ -1019,8 +1138,8 @@ class ListRight(RevMixin, AclMixin, Group):
unique=True,
validators=[RegexValidator(
'^[a-z]+$',
message="Les groupes unix ne peuvent contenir\
que des lettres minuscules"
message=("Les groupes unix ne peuvent contenir que des lettres "
"minuscules")
)]
)
gid = models.PositiveIntegerField(unique=True, null=True)
@ -1060,14 +1179,14 @@ class ListRight(RevMixin, AclMixin, Group):
@receiver(post_save, sender=ListRight)
def listright_post_save(sender, **kwargs):
def listright_post_save(**kwargs):
""" Synchronise le droit ldap quand il est modifié"""
right = kwargs['instance']
right.ldap_sync()
@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"""
right = kwargs['instance']
right.ldap_del()
@ -1140,7 +1259,7 @@ class Ban(RevMixin, AclMixin, models.Model):
"""Ce ban est-il actif?"""
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.
: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
text
"""
if not user_request.has_perm('users.view_ban') and\
self.user != user_request:
return False, u"Vous n'avez pas le droit de voir les bannissements\
autre que les vôtres"
if (not user_request.has_perm('users.view_ban') and
self.user != user_request):
return False, (u"Vous n'avez pas le droit de voir les "
"bannissements autre que les vôtres")
else:
return True, None
@ -1160,7 +1279,7 @@ class Ban(RevMixin, AclMixin, models.Model):
@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"""
ban = kwargs['instance']
is_created = kwargs['created']
@ -1177,7 +1296,7 @@ def ban_post_save(sender, **kwargs):
@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"""
user = kwargs['instance'].user
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):
""" Is this whitelisting active ? """
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.
: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
text
"""
if not user_request.has_perm('users.view_whitelist') and\
self.user != user_request:
return False, u"Vous n'avez pas le droit de voir les accès\
gracieux autre que les vôtres"
if (not user_request.has_perm('users.view_whitelist') and
self.user != user_request):
return False, (u"Vous n'avez pas le droit de voir les accès "
"gracieux autre que les vôtres")
else:
return True, None
@ -1225,7 +1345,7 @@ class Whitelist(RevMixin, AclMixin, models.Model):
@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
et on lui permet d'avoir internet"""
whitelist = kwargs['instance']
@ -1242,7 +1362,7 @@ def whitelist_post_save(sender, **kwargs):
@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
en forçant la régénration"""
user = kwargs['instance'].user
@ -1270,8 +1390,12 @@ class Request(models.Model):
def save(self):
if not self.expires_at:
self.expires_at = timezone.now() \
+ datetime.timedelta(hours=GeneralOption.get_cached_value('req_expire_hrs'))
self.expires_at = (timezone.now() +
datetime.timedelta(
hours=GeneralOption.get_cached_value(
'req_expire_hrs'
)
))
if not self.token:
self.token = str(uuid.uuid4()).replace('-', '') # remove hyphens
super(Request, self).save()
@ -1375,7 +1499,10 @@ class LdapUserGroup(ldapdb.models.Model):
# attributes
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(
db_column='cn',
max_length=200,

View file

@ -20,19 +20,30 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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 users.models import Club, Adherent
class MailingSerializer(serializers.ModelSerializer):
""" Serializer to build Mailing objects """
name = serializers.CharField(source='pseudo')
class Meta:
model = Club
fields = ('name',)
class MailingMemberSerializer(serializers.ModelSerializer):
""" Serializer fot the Adherent objects (who belong to a
Mailing) """
class Meta:
model = Adherent
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">
<table class="table table-striped">
<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>
{% endif %}
<th>Nom</th>
<td>{{ users.surname }}</td>
</tr>

View file

@ -19,7 +19,10 @@
# 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.
"""users.tests
The tests for the Users module.
"""
from django.test import TestCase
# from django.test import TestCase
# Create your tests here.

View file

@ -34,109 +34,79 @@ urlpatterns = [
url(r'^new_user/$', views.new_user, name='new-user'),
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_club_admin_members/(?P<clubid>[0-9]+)$',
url(r'^edit_club_admin_members/(?P<clubid>[0-9]+)$',
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'^groups/(?P<userid>[0-9]+)$', views.groups, name='groups'),
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'^edit_serviceuser/(?P<serviceuserid>[0-9]+)$',
url(r'^edit_serviceuser/(?P<serviceuserid>[0-9]+)$',
views.edit_serviceuser,
name='edit-serviceuser'
),
url(
r'^del_serviceuser/(?P<serviceuserid>[0-9]+)$',
name='edit-serviceuser'),
url(r'^del_serviceuser/(?P<serviceuserid>[0-9]+)$',
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'^edit_ban/(?P<banid>[0-9]+)$', views.edit_ban, name='edit-ban'),
url(
r'^add_whitelist/(?P<userid>[0-9]+)$',
url(r'^add_whitelist/(?P<userid>[0-9]+)$',
views.add_whitelist,
name='add-whitelist'
),
url(
r'^edit_whitelist/(?P<whitelistid>[0-9]+)$',
name='add-whitelist'),
url(r'^edit_whitelist/(?P<whitelistid>[0-9]+)$',
views.edit_whitelist,
name='edit-whitelist'
),
name='edit-whitelist'),
url(r'^add_school/$', views.add_school, name='add-school'),
url(
r'^edit_school/(?P<schoolid>[0-9]+)$',
url(r'^edit_school/(?P<schoolid>[0-9]+)$',
views.edit_school,
name='edit-school'
),
name='edit-school'),
url(r'^del_school/$', views.del_school, name='del-school'),
url(r'^add_listright/$', views.add_listright, name='add-listright'),
url(
r'^edit_listright/(?P<listrightid>[0-9]+)$',
url(r'^edit_listright/(?P<listrightid>[0-9]+)$',
views.edit_listright,
name='edit-listright'
),
name='edit-listright'),
url(r'^del_listright/$', views.del_listright, name='del-listright'),
url(r'^add_shell/$', views.add_shell, name='add-shell'),
url(
r'^edit_shell/(?P<listshellid>[0-9]+)$',
url(r'^edit_shell/(?P<listshellid>[0-9]+)$',
views.edit_shell,
name='edit-shell'
),
url(
r'^del_shell/(?P<listshellid>[0-9]+)$',
name='edit-shell'),
url(r'^del_shell/(?P<listshellid>[0-9]+)$',
views.del_shell,
name='del-shell'
),
name='del-shell'),
url(r'^profil/(?P<userid>[0-9]+)$', views.profil, name='profil'),
url(r'^index_ban/$', views.index_ban, name='index-ban'),
url(r'^index_white/$', views.index_white, name='index-white'),
url(r'^index_school/$', views.index_school, name='index-school'),
url(r'^index_shell/$', views.index_shell, name='index-shell'),
url(r'^index_listright/$', views.index_listright, name='index-listright'),
url(
r'^index_serviceusers/$',
url(r'^index_serviceusers/$',
views.index_serviceusers,
name='index-serviceusers'
),
name='index-serviceusers'),
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'^reset_password/$', views.reset_password, name='reset-password'),
url(r'^mass_archive/$', views.mass_archive, name='mass-archive'),
url(
r'^history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
url(r'^history/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$',
re2o.views.history,
name='history',
kwargs={'application':'users'},
),
kwargs={'application': 'users'}),
url(r'^$', views.index, name='index'),
url(r'^index_clubs/$', views.index_clubs, name='index-clubs'),
url(
r'^rest/ml/std/$',
url(r'^rest/ml/std/$',
views.ml_std_list,
name='ml-std-list'
),
url(
r'^rest/ml/std/member/(?P<ml_name>\w+)/$',
name='ml-std-list'),
url(r'^rest/ml/std/member/(?P<ml_name>\w+)/$',
views.ml_std_members,
name='ml-std-members'
),
url(
r'^rest/ml/club/$',
name='ml-std-members'),
url(r'^rest/ml/club/$',
views.ml_club_list,
name='ml-club-list'
),
url(
r'^rest/ml/club/admin/(?P<ml_name>\w+)/$',
name='ml-club-list'),
url(r'^rest/ml/club/admin/(?P<ml_name>\w+)/$',
views.ml_club_admins,
name='ml-club-admins'
),
url(
r'^rest/ml/club/member/(?P<ml_name>\w+)/$',
name='ml-club-admins'),
url(r'^rest/ml/club/member/(?P<ml_name>\w+)/$',
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.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required
from django.db.models import ProtectedError, Q
from django.db import IntegrityError
from django.db.models import ProtectedError
from django.utils import timezone
from django.db import transaction
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from reversion.models import Version
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,
Ban,
Whitelist,
@ -66,7 +81,7 @@ from users.models import (
Club,
ListShell,
)
from users.forms import (
from .forms import (
BanForm,
WhitelistForm,
DelSchoolForm,
@ -86,25 +101,7 @@ from users.forms import (
ClubAdminandMembersForm,
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)
def new_user(request):
@ -121,9 +118,19 @@ def new_user(request):
pour l'initialisation du mot de passe a été envoyé" % user.pseudo)
return redirect(reverse(
'users:profil',
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)
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
)
@login_required
@ -140,26 +147,41 @@ def new_club(request):
pour l'initialisation du mot de passe a été envoyé" % club.pseudo)
return redirect(reverse(
'users:profil',
kwargs={'userid':str(club.id)}
))
return form({'userform': club, 'showCGU':False, 'action_name':'Créer un club'}, 'users/user.html', request)
kwargs={'userid': str(club.id)}
))
return form(
{'userform': club, 'showCGU': False, 'action_name': 'Créer un club'},
'users/user.html',
request
)
@login_required
@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
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.changed_data:
club.save()
messages.success(request, "Le club a bien été modifié")
return redirect(reverse(
'users:profil',
kwargs={'userid':str(club_instance.id)}
))
return form({'userform': club, 'showCGU':False, 'action_name':'Editer les admin et membres'}, 'users/user.html', request)
kwargs={'userid': str(club_instance.id)}
))
return form(
{
'userform': club,
'showCGU': False,
'action_name': 'Editer les admin et membres'
},
'users/user.html',
request
)
@login_required
@ -169,26 +191,30 @@ def edit_info(request, user, userid):
si l'id est différent de request.user, vérifie la
possession du droit cableur """
if user.is_class_adherent:
user = AdherentForm(
user_form = AdherentForm(
request.POST or None,
instance=user.adherent,
user=request.user
)
elif user.is_class_club:
user = ClubForm(
else:
user_form = ClubForm(
request.POST or None,
instance=user.club,
user=request.user
)
if user.is_valid():
if user.changed_data:
user.save()
if user_form.is_valid():
if user_form.changed_data:
user_form.save()
messages.success(request, "L'user a bien été modifié")
return redirect(reverse(
'users:profil',
kwargs={'userid':str(userid)}
))
return form({'userform': user, 'action_name': "Editer l'utilisateur"}, 'users/user.html', request)
kwargs={'userid': str(userid)}
))
return form(
{'userform': user_form, 'action_name': "Editer l'utilisateur"},
'users/user.html',
request
)
@login_required
@ -196,35 +222,44 @@ def edit_info(request, user, userid):
def state(request, user, userid):
""" Changer l'etat actif/desactivé/archivé d'un user,
need droit bureau """
state = StateForm(request.POST or None, instance=user)
if state.is_valid():
if state.changed_data:
if state.cleaned_data['state'] == User.STATE_ARCHIVE:
state_form = StateForm(request.POST or None, instance=user)
if state_form.is_valid():
if state_form.changed_data:
if state_form.cleaned_data['state'] == User.STATE_ARCHIVE:
user.archive()
elif state.cleaned_data['state'] == User.STATE_ACTIVE:
elif state_form.cleaned_data['state'] == User.STATE_ACTIVE:
user.unarchive()
state.save()
state_form.save()
messages.success(request, "Etat changé avec succès")
return redirect(reverse(
'users:profil',
kwargs={'userid':str(userid)}
))
return form({'userform': state, 'action_name': "Editer l'état"}, 'users/user.html', request)
kwargs={'userid': str(userid)}
))
return form(
{'userform': state_form, 'action_name': "Editer l'état"},
'users/user.html',
request
)
@login_required
@can_edit(User, 'groups')
def groups(request, user, userid):
group = GroupForm(request.POST or None, instance=user)
if group.is_valid():
if group.changed_data:
group.save()
""" View to edit the groups of a user """
group_form = GroupForm(request.POST or None, instance=user)
if group_form.is_valid():
if group_form.changed_data:
group_form.save()
messages.success(request, "Groupes changés avec succès")
return redirect(reverse(
'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
@ -239,15 +274,20 @@ def password(request, user, userid):
u_form.save()
messages.success(request, "Le mot de passe a changé")
return redirect(reverse(
'users:profil',
kwargs={'userid':str(user.id)}
'users:profil',
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
@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.save()
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
)
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
@can_edit(ServiceUser)
def edit_serviceuser(request, serviceuser, serviceuserid):
def edit_serviceuser(request, serviceuser, **_kwargs):
""" Edit a ServiceUser """
serviceuser = EditServiceUserForm(request.POST or None, instance=serviceuser)
serviceuser = EditServiceUserForm(
request.POST or None,
instance=serviceuser
)
if serviceuser.is_valid():
user_object = serviceuser.save(commit=False)
if serviceuser.cleaned_data['password']:
@ -284,12 +331,16 @@ def edit_serviceuser(request, serviceuser, serviceuserid):
user_object.save()
messages.success(request, "L'user a bien été modifié")
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
@can_delete(ServiceUser)
def del_serviceuser(request, serviceuser, serviceuserid):
def del_serviceuser(request, serviceuser, **_kwargs):
"""Suppression d'un ou plusieurs serviceusers"""
if request.method == "POST":
serviceuser.delete()
@ -312,22 +363,27 @@ def add_ban(request, user, userid):
ban_instance = Ban(user=user)
ban = BanForm(request.POST or None, instance=ban_instance)
if ban.is_valid():
_ban_object = ban.save()
ban.save()
messages.success(request, "Bannissement ajouté")
return redirect(reverse(
'users:profil',
kwargs={'userid':str(userid)}
kwargs={'userid': str(userid)}
))
if user.is_ban():
messages.error(
request,
"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
@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
(a fortiori bureau)
Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement"""
@ -337,7 +393,11 @@ def edit_ban(request, ban_instance, banid):
ban.save()
messages.success(request, "Bannissement modifié")
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
@ -358,19 +418,23 @@ def add_whitelist(request, user, userid):
messages.success(request, "Accès à titre gracieux accordé")
return redirect(reverse(
'users:profil',
kwargs={'userid':str(userid)}
))
kwargs={'userid': str(userid)}
))
if user.is_whitelisted():
messages.error(
request,
"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
@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.
Need droit cableur
Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement,
@ -384,7 +448,11 @@ def edit_whitelist(request, whitelist_instance, whitelistid):
whitelist.save()
messages.success(request, "Whitelist modifiée")
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
@ -397,12 +465,16 @@ def add_school(request):
school.save()
messages.success(request, "L'établissement a été ajouté")
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
@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
la base de donnée, need cableur"""
school = SchoolForm(request.POST or None, instance=school_instance)
@ -411,7 +483,11 @@ def edit_school(request, school_instance, schoolid):
school.save()
messages.success(request, "Établissement modifié")
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
@ -434,7 +510,11 @@ def del_school(request, instances):
"L'établissement %s est affecté à au moins un user, \
vous ne pouvez pas le supprimer" % school_del)
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
@ -446,12 +526,16 @@ def add_shell(request):
shell.save()
messages.success(request, "Le shell a été ajouté")
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
@can_edit(ListShell)
def edit_shell(request, shell_instance, listshellid):
def edit_shell(request, shell_instance, **_kwargs):
""" Editer un shell à partir du listshellid"""
shell = ShellForm(request.POST or None, instance=shell_instance)
if shell.is_valid():
@ -459,12 +543,16 @@ def edit_shell(request, shell_instance, listshellid):
shell.save()
messages.success(request, "Le shell a été modifié")
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
@can_delete(ListShell)
def del_shell(request, shell, listshellid):
def del_shell(request, shell, **_kwargs):
"""Destruction d'un shell"""
if request.method == "POST":
shell.delete()
@ -487,12 +575,16 @@ def add_listright(request):
listright.save()
messages.success(request, "Le droit/groupe a été ajouté")
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
@can_edit(ListRight)
def edit_listright(request, listright_instance, listrightid):
def edit_listright(request, listright_instance, **_kwargs):
""" Editer un groupe/droit, necessite droit bureau,
à partir du listright id """
listright = ListRightForm(
@ -504,7 +596,11 @@ def edit_listright(request, listright_instance, listrightid):
listright.save()
messages.success(request, "Droit modifié")
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
@ -525,7 +621,11 @@ def del_listright(request, instances):
"Le groupe %s est affecté à au moins un user, \
vous ne pouvez pas le supprimer" % listright_del)
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
@ -587,7 +687,11 @@ def index_clubs(request):
SortTable.USERS_INDEX
)
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
@ -688,13 +792,13 @@ def mon_profil(request):
""" Lien vers profil, renvoie request.id à la fonction """
return redirect(reverse(
'users:profil',
kwargs={'userid':str(request.user.id)}
kwargs={'userid': str(request.user.id)}
))
@login_required
@can_view(User)
def profil(request, users, userid):
def profil(request, users, **_kwargs):
""" Affiche un profil, self or cableur, prend un userid en argument """
machines = Machine.objects.filter(user=users).select_related('user')\
.prefetch_related('interface_set__domain__extension')\
@ -707,7 +811,9 @@ def profil(request, users, userid):
request.GET.get('order'),
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)
factures = Facture.objects.filter(user=users)
factures = SortTable.sort(
@ -742,7 +848,7 @@ def profil(request, users, userid):
'ban_list': bans,
'white_list': whitelists,
'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:
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)
messages.success(request, "Un mail pour l'initialisation du mot\
de passe a été envoyé")
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):
@ -790,7 +904,11 @@ def process_passwd(request, req):
req.delete()
messages.success(request, "Le mot de passe a changé")
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):
@ -804,7 +922,7 @@ class JSONResponse(HttpResponse):
@csrf_exempt
@login_required
@permission_required('machines.serveur')
def ml_std_list(request):
def ml_std_list(_request):
""" API view sending all the available standard mailings"""
return JSONResponse([
{'name': 'adherents'}
@ -830,7 +948,7 @@ def ml_std_members(request, ml_name):
@csrf_exempt
@login_required
@permission_required('machines.serveur')
def ml_club_list(request):
def ml_club_list(_request):
""" API view sending all the available club mailings"""
clubs = Club.objects.filter(mailing=True).values('pseudo')
seria = MailingSerializer(clubs, many=True)
@ -862,6 +980,9 @@ def ml_club_members(request, ml_name):
except Club.DoesNotExist:
messages.error(request, "Cette mailing n'existe pas")
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)
return JSONResponse(seria.data)