from django.db import 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 djadhere.utils import get_active_filter, is_overlapping from adhesions.models import Adhesion from banking.models import Payment class IPResource(models.Model): ip = models.GenericIPAddressField(verbose_name='IP') mask = models.PositiveIntegerField(validators=[MaxValueValidator(128)], default=0, verbose_name='Masque') @property def in_use(self): return self.allocation is not None @property def allocation(self): try: return self.allocations.get(get_active_filter()) except ResourceAllocation.DoesNotExist: return None class Meta: verbose_name = 'ressource IP' verbose_name_plural = 'ressources IP' def get_absolute_url(self): return reverse('ip-detail', kwargs={'pk': self.pk}) def __str__(self): r = str(self.ip) if self.mask: r += '/%d' % self.mask return r class ServiceType(models.Model): name = models.CharField(max_length=64, verbose_name='Nom', unique=True) group = models.ForeignKey(Group, null=True, blank=True, verbose_name='Groupe de gestion', related_name='service_types') class Meta: verbose_name = 'type de service' verbose_name_plural = 'types de service' def get_absolute_url(self): return reverse('servicetype-detail', kwargs={'pk': self.pk}) 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 @property def active_allocations(self): return self.allocations.filter(get_active_filter()) @property def inactive_allocations(self): return self.allocations.exclude(get_active_filter()) 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 get_absolute_url(self): return reverse('service-detail', kwargs={'pk': self.pk}) def __str__(self): s = str(self.service_type) if self.label: s += ' ' + self.label return s class ResourceAllocation(models.Model): resource = models.ForeignKey(IPResource, verbose_name='Ressource', related_name='allocations', related_query_name='allocation') service = models.ForeignKey(Service, related_name='allocations', related_query_name='allocation') 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') @property def active(self): return ResourceAllocation.objects.filter(pk=self.pk).filter(get_active_filter()).exists() 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.") # Vérification de l’abscence de chevauchement de la période d’allocation if self.resource_id: allocations = ResourceAllocation.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.") # Penser à appeler la méthode save ! def deallocate(self): if not self.active: raise PermissionDenied self.end = timezone.now() class Meta: verbose_name = 'allocation' verbose_name_plural = 'allocations' ordering = ['-start'] def get_absolute_url(self): return reverse('ip-detail', kwargs={'pk': self.resource.pk}) def __str__(self): return str(self.resource)