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 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): if self.allocations.filter(get_active_filter()).exists(): return True else: return False class Meta: verbose_name = 'ressource IP' verbose_name_plural = 'ressources IP' 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 __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.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') end = models.DateTimeField(null=True, blank=True, verbose_name='Fin de la période d’allocation') 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.") class Meta: verbose_name = 'allocation' verbose_name_plural = 'allocations' def __str__(self): return str(self.resource)