# -*- coding: utf-8 -*- from __future__ import unicode_literals 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 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=IPSubnet) @receiver(post_delete, sender=IPSubnet) 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() except ObjectDoesNotExist: pass