from django.db import models from django.contrib.gis.db import models as geo_models from django.db.models import Q from django.core.validators import MaxValueValidator from django.utils import timezone from django.contrib.auth.models import Group from django.contrib.contenttypes.fields import GenericRelation from django.core.exceptions import ValidationError from django.urls import reverse from django.utils import timezone from django.core.exceptions import PermissionDenied from django.core.validators import RegexValidator from djadhere.utils import get_active_filter, is_overlapping from adhesions.models import Adhesion from banking.models import Payment class IPPrefix(models.Model): prefix = models.CharField(max_length=128) def __str__(self): return self.prefix class IPResourceManager(models.Manager): def get_queryset(self): qs = super().get_queryset() # On rajoute une super annotation « in_use » pour savoir si l’IP est dispo ou non :-) query = Q(resource=models.OuterRef('pk')) & get_active_filter() qs = qs.annotate( in_use_by_service=models.Exists( ServiceAllocation.objects.filter(query) ), in_use_by_antenna=models.Exists( AntennaAllocation.objects.filter(query) ), in_use=models.ExpressionWrapper( models.F('in_use_by_service') + models.F('in_use_by_antenna'), output_field=models.BooleanField() ) ) return qs class IPResource(models.Model): CATEGORIES = ( (0, 'IP Public'), (1, 'IP Antenne'), ) ip = models.GenericIPAddressField(verbose_name='IP', primary_key=True) prefixes = models.ManyToManyField(IPPrefix, verbose_name='préfixes') reserved = models.BooleanField(default=False, verbose_name='réservée') category = models.IntegerField(choices=CATEGORIES, verbose_name='catégorie') notes = models.TextField(blank=True, default='') objects = IPResourceManager() @property def allocations(self): if self.category == 0: return self.service_allocations if self.category == 1: return self.antenna_allocations class Meta: ordering = ['ip'] verbose_name = 'IP' verbose_name_plural = 'IP' def __str__(self): return str(self.ip) class ServiceType(models.Model): name = models.CharField(max_length=64, verbose_name='Nom', unique=True) class Meta: ordering = ['name'] verbose_name = 'type de service' verbose_name_plural = 'types de service' def __str__(self): return self.name class Service(models.Model): adhesion = models.ForeignKey(Adhesion, verbose_name='Adhérent', related_name='services') service_type = models.ForeignKey(ServiceType, related_name='services', verbose_name='Type de service') label = models.CharField(blank=True, default='', max_length=128) notes = models.TextField(blank=True, default='') active = models.BooleanField(default=True, verbose_name='actif') created = models.DateTimeField(auto_now_add=True) contributions = GenericRelation(Payment, content_type_field='reason_type', object_id_field='reason_id', related_query_name='service') @property def contribution(self): try: return self.contributions.exclude(period=0).get(get_active_filter()) except Payment.DoesNotExist: return None # MultipleObjectsReturned non catché volontairement, cf remarque adhesions.Adhesion.contribution def clean(self): super().clean() # Vérification de l’unicité par type de service du label if self.label != '' and Service.objects.exclude(pk=self.pk).filter(service_type=self.service_type, label=self.label): raise ValidationError("Un service du même type existe déjà avec ce label.") def __str__(self): s = str(self.service_type) if self.label: s += ' ' + self.label return s class Antenna(models.Model): position = geo_models.PointField(null=True) mac = models.CharField( blank=True, default='', max_length=17, validators=[ RegexValidator(r'^([0-9a-fA-F]{2}([:-]?|$)){6}$'), ]) notes = models.TextField(blank=True) class Meta: verbose_name = 'antenne' def __str__(self): return 'Antenne %d' % self.pk class Route(models.Model): name = models.CharField(max_length=64) def __str__(self): return self.name class Allocation(models.Model): start = models.DateTimeField(verbose_name='Début de la période d’allocation', default=timezone.now) end = models.DateTimeField(null=True, blank=True, verbose_name='Fin de la période d’allocation') notes = models.TextField(blank=True, default='') @property def active(self): now = timezone.now() return self.start <= now and (self.end is None or self.end >= now) def clean(self): super().clean() # Vérification de la cohérence des champs start et end if self.end and self.start > self.end: raise ValidationError("La date de début de l’allocation doit être antérieur " "à la date de fin de l’allocation.") if self.resource_id: if self.resource.reserved: raise ValidationError("L’IP sélectionnée est réservée") # Vérification de l’abscence de chevauchement de la période d’allocation allocations = type(self).objects.filter(resource__pk=self.resource.pk) if is_overlapping(self, allocations): raise ValidationError("La période d’allocation de cette ressource chevauche " "avec une période d’allocation précédente.") class Meta: abstract = True ordering = ['-start'] def __str__(self): return str(self.resource) class ServiceAllocation(Allocation): resource = models.ForeignKey(IPResource, verbose_name='Ressource', related_name='service_allocations', related_query_name='service_allocation', limit_choices_to={'category': 0}) service = models.ForeignKey(Service, related_name='allocations', related_query_name='allocation') route = models.ForeignKey(Route, verbose_name='Route', related_name='services', related_query_name='allocation') class Meta: verbose_name = 'allocation' verbose_name_plural = 'allocations' class AntennaAllocation(Allocation): resource = models.ForeignKey(IPResource, verbose_name='Ressource', related_name='antenna_allocations', related_query_name='antenna_allocation', limit_choices_to={'category': 1}) antenna = models.ForeignKey(Antenna, related_name='allocations', related_query_name='allocation') class Meta: verbose_name = 'allocation' verbose_name_plural = 'allocations'