models.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. from django.db import models
  2. from django.contrib.gis.db import models as geo_models
  3. from django.db.models import Q
  4. from django.core.validators import MaxValueValidator
  5. from django.utils import timezone
  6. from django.contrib.auth.models import Group
  7. from django.contrib.contenttypes.fields import GenericRelation
  8. from django.core.exceptions import ValidationError
  9. from django.urls import reverse
  10. from django.utils import timezone
  11. from django.core.exceptions import PermissionDenied
  12. from django.core.validators import RegexValidator
  13. from djadhere.utils import get_active_filter, is_overlapping
  14. from adhesions.models import Adhesion
  15. from banking.models import Payment
  16. class IPPrefix(models.Model):
  17. prefix = models.CharField(max_length=128)
  18. class Meta:
  19. ordering = ['prefix']
  20. def __str__(self):
  21. return self.prefix
  22. class IPResourceManager(models.Manager):
  23. def get_queryset(self):
  24. qs = super().get_queryset()
  25. # On rajoute une super annotation « in_use » pour savoir si l’IP est dispo ou non :-)
  26. query = Q(resource=models.OuterRef('pk')) & get_active_filter()
  27. qs = qs.annotate(
  28. in_use_by_service=models.Exists(
  29. ServiceAllocation.objects.filter(query)
  30. ),
  31. in_use_by_antenna=models.Exists(
  32. AntennaAllocation.objects.filter(query)
  33. )
  34. )
  35. qs = qs.annotate(
  36. in_use=models.Case(
  37. models.When(Q(in_use_by_service=True) | Q(in_use_by_antenna=True), then=True),
  38. default=False,
  39. output_field=models.BooleanField()
  40. )
  41. )
  42. return qs
  43. class ActiveAllocationManager(models.Manager):
  44. def get_queryset(self):
  45. qs = super().get_queryset()
  46. qs = qs.annotate(
  47. active=models.Case(
  48. models.When(get_active_filter(), then=True),
  49. default=False,
  50. output_field=models.BooleanField()
  51. )
  52. )
  53. return qs
  54. class IPResource(models.Model):
  55. CATEGORIES = (
  56. (0, 'IP Public'),
  57. (1, 'IP Antenne'),
  58. )
  59. ip = models.GenericIPAddressField(verbose_name='IP', primary_key=True)
  60. prefixes = models.ManyToManyField(IPPrefix, verbose_name='préfixes')
  61. reserved = models.BooleanField(default=False, verbose_name='réservée')
  62. category = models.IntegerField(choices=CATEGORIES, verbose_name='catégorie')
  63. notes = models.TextField(blank=True, default='')
  64. objects = IPResourceManager()
  65. @property
  66. def allocations(self):
  67. if self.category == 0:
  68. return self.service_allocations
  69. if self.category == 1:
  70. return self.antenna_allocations
  71. class Meta:
  72. ordering = ['ip']
  73. verbose_name = 'IP'
  74. verbose_name_plural = 'IP'
  75. def __str__(self):
  76. return str(self.ip)
  77. class ServiceType(models.Model):
  78. name = models.CharField(max_length=64, verbose_name='Nom', unique=True)
  79. class Meta:
  80. ordering = ['name']
  81. verbose_name = 'type de service'
  82. verbose_name_plural = 'types de service'
  83. def __str__(self):
  84. return self.name
  85. class Service(models.Model):
  86. adhesion = models.ForeignKey(Adhesion, verbose_name='Adhérent', related_name='services')
  87. service_type = models.ForeignKey(ServiceType, related_name='services',
  88. verbose_name='Type de service')
  89. label = models.CharField(blank=True, default='', max_length=128)
  90. notes = models.TextField(blank=True, default='')
  91. active = models.BooleanField(default=True, verbose_name='actif')
  92. created = models.DateTimeField(auto_now_add=True)
  93. contributions = GenericRelation(Payment,
  94. content_type_field='reason_type',
  95. object_id_field='reason_id',
  96. related_query_name='service')
  97. @property
  98. def contribution(self):
  99. try:
  100. return self.contributions.exclude(period=0).get(get_active_filter())
  101. except Payment.DoesNotExist:
  102. return None
  103. # MultipleObjectsReturned non catché volontairement, cf remarque adhesions.Adhesion.contribution
  104. def clean(self):
  105. super().clean()
  106. # Vérification de l’unicité par type de service du label
  107. if self.label != '' and Service.objects.exclude(pk=self.pk).filter(service_type=self.service_type, label=self.label):
  108. raise ValidationError("Un service du même type existe déjà avec ce label.")
  109. def __str__(self):
  110. s = str(self.service_type)
  111. if self.label:
  112. s += ' ' + self.label
  113. return s
  114. class Antenna(models.Model):
  115. label = models.CharField(max_length=128, blank=True, default='')
  116. mac = models.CharField(
  117. blank=True,
  118. default='',
  119. max_length=17,
  120. validators=[
  121. RegexValidator(r'^([0-9a-fA-F]{2}([:-]?|$)){6}$'),
  122. ],
  123. verbose_name='Adresse MAC')
  124. notes = models.TextField(blank=True)
  125. position = geo_models.PointField(null=True, blank=True)
  126. class Meta:
  127. verbose_name = 'antenne'
  128. def __str__(self):
  129. name = 'Antenne %d' % self.pk
  130. if self.label:
  131. name += ' (%s)' % self.label
  132. return name
  133. class Route(models.Model):
  134. name = models.CharField(max_length=64)
  135. def __str__(self):
  136. return self.name
  137. class Allocation(models.Model):
  138. start = models.DateTimeField(verbose_name='Début de la période d’allocation', default=timezone.now)
  139. end = models.DateTimeField(null=True, blank=True, verbose_name='Fin de la période d’allocation')
  140. notes = models.TextField(blank=True, default='')
  141. objects = models.Manager()
  142. actives = ActiveAllocationManager()
  143. @property
  144. def active(self):
  145. now = timezone.now()
  146. return self.start <= now and (self.end is None or self.end >= now)
  147. def clean(self):
  148. super().clean()
  149. # Vérification de la cohérence des champs start et end
  150. if self.end and self.start > self.end:
  151. raise ValidationError("La date de début de l’allocation doit être antérieur "
  152. "à la date de fin de l’allocation.")
  153. if self.resource_id:
  154. if self.resource.reserved:
  155. raise ValidationError("L’IP sélectionnée est réservée")
  156. # Vérification de l’abscence de chevauchement de la période d’allocation
  157. allocations = type(self).objects.filter(resource__pk=self.resource.pk)
  158. if is_overlapping(self, allocations):
  159. raise ValidationError("La période d’allocation de cette ressource chevauche "
  160. "avec une période d’allocation précédente.")
  161. class Meta:
  162. abstract = True
  163. ordering = ['-start']
  164. def __str__(self):
  165. return str(self.resource)
  166. class ServiceAllocation(Allocation):
  167. resource = models.ForeignKey(IPResource, verbose_name='Ressource', related_name='service_allocations',
  168. related_query_name='service_allocation', limit_choices_to={'category': 0})
  169. service = models.ForeignKey(Service, related_name='allocations', related_query_name='allocation')
  170. route = models.ForeignKey(Route, verbose_name='Route', related_name='services', related_query_name='allocation')
  171. class Meta:
  172. verbose_name = 'allocation'
  173. verbose_name_plural = 'allocations'
  174. class AntennaAllocation(Allocation):
  175. resource = models.ForeignKey(IPResource, verbose_name='Ressource', related_name='antenna_allocations',
  176. related_query_name='antenna_allocation', limit_choices_to={'category': 1})
  177. antenna = models.ForeignKey(Antenna, related_name='allocations', related_query_name='allocation')
  178. class Meta:
  179. verbose_name = 'allocation'
  180. verbose_name_plural = 'allocations'