123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- from django.core.management.base import BaseCommand, CommandParser, CommandError
- from django.db.models import DecimalField, F, Sum, Func, Q
- from django.core.validators import validate_ipv4_address, validate_ipv6_address
- from django.core.exceptions import ValidationError
- from django.utils import timezone
- import argparse
- from decimal import Decimal
- from djadhere.utils import get_active_filter
- from adhesions.models import Adhesion
- from services.models import Service, ServiceType, IPResource, ResourceAllocation
- class IPAction(argparse.Action):
- def __call__(self, parser, namespace, values, option_string=None):
- args = values.split('/')
- if len(args) == 1:
- ip, = args
- mask = 0
- elif len(args) == 2:
- ip, mask = args
- else:
- raise argparse.ArgumentError(self, '"%s" n’est pas une IP valide' % values)
- try:
- validate_ipv4_address(ip)
- protocol = 'ipv4'
- except ValidationError:
- try:
- validate_ipv6_address(ip)
- protocol = 'ipv6'
- except ValidationError:
- raise argparse.ArgumentError(self, '"%s" n’est pas une IP valide' % values)
- try:
- mask = int(mask)
- except ValueError:
- raise argparse.ArgumentError(self, 'masque invalide')
- if (mask < 0) or \
- (protocol == 'ipv4' and mask > 32) or \
- (protocol == 'ipv6' and mask > 128):
- raise argparse.ArgumentError(self, 'masque invalide')
- ip, created = IPResource.objects.get_or_create(ip=ip, mask=mask)
- ip_set = getattr(namespace, 'ip')
- ip_set.append(ip)
- class Command(BaseCommand):
- help = 'Gestion des services'
- def add_arguments(self, parser):
- cmd = self
- class SubParser(CommandParser):
- def __init__(self, **kwargs):
- super().__init__(cmd, **kwargs)
- subparsers = parser.add_subparsers(dest='command', parser_class=SubParser)
- subparsers.required = True
- parser_stats = subparsers.add_parser('stats', help='Afficher les statistiques sur les services')
- parser_list = subparsers.add_parser('list', help='Lister les services')
- parser_list.add_argument('--type',
- choices=ServiceType.objects.all().values_list('name', flat=True),
- help='Afficher uniquement les services d’un certain type')
- group = parser_list.add_mutually_exclusive_group()
- group.add_argument('--active', action='store_true',
- help='Afficher uniquement les services actifs')
- group.add_argument('--inactive', action='store_true',
- help='Afficher uniquement les services inactifs')
- parser_show = subparsers.add_parser('show', help='Afficher les informations concernant un service')
- parser_show.add_argument('id', type=int)
- parser_add = subparsers.add_parser('add', help='Ajouter un nouveau service')
- parser_add.add_argument('--adherent', type=int, help='Numéro d’adhérent', required=True)
- parser_add.add_argument('--type', required=True,
- choices=ServiceType.objects.all().values_list('name', flat=True),
- help='Afficher uniquement les services d’un certain type')
- parser_add.add_argument('--ip', action=IPAction, default=[], help='Assigner une IP au service')
- parser_add.add_argument('--label', default='')
- parser_delete = subparsers.add_parser('delete', help='Supprimer un service existant')
- parser_delete.add_argument('id', type=int)
- def handle(self, *args, **options):
- cmd = options.pop('command')
- getattr(self, 'handle_{cmd}'.format(cmd=cmd))(*args, **options)
- def handle_stats(self, *args, **options):
- # TODO: move this code in utils.py or some place like that
- as_float_template = '%(function)s(%(expressions)s AS FLOAT)'
- total_income = Decimal(0.0)
- lines = []
- for service_type in ServiceType.objects.all():
- services = service_type.services.filter(active=True)
- ntotal = services.count()
- #services = services.exclude(adherent=None) # TODO: remove infra services here
- nadh = services.count()
- ninf = ntotal - nadh
- services = services.exclude(
- Q(contributions__isnull=True)
- | Q(contributions__amount=0)
- | Q(contributions__period=0)
- )
- npay = services.count()
- ngra = nadh - npay
- amount = Func(F('contributions__amount'), function='CAST', template=as_float_template)
- period = F('contributions__period')
- output_field = DecimalField(max_digits=9, decimal_places=2)
- income = services.aggregate(income=Sum(amount / period,
- output_field=output_field))['income'] or Decimal(0.0)
- total_income += income
- lines += [(str(service_type), npay, ninf, ngra, income,)]
- self.stdout.write("%-16s%12s%12s%12s%12s%12s" % ('Service', 'npay', 'ninf', 'ngra', 'eur', 'pourcent'))
- for service_type, npay, ninf, ngra, income in lines:
- if total_income:
- percent = income / total_income * 100
- else:
- percent = 0
- self.stdout.write("%-16s%12d%12d%12d%12.2f%12.1f" % (service_type, npay, ninf, ngra, income, percent))
- def handle_list(self, *args, **options):
- services = Service.objects.all()
- if options['type']:
- st = ServiceType.objects.get(name=options['type'])
- services = services.filter(service_type=st)
- if options['active']:
- services = services.filter(active=True)
- if options['inactive']:
- services = services.filter(active=False)
- fmt = '{:<8}{:<20}{:<20}{:<20}{:<10}{:<40}'
- self.stdout.write(fmt.format('ID', 'Type', 'Label', 'Adhérent', 'Actif', 'IPs'))
- for service in services:
- adh = service.adhesion.get_adherent_name()
- ips = ', '.join(map(lambda a: str(a.resource), service.active_allocations.all())) or '-'
- active = '✔' if service.active else '✘'
- self.stdout.write(fmt.format(service.pk, str(service.service_type), service.label, adh, active, ips))
- def handle_show(self, *args, **options):
- try:
- service = Service.objects.get(pk=options['id'])
- except Service.DoesNotExist:
- raise CommandError('Le service n°%d n’existe pas' % options['id'])
- self.stdout.write('Service n°%d' % service.pk)
- self.stdout.write('\tType de service : %s' % service.service_type)
- self.stdout.write('\tAdhérent : ADT%d - %s' % (service.adherent.pk, service.adherent.get_name()))
- self.stdout.write('\tActif : %s' % ('oui' if service.active else 'non'))
- if service.active_allocations.exists():
- self.stdout.write('\tIP :')
- for a in service.active_allocations.all():
- self.stdout.write('\t\t%s' % a.resource)
- else:
- self.stdout.write('\tIP : aucune')
- def handle_add(self, *args, **options):
- for ip in options['ip']:
- try:
- allocation = ResourceAllocation.objects.filter(resource=ip).get(get_active_filter())
- raise CommandError("La ressource %s est déjà utilisé : %s (%s)" % (ip, allocation.service, allocation.service.adherent))
- except ResourceAllocation.DoesNotExist:
- pass
- st = ServiceType.objects.get(name=options['type'])
- try:
- adherent = Adhesion.objects.get(pk=options['adherent'])
- except Adhesion.DoesNotExist:
- raise CommandError("L’adhérent %d n’existe pas." % options['adherent'])
- if Service.objects.filter(service_type=st, label=options['label']).exists():
- raise CommandError("Un service du même type et portant le même label existe déjà.")
- service = Service.objects.create(service_type=st, adherent=adherent, label=options['label'])
- for ip in options['ip']:
- ResourceAllocation.objects.create(service=service, resource=ip, start=timezone.now())
- self.stdout.write(self.style.SUCCESS('Service n°%d créé avec succès' % service.pk))
- def handle_delete(self, *args, **options):
- try:
- service = Service.objects.get(pk=options['id'])
- except Service.DoesNotExist:
- raise CommandError('Le service n°%d n’existe pas' % options['id'])
- self.stdout.write('%s – %s' % (service, service.adherent))
- value = input('Êtes vous sûr de vouloir supprimer ce service ? [y/N] ')
- if value.lower() == 'y':
- service.delete()
- self.stdout.write(self.style.SUCCESS('Service supprimé'))
- else:
- self.stdout.write('Service non supprimé')
|