models.py 4.4 KB

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