models.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import logging
  4. from django.db import models
  5. from polymorphic import PolymorphicModel
  6. from coin.offers.models import OfferSubscription
  7. from django.db.models.signals import post_save, post_delete
  8. from django.core.exceptions import ObjectDoesNotExist
  9. from django.dispatch import receiver
  10. from django.conf import settings
  11. from coin.resources.models import IPSubnet
  12. """
  13. Implementation note : Configuration is a PolymorphicModel.
  14. The childs of Configuration are the differents models to store
  15. technical informations of a subscription.
  16. To add a new configuration backend, you have to create a new app with a model
  17. which inherit from Configuration.
  18. Your model can implement Meta verbose_name to have human readable name and a
  19. url_namespace variable to specify the url namespace used by this model.
  20. """
  21. class Configuration(PolymorphicModel):
  22. offersubscription = models.OneToOneField(OfferSubscription,
  23. related_name='configuration',
  24. verbose_name='abonnement')
  25. comment = models.CharField(blank=True, max_length=512,
  26. verbose_name="commentaire")
  27. @staticmethod
  28. def get_configurations_choices_list():
  29. """
  30. Génère automatiquement la liste de choix possibles de configurations
  31. en fonction des classes enfants de Configuration
  32. """
  33. return tuple((x().__class__.__name__,x()._meta.verbose_name)
  34. for x in Configuration.__subclasses__())
  35. def model_name(self):
  36. return self.__class__.__name__
  37. model_name.short_description = 'Nom du modèle'
  38. def configuration_type_name(self):
  39. return self._meta.verbose_name
  40. configuration_type_name.short_description = 'Type'
  41. def get_absolute_url(self):
  42. """
  43. Renvoi l'URL d'accès à la page "details" de l'objet
  44. Une url doit être nommée "details"
  45. """
  46. from django.core.urlresolvers import reverse
  47. return reverse('%s:details' % self.get_url_namespace(),
  48. args=[str(self.id)])
  49. def get_url_namespace(self):
  50. """
  51. Renvoi le namespace utilisé par la configuration. Utilise en priorité
  52. celui définit dans la classe enfant dans url_namespace sinon
  53. par défaut utilise le nom de la classe en minuscule
  54. """
  55. if hasattr(self, 'url_namespace') and self.url_namespace:
  56. return self.url_namespace
  57. else:
  58. return self.model_name().lower()
  59. class Meta:
  60. verbose_name = 'configuration'
  61. @receiver(post_save, sender=OfferSubscription)
  62. def offer_subscription_event(sender, **kwargs):
  63. os = kwargs['instance']
  64. if not hasattr(os, 'configuration'):
  65. config_cls = None
  66. for subconfig_cls in Configuration.__subclasses__():
  67. if subconfig_cls().__class__.__name__ == os.offer.configuration_type:
  68. config_cls = subconfig_cls
  69. break
  70. if config_cls is not None:
  71. config = config_cls.objects.create(offersubscription=os)
  72. for offer_ip_pool in os.offer.offerippool_set.order_by('-to_assign'):
  73. IPSubnet.objects.create(
  74. configuration=config,
  75. ip_pool=offer_ip_pool.ip_pool)
  76. config.save()
  77. @receiver(post_save, sender=IPSubnet)
  78. def subnet_event_save(sender, **kwargs):
  79. kwargs["signal_type"] = "save"
  80. subnet_event(sender, **kwargs)
  81. @receiver(post_delete, sender=IPSubnet)
  82. def subnet_event_delete(sender, **kwargs):
  83. kwargs["signal_type"] = "delete"
  84. subnet_event(sender, **kwargs)
  85. subnet_log = logging.getLogger("coin.subnets")
  86. def subnet_event(sender, **kwargs):
  87. """Fires when a subnet is created, modified or deleted. We tell the
  88. configuration backend to do whatever it needs to do with it.
  89. Note that we could provide a more advanced API to configurations
  90. (subnet created, subnet modified, subnet deleted), but this is quite
  91. complicated to do. It's much simpler to simply tell the configuration
  92. model that something has changed in IP subnets. The configuration
  93. model can then access the list of its associated subnets (via the
  94. "ip_subnet" attribute) to decide for itself what it wants to do.
  95. We should use a pre_save/pre_delete signal, so that if anything goes
  96. wrong in the backend (exception raised), nothing is actually saved in
  97. the database: this provides consistency between the database and the
  98. backend. But if we do this, there is a major issue: the configuration
  99. backend will not see the new state of subnets by querying the
  100. database, since the changes have not been saved into the database yet.
  101. That's why we use a post_save/post_delete signal instead. In theory,
  102. this is a bad idea, because if the backend fails to do whatever it
  103. needs to do, the subnet will be saved into Django's database anyway,
  104. causing a desynchronisation with the backend. But surprisingly, even
  105. if not a documented feature of Django's signals, all is well: if we
  106. raise an exception here, the IPSubnet object will not be saved in the
  107. database. It looks like the database rollbacks if an exception is
  108. raised, which is great (even if undocumented).
  109. """
  110. subnet = kwargs['instance']
  111. try:
  112. config = subnet.configuration
  113. if hasattr(config, 'subnet_event'):
  114. config.subnet_event()
  115. offer = config.offersubscription.offer.name
  116. ref = config.offersubscription.get_subscription_reference()
  117. member = config.offersubscription.member
  118. ip = subnet.inet
  119. if kwargs['signal_type'] == "save":
  120. msg = "[Allocating IP] " + settings.IP_ALLOCATION_MESSAGE
  121. elif kwargs['signal_type'] == "delete":
  122. msg = "[Deallocating IP] " + settings.IP_ALLOCATION_MESSAGE
  123. else:
  124. # Does not happens
  125. msg = ""
  126. subnet_log.info(msg.format(ip=ip, member=member, offer=offer, ref=ref))
  127. except ObjectDoesNotExist:
  128. pass