mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-27 07:02:26 +00:00
Merge branch 'master' into graph_topo
This commit is contained in:
commit
8e057db5ea
96 changed files with 4512 additions and 2178 deletions
|
@ -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,13 +230,19 @@ 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):
|
||||
@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}
|
||||
|
||||
|
||||
|
@ -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,9 +409,21 @@ 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:
|
||||
{
|
||||
""" 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()],
|
||||
|
@ -348,9 +432,11 @@ class OuverturePortsSerializer(serializers.Serializer):
|
|||
for i in Interface.objects.all() if i.ipv4
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_ipv6():
|
||||
return {i.ipv6:
|
||||
{
|
||||
""" 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()],
|
||||
|
|
|
@ -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.
|
||||
|
|
15
api/urls.py
15
api/urls.py
|
@ -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
|
||||
),
|
||||
]
|
||||
|
|
23
api/utils.py
23
api/utils.py
|
@ -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.
|
||||
|
@ -60,9 +61,9 @@ class JSONError(JSONResponse):
|
|||
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 = {
|
||||
|
@ -86,9 +87,9 @@ class JSONSuccess(JSONResponse):
|
|||
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 = {
|
||||
|
@ -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
|
||||
|
|
243
api/views.py
243
api/views.py
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -110,8 +146,8 @@ 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,7 +384,7 @@ def dns_srv(request):
|
|||
@login_required
|
||||
@permission_required('machines.serveur')
|
||||
@accept_method(['GET'])
|
||||
def dns_zones(request):
|
||||
def dns_zones(_request):
|
||||
"""The list of the zones managed
|
||||
|
||||
Returns:
|
||||
|
@ -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'):
|
||||
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:
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 .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
|
||||
|
@ -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 €.") % {
|
||||
|
|
151
cotisations/migrations/0029_auto_20180414_2056.py
Normal file
151
cotisations/migrations/0029_auto_20180414_2056.py
Normal 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'),
|
||||
),
|
||||
]
|
|
@ -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
|
||||
|
||||
|
@ -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 \
|
||||
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.")
|
||||
return False, _("You don't have the right to edit an invoice "
|
||||
"already controlled or invalidated.")
|
||||
else:
|
||||
return True, None
|
||||
|
||||
|
@ -171,28 +178,40 @@ 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):
|
||||
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.")
|
||||
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)
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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,7 +341,9 @@ 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']
|
||||
|
@ -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)
|
||||
"""
|
||||
|
@ -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.")
|
||||
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')
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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,10 +24,17 @@ 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
|
||||
|
||||
|
@ -46,23 +54,33 @@ class Payment():
|
|||
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
|
||||
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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),
|
||||
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
|
||||
|
|
|
@ -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,75 +32,93 @@ 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'
|
||||
),
|
||||
|
@ -107,27 +128,33 @@ urlpatterns = [
|
|||
name='history',
|
||||
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'
|
||||
),
|
||||
|
|
|
@ -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.")
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
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,14 +154,19 @@ 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,
|
||||
return (
|
||||
radiusd.RLM_MODULE_UPDATED,
|
||||
(),
|
||||
(
|
||||
(str("NT-Password"), str(password)),
|
||||
|
@ -170,15 +174,20 @@ def authorize(data):
|
|||
)
|
||||
|
||||
else:
|
||||
return (radiusd.RLM_MODULE_UPDATED,
|
||||
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,12 +196,15 @@ 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'):
|
||||
|
@ -203,18 +215,30 @@ def post_auth(data):
|
|||
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"),
|
||||
|
@ -226,22 +250,35 @@ def post_auth(data):
|
|||
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,7 +348,12 @@ 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()
|
||||
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)
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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__
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
148
logs/views.py
148
logs/views.py
|
@ -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,65 +216,98 @@ 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"], {
|
||||
[ # 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()],
|
||||
(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()],
|
||||
(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.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()],
|
||||
_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()],
|
||||
_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()],
|
||||
_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()],
|
||||
_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()],
|
||||
(_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]
|
||||
(_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[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})
|
||||
return render(
|
||||
request,
|
||||
'logs/stats_droits.html',
|
||||
{'stats_list': stats_list}
|
||||
)
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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):
|
||||
""" 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)
|
||||
|
||||
|
||||
|
|
|
@ -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,7 +137,7 @@ 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
|
||||
|
@ -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()
|
||||
|
|
21
machines/migrations/0078_auto_20180415_1252.py
Normal file
21
machines/migrations/0078_auto_20180415_1252.py
Normal 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)]),
|
||||
),
|
||||
]
|
|
@ -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
|
||||
|
@ -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,20 +167,27 @@ 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):
|
||||
|
@ -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
|
||||
|
@ -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,6 +653,7 @@ class Txt(RevMixin, AclMixin, models.Model):
|
|||
|
||||
|
||||
class Srv(RevMixin, AclMixin, models.Model):
|
||||
""" A SRV record """
|
||||
PRETTY_NAME = "Enregistrement Srv"
|
||||
|
||||
TCP = 'TCP'
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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,20 +944,26 @@ 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):
|
||||
|
@ -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,20 +1062,26 @@ 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):
|
||||
|
@ -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')
|
||||
|
||||
|
|
|
@ -22,6 +22,10 @@
|
|||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
# 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,13 +205,19 @@ 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):
|
||||
@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}
|
||||
|
||||
|
||||
|
@ -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,9 +384,21 @@ 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:
|
||||
{
|
||||
""" 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()],
|
||||
|
@ -326,9 +407,11 @@ class OuverturePortsSerializer(serializers.Serializer):
|
|||
for i in Interface.objects.all() if i.ipv4
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_ipv6():
|
||||
return {i.ipv6:
|
||||
{
|
||||
""" 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()],
|
||||
|
|
|
@ -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.
|
||||
|
|
106
machines/urls.py
106
machines/urls.py
|
@ -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'),
|
||||
]
|
||||
|
|
1058
machines/views.py
1058
machines/views.py
File diff suppressed because it is too large
Load diff
|
@ -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 *
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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,12 +78,6 @@ 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
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PreferencesConfig(AppConfig):
|
||||
name = 'preferences'
|
|
@ -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:
|
||||
|
|
|
@ -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,6 +153,7 @@ class OptionalMachine(AclMixin, PreferencesModel):
|
|||
|
||||
@cached_property
|
||||
def ipv6(self):
|
||||
""" Check if the IPv6 option is activated """
|
||||
return not self.get_cached_value('ipv6_mode') == 'DISABLED'
|
||||
|
||||
class Meta:
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -27,8 +27,8 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.conf.urls import url
|
||||
|
||||
from . import views
|
||||
import re2o
|
||||
from . import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
|
|
@ -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()
|
||||
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
* ...
|
||||
"""
|
||||
|
|
97
re2o/acl.py
97
re2o/acl.py
|
@ -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,30 +64,36 @@ 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',
|
||||
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',
|
||||
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
|
||||
|
@ -103,16 +105,20 @@ 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
|
||||
|
@ -127,19 +133,25 @@ 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',
|
||||
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',
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
return view(request, instance, *args, **kwargs)
|
||||
|
@ -151,17 +163,23 @@ 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',
|
||||
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)
|
||||
|
@ -177,19 +195,25 @@ 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',
|
||||
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',
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
return view(request, instance, *args, **kwargs)
|
||||
|
@ -201,12 +225,17 @@ 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',
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
return view(request, *args, **kwargs)
|
||||
|
@ -220,13 +249,18 @@ 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',
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
return wrapper
|
||||
|
@ -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',
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
return wrapper
|
||||
|
|
|
@ -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"'
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -95,13 +100,13 @@ class SSHAPasswordHasher(hashers.BasePasswordHasher):
|
|||
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):
|
||||
|
|
|
@ -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):
|
||||
""" 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))
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
||||
proj_path="/var/www/re2o"
|
||||
from getpass import getpass
|
||||
from reversion import revisions as reversion
|
||||
|
||||
from django.core.wsgi import 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
|
||||
|
||||
proj_path = dirname(dirname(__file__))
|
||||
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 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
|
||||
|
||||
|
||||
def get_user(pseudo):
|
||||
"""Cherche un utilisateur re2o à partir de son pseudo"""
|
||||
user = User.objects.filter(pseudo=pseudo)
|
||||
if len(user) == 0:
|
||||
raise CommandError("Utilisateur invalide")
|
||||
if len(user) > 1:
|
||||
raise CommandError("Plusieurs utilisateurs correspondant à ce pseudo. Ceci NE DEVRAIT PAS arriver")
|
||||
raise CommandError("Plusieurs utilisateurs correspondant à ce "
|
||||
"pseudo. Ceci NE DEVRAIT PAS arriver")
|
||||
return user[0]
|
||||
|
||||
|
||||
|
@ -74,8 +83,11 @@ def form_cli(Form,user,action,*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():
|
||||
|
@ -83,4 +95,5 @@ def form_cli(Form,user,action,*args,**kwargs):
|
|||
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)
|
||||
|
|
|
@ -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,12 +95,15 @@ 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': [
|
||||
# Use only absolute paths with '/' delimiters even on Windows
|
||||
os.path.join(BASE_DIR, 'templates').replace('\\', '/'),
|
||||
],
|
||||
'APP_DIRS': True,
|
||||
|
@ -115,61 +120,51 @@ 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,
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
|
@ -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,12 +77,15 @@ 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',
|
||||
|
@ -80,16 +94,16 @@ LDAP = {
|
|||
'user_gid': 500,
|
||||
}
|
||||
|
||||
|
||||
# A range of UID to use. Used in linux environement
|
||||
UID_RANGES = {
|
||||
'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],
|
||||
}
|
||||
|
||||
# Some Django apps you want to add in you local project
|
||||
OPTIONNAL_APPS = ()
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -250,7 +274,7 @@ def acl_app_filter(parser, token):
|
|||
% 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,7 +302,7 @@ 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:]
|
||||
|
@ -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')
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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(
|
||||
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()
|
||||
).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'],
|
||||
|
@ -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:
|
||||
|
|
|
@ -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,6 +65,7 @@ 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': {
|
||||
|
@ -114,6 +116,7 @@ HISTORY_BIND = {
|
|||
},
|
||||
}
|
||||
|
||||
|
||||
@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,7 +147,8 @@ 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',
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
can, msg = instance.can_view(request.user)
|
||||
|
@ -158,7 +162,8 @@ def history(request, application, object_name, object_id):
|
|||
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,7 +193,7 @@ 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,
|
||||
|
@ -202,3 +210,7 @@ def about_page(request):
|
|||
}
|
||||
)
|
||||
|
||||
|
||||
def handler500(request):
|
||||
"""The handler view for a 500 error"""
|
||||
return render(request, 'errors/500.html')
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
56
templates/errors/500.html
Normal file
56
templates/errors/500.html
Normal file
File diff suppressed because one or more lines are too long
|
@ -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 *
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -32,15 +32,17 @@ 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 re2o.mixins import FormRevMixin
|
||||
|
||||
from .models import (
|
||||
Port,
|
||||
Switch,
|
||||
|
@ -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')
|
||||
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(
|
||||
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')
|
||||
queryset=(Interface.objects
|
||||
.select_related('ipv4__ip_type__extension')
|
||||
.select_related('domain__extension'))
|
||||
))
|
||||
)
|
||||
|
||||
|
||||
class StackForm(FormRevMixin, ModelForm):
|
||||
|
@ -170,7 +188,10 @@ 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
|
||||
|
@ -178,7 +199,11 @@ class EditModelSwitchForm(FormRevMixin, ModelForm):
|
|||
|
||||
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,12 +222,19 @@ 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
|
||||
|
|
|
@ -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
|
||||
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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\
|
||||
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')
|
||||
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')\
|
||||
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')
|
||||
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', {
|
||||
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\
|
||||
ap_list = (AccessPoint.objects
|
||||
.prefetch_related(Prefetch(
|
||||
'interface_set',
|
||||
queryset=Interface.objects.select_related('ipv4__ip_type__extension').select_related('domain__extension')
|
||||
))
|
||||
queryset=(Interface.objects
|
||||
.select_related('ipv4__ip_type__extension')
|
||||
.select_related('domain__extension'))
|
||||
)))
|
||||
ap_list = SortTable.sort(
|
||||
ap_list,
|
||||
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', {
|
||||
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', {
|
||||
return render(
|
||||
request,
|
||||
'topologie/index_model_switch.html',
|
||||
{
|
||||
'model_switch_list': model_switch_list,
|
||||
'constructor_switch_list': constructor_switch_list,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -265,12 +291,15 @@ def new_port(request, switchid):
|
|||
'topologie:index-port',
|
||||
kwargs={'switchid': switchid}
|
||||
))
|
||||
return form({'id_switch': switchid,'topoform': port, 'action_name' : 'Ajouter'}, 'topologie/topo.html', request)
|
||||
return form(
|
||||
{'id_switch': switchid, 'topoform': port, 'action_name': 'Ajouter'},
|
||||
'topologie/topo.html',
|
||||
request)
|
||||
|
||||
|
||||
@login_required
|
||||
@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"""
|
||||
|
||||
|
@ -283,20 +312,31 @@ def edit_port(request, port_object, portid):
|
|||
'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)}
|
||||
|
@ -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({
|
||||
return form(
|
||||
{
|
||||
'topoform': interface,
|
||||
'machineform': switch,
|
||||
'domainform': domain,
|
||||
'i_mbf_param': i_mbf_param,
|
||||
'device': 'switch',
|
||||
}, 'topologie/topo_more.html', request)
|
||||
},
|
||||
'topologie/topo_more.html',
|
||||
request
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -435,7 +493,11 @@ def create_ports(request, switchid):
|
|||
'topologie:index-port',
|
||||
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({
|
||||
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)
|
||||
},
|
||||
'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({
|
||||
return form(
|
||||
{
|
||||
'topoform': interface,
|
||||
'machineform': ap,
|
||||
'domainform': domain,
|
||||
'i_mbf_param': i_mbf_param,
|
||||
'device': 'wifi ap',
|
||||
}, 'topologie/topo_more.html', request)
|
||||
},
|
||||
'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,28 +622,35 @@ 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({
|
||||
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)
|
||||
},
|
||||
'topologie/topo_more.html',
|
||||
request
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -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,
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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 = ()
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
@ -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."
|
||||
))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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'])
|
||||
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
]
|
||||
|
|
21
users/migrations/0071_auto_20180415_1252.py
Normal file
21
users/migrations/0071_auto_20180415_1252.py
Normal 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')]),
|
||||
),
|
||||
]
|
333
users/models.py
333
users/models.py
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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\
|
||||
|
@ -668,8 +689,8 @@ 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.
|
||||
|
@ -678,9 +699,9 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
|
|||
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,54 +712,107 @@ 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.
|
||||
|
@ -747,35 +821,46 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, PermissionsMix
|
|||
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)
|
||||
|
@ -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',
|
||||
|
@ -846,7 +941,8 @@ class Club(User):
|
|||
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.
|
||||
|
@ -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']
|
||||
# TODO : remove if unnecessary
|
||||
# if is_created:
|
||||
# user.notif_inscription()
|
||||
user.ldap_sync(base=True, access_refresh=True, mac_refresh=False, group_refresh=True)
|
||||
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,
|
||||
|
|
|
@ -22,17 +22,28 @@
|
|||
|
||||
# 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',)
|
||||
|
|
|
@ -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>
|
||||
{% 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>
|
||||
|
|
|
@ -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.
|
||||
|
|
100
users/urls.py
100
users/urls.py
|
@ -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'),
|
||||
]
|
||||
|
|
295
users/views.py
295
users/views.py
|
@ -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):
|
||||
|
@ -123,7 +120,17 @@ def new_user(request):
|
|||
'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)
|
||||
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
|
||||
|
@ -142,15 +149,22 @@ def new_club(request):
|
|||
'users:profil',
|
||||
kwargs={'userid': str(club.id)}
|
||||
))
|
||||
return form({'userform': club, 'showCGU':False, 'action_name':'Créer un club'}, 'users/user.html', request)
|
||||
return form(
|
||||
{'userform': club, 'showCGU': False, 'action_name': 'Créer un club'},
|
||||
'users/user.html',
|
||||
request
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@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()
|
||||
|
@ -159,7 +173,15 @@ def edit_club_admin_members(request, club_instance, clubid):
|
|||
'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)
|
||||
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)
|
||||
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)
|
||||
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)}
|
||||
))
|
||||
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
|
||||
|
@ -240,14 +275,19 @@ def password(request, user, userid):
|
|||
messages.success(request, "Le mot de passe a changé")
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid':str(user.id)}
|
||||
kwargs={'userid': str(userid)}
|
||||
))
|
||||
return form({'userform': u_form, 'action_name':'Changer le mot de passe'}, 'users/user.html', request)
|
||||
return form(
|
||||
{'userform': u_form, 'action_name': 'Changer le mot de passe'},
|
||||
'users/user.html',
|
||||
request
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@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,7 +363,7 @@ 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',
|
||||
|
@ -323,11 +374,16 @@ def add_ban(request, user, userid):
|
|||
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
|
||||
|
@ -365,12 +425,16 @@ def add_whitelist(request, user, userid):
|
|||
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
|
||||
|
@ -694,7 +798,7 @@ def mon_profil(request):
|
|||
|
||||
@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(
|
||||
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue