123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- # -*- coding: utf-8 -*-
- from __future__ import unicode_literals
- import logging
- from django.db import models
- from polymorphic import PolymorphicModel
- from coin.offers.models import OfferSubscription
- from django.db.models.signals import post_save, post_delete
- from django.core.exceptions import ObjectDoesNotExist
- from django.dispatch import receiver
- from django.conf import settings
- from coin.resources.models import IPSubnet
- """
- Implementation note : Configuration is a PolymorphicModel.
- The childs of Configuration are the differents models to store
- technical informations of a subscription.
- To add a new configuration backend, you have to create a new app with a model
- which inherit from Configuration.
- Your model can implement Meta verbose_name to have human readable name and a
- url_namespace variable to specify the url namespace used by this model.
- """
- class Configuration(PolymorphicModel):
- offersubscription = models.OneToOneField(OfferSubscription,
- related_name='configuration',
- verbose_name='abonnement')
- comment = models.CharField(blank=True, max_length=512,
- verbose_name="commentaire")
- @staticmethod
- def get_configurations_choices_list():
- """
- Génère automatiquement la liste de choix possibles de configurations
- en fonction des classes enfants de Configuration
- """
- return tuple((x().__class__.__name__,x()._meta.verbose_name)
- for x in Configuration.__subclasses__())
- def model_name(self):
- return self.__class__.__name__
- model_name.short_description = 'Nom du modèle'
- def configuration_type_name(self):
- return self._meta.verbose_name
- configuration_type_name.short_description = 'Type'
- def get_absolute_url(self):
- """
- Renvoi l'URL d'accès à la page "details" de l'objet
- Une url doit être nommée "details"
- """
- from django.core.urlresolvers import reverse
- return reverse('%s:details' % self.get_url_namespace(),
- args=[str(self.id)])
- def get_url_namespace(self):
- """
- Renvoi le namespace utilisé par la configuration. Utilise en priorité
- celui définit dans la classe enfant dans url_namespace sinon
- par défaut utilise le nom de la classe en minuscule
- """
- if hasattr(self, 'url_namespace') and self.url_namespace:
- return self.url_namespace
- else:
- return self.model_name().lower()
- class Meta:
- verbose_name = 'configuration'
- @receiver(post_save, sender=OfferSubscription)
- def offer_subscription_event(sender, **kwargs):
- os = kwargs['instance']
- if not hasattr(os, 'configuration'):
- config_cls = None
- for subconfig_cls in Configuration.__subclasses__():
- if subconfig_cls().__class__.__name__ == os.offer.configuration_type:
- config_cls = subconfig_cls
- break
- if config_cls is not None:
- config = config_cls.objects.create(offersubscription=os)
- for offer_ip_pool in os.offer.offerippool_set.order_by('-to_assign'):
- IPSubnet.objects.create(
- configuration=config,
- ip_pool=offer_ip_pool.ip_pool)
- config.save()
- @receiver(post_save, sender=IPSubnet)
- def subnet_event_save(sender, **kwargs):
- kwargs["signal_type"] = "save"
- subnet_event(sender, **kwargs)
- @receiver(post_delete, sender=IPSubnet)
- def subnet_event_delete(sender, **kwargs):
- kwargs["signal_type"] = "delete"
- subnet_event(sender, **kwargs)
- subnet_log = logging.getLogger("coin.subnets")
- def subnet_event(sender, **kwargs):
- """Fires when a subnet is created, modified or deleted. We tell the
- configuration backend to do whatever it needs to do with it.
- Note that we could provide a more advanced API to configurations
- (subnet created, subnet modified, subnet deleted), but this is quite
- complicated to do. It's much simpler to simply tell the configuration
- model that something has changed in IP subnets. The configuration
- model can then access the list of its associated subnets (via the
- "ip_subnet" attribute) to decide for itself what it wants to do.
- We should use a pre_save/pre_delete signal, so that if anything goes
- wrong in the backend (exception raised), nothing is actually saved in
- the database: this provides consistency between the database and the
- backend. But if we do this, there is a major issue: the configuration
- backend will not see the new state of subnets by querying the
- database, since the changes have not been saved into the database yet.
- That's why we use a post_save/post_delete signal instead. In theory,
- this is a bad idea, because if the backend fails to do whatever it
- needs to do, the subnet will be saved into Django's database anyway,
- causing a desynchronisation with the backend. But surprisingly, even
- if not a documented feature of Django's signals, all is well: if we
- raise an exception here, the IPSubnet object will not be saved in the
- database. It looks like the database rollbacks if an exception is
- raised, which is great (even if undocumented).
- """
- subnet = kwargs['instance']
- try:
- config = subnet.configuration
- if hasattr(config, 'subnet_event'):
- config.subnet_event()
- offer = config.offersubscription.offer.name
- ref = config.offersubscription.get_subscription_reference()
- member = config.offersubscription.member
- ip = subnet.inet
- if kwargs['signal_type'] == "save":
- msg = "[Allocating IP] " + settings.IP_ALLOCATION_MESSAGE
- elif kwargs['signal_type'] == "delete":
- msg = "[Deallocating IP] " + settings.IP_ALLOCATION_MESSAGE
- else:
- # Does not happens
- msg = ""
- subnet_log.info(msg.format(ip=ip, member=member, offer=offer, ref=ref))
- except ObjectDoesNotExist:
- pass
|