# -*- 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') @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 self.url_namespace: return self.url_namespace else: return self.model_name().lower() class Meta: verbose_name = 'configuration' @receiver(post_save, sender=IPSubnet) def subnet_save_event(sender, **kwargs): """Fires when a subnet is saved (created/modified). We tell the configuration backend to do whatever it needs to do with it. We should use a pre_save signal, so that if anything goes wrong in the backend (exception raised), nothing is actually saved in the database. But it has a big problem: the configuration backend will not see the change, since it has not been saved into the database yet. That's why we use a post_save signal instead. But surprisingly, all is well: if we raise an exception here, the IPSubnet object will not be saved in the database. But the backend *does* see the new state of the database. It looks like the database rollbacks if an exception is raised. Whatever the reason, this is not a documented feature of Django signals. """ subnet = kwargs['instance'] try: config = subnet.configuration config.save_subnet(subnet, kwargs['created']) except ObjectDoesNotExist: pass @receiver(post_delete, sender=IPSubnet) def subnet_delete_event(sender, **kwargs): """Fires when a subnet is deleted. We tell the configuration backend to do whatever it needs to do with it. """ subnet = kwargs['instance'] try: config = subnet.configuration config.delete_subnet(subnet) except ObjectDoesNotExist: pass